Right way to handle relations between entities in forms - symfony

I have a table stock which have relation 1:m with a table called warranty. I made this entity for stock table:
<?php
namespace StockBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Gedmo\Mapping\Annotation as Gedmo;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Bridge\Doctrine\Validator\Constraints as DoctrineAssert;
use CompanyBundle\Entity\Company;
use StockBundle\Entity\NStockStatus;
use ProductBundle\Entity\NLength;
use ProductBundle\Entity\NWeight;
/**
* #ORM\Table(name="stock")
* #ORM\Entity(repositoryClass="StockBundle\Entity\Repository\KStockRepository")
* #Gedmo\SoftDeleteable(fieldName="deletedAt")
*/
class KStock {
/**
* #ORM\Id
* #ORM\ManyToOne(targetEntity="ProductBundle\Entity\Product", inversedBy="stocks" )
* #ORM\JoinColumn(name="product", referencedColumnName="upc")
*/
protected $product;
/**
* #ORM\Id
* #ORM\ManyToOne(targetEntity="CompanyBundle\Entity\Company", inversedBy="companyHasStock" )
* #ORM\JoinColumn(name="company", referencedColumnName="id")
*/
protected $company;
/**
*
* #ORM\ManyToOne(targetEntity="StockBundle\Entity\NCondition", inversedBy="stocks" )
* #ORM\JoinColumn(name="kcondition", referencedColumnName="id")
*/
protected $condition;
/**
*
* #ORM\ManyToOne(targetEntity="StockBundle\Entity\NStockStatus", inversedBy="stocks" )
* #ORM\JoinColumn(name="status", referencedColumnName="id")
*/
protected $status;
/**
*
* #ORM\ManyToOne(targetEntity="StockBundle\Entity\Warranty", inversedBy="stocks" )
* #ORM\JoinColumn(name="warranty", referencedColumnName="id")
*/
// protected $warranty;
/**
* #ORM\Column(type="string", length=255, name="sku")
*/
protected $sku;
/**
* #ORM\Column(type="integer")
*/
protected $availability;
/**
* #ORM\Column(type="string", length=255)
*/
protected $description;
/**
*
* #ORM\Column(type="decimal", precision=19, scale=4)
*/
protected $price;
/**
* #ORM\ManyToOne(targetEntity="StockBundle\Entity\NUnit")
* #ORM\JoinColumn(name="unit", referencedColumnName="id")
*/
protected $unit;
/**
*
* #ORM\Column(type="decimal", precision=4, scale=2)
*/
protected $width;
/**
*
* #ORM\Column(type="decimal", precision=4, scale=2)
*/
protected $height;
/**
*
* #ORM\Column(type="decimal", precision=4, scale=2)
*/
protected $weight;
/**
*
* #ORM\Column(type="decimal", precision=4, scale=2)
*/
protected $length;
/**
*
* #ORM\Column(type="integer")
*/
protected $amount;
/**
* #ORM\ManyToOne(targetEntity="ProductBundle\Entity\NWeight")
* #ORM\JoinColumn(name="nweight", referencedColumnName="id")
*/
protected $nweight;
/**
* #ORM\ManyToOne(targetEntity="ProductBundle\Entity\NLength")
* #ORM\JoinColumn(name="nlength", referencedColumnName="id")
*/
protected $nlength;
/**
* #Gedmo\Timestampable(on="create")
* #ORM\Column(name="created", type="datetime")
*/
protected $created;
/**
* #Gedmo\Timestampable(on="update")
* #ORM\Column(name="modified", type="datetime")
*/
protected $modified;
/**
* #ORM\Column(name="deletedAt", type="datetime", nullable=true)
*/
protected $deletedAt;
public function setProduct(\ProductBundle\Entity\Product $param) {
$this->product = $param;
}
public function getProduct() {
return $this->product;
}
public function setCompany(\CompanyBundle\Entity\Company $param) {
$this->company = $param;
}
public function getCompany() {
return $this->company;
}
public function setCondition(\StockBundle\Entity\NCondition $condition) {
$this->condition = $condition;
}
public function getCondition() {
return $this->condition;
}
public function setStatus(\StockBundle\Entity\NStockStatus $param) {
$this->status = $param;
}
public function getStatus() {
return $this->status;
}
public function setSku($param) {
$this->sku = $param;
}
public function getSku() {
return $this->sku;
}
public function setAvailability($param) {
$this->availability = $param;
}
public function getAvailability() {
return $this->availability;
}
public function setDescription($description) {
$this->description = $description;
}
public function getDescription() {
return $this->description;
}
public function setPrice($param) {
$this->price = $param;
}
public function getPrice() {
return $this->price;
}
public function setUnit(\StockBundle\Entity\NUnit $unit) {
$this->unit = $unit;
}
public function getUnit() {
return $this->unit;
}
public function setWidth($width) {
$this->width = $width;
}
public function getWidth() {
return $this->width;
}
public function setHeight($height) {
$this->height = $height;
}
public function getHeight() {
return $this->height;
}
public function setWeight($weight) {
$this->weight = $weight;
}
public function getWeight() {
return $this->weight;
}
public function setLength($length) {
$this->length = $length;
}
public function getLength() {
return $this->length;
}
public function setAmount($amount) {
$this->amount = $amount;
}
public function getAmount() {
return $this->amount;
}
public function setCreated($created) {
$this->created = $created;
}
public function getCreated() {
return $this->created;
}
public function setModified($modified) {
$this->modified = $modified;
}
public function getModified() {
return $this->modified;
}
public function getDeletedAt() {
return $this->deletedAt;
}
public function setDeletedAt($deletedAt) {
$this->deletedAt = $deletedAt;
}
public function setNWeight(\ProductBundle\Entity\NWeight $nweight) {
$this->nweight = $nweight;
}
public function getNWeight() {
return $this->nweight;
}
public function setNLength(\ProductBundle\Entity\NLength $nlength) {
$this->nlength = $nlength;
}
public function getNLength() {
return $this->nlength;
}
public function __toString() {
return $this->company . ' -- ' . $this->product;
}
}
And this entity for Warranty table:
<?php
namespace StockBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Gedmo\Mapping\Annotation as Gedmo;
/**
* #ORM\Entity
* #ORM\Table(name="warranty")
* #Gedmo\SoftDeleteable(fieldName="deletedAt")
*/
class Warranty {
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*
*/
protected $id;
/**
* #ORM\ManyToOne(targetEntity="Warranty")
* #ORM\JoinColumn(name="parent", referencedColumnName="id")
*/
protected $parent;
/**
* #ORM\ManyToOne(targetEntity="ProductBundle\Entity\Product")
* #ORM\JoinColumn(name="product", referencedColumnName="upc")
*/
protected $product;
/**
* #ORM\ManyToOne(targetEntity="StockBundle\Entity\NCondition")
* #ORM\JoinColumn(name="kcondition", referencedColumnName="id")
*/
protected $kcondition;
/**
* #ORM\ManyToOne(targetEntity="CompanyBundle\Entity\Company")
* #ORM\JoinColumn(name="company", referencedColumnName="id")
*/
protected $company;
/**
*
* #ORM\Column(type="date")
*/
protected $valid_time;
/**
*
* #ORM\Column(type="text")
*/
protected $description;
/**
* #Gedmo\Timestampable(on="create")
* #ORM\Column(name="created", type="datetime")
*/
protected $created;
/**
* #Gedmo\Timestampable(on="update")
* #ORM\Column(name="modified", type="datetime")
*/
protected $modified;
/**
* #ORM\Column(name="deletedAt", type="datetime", nullable=true)
*/
protected $deletedAt;
/**
* #ORM\OneToMany(targetEntity="StockBundle\Entity\KStock")
*/
protected $stocks;
public function getId() {
return $this->id;
}
public function setParent(Warranty $parent = null) {
$this->parent = $parent;
}
public function getParent() {
return $this->parent;
}
public function setDescription($description) {
$this->description = $description;
}
public function getDescription() {
return $this->description;
}
public function setCreated($created) {
$this->created = $created;
}
public function getCreated() {
return $this->created;
}
public function setModified($modified) {
$this->modified = $modified;
}
public function getModified() {
return $this->modified;
}
public function getDeletedAt() {
return $this->deletedAt;
}
public function setDeletedAt($deletedAt) {
$this->deletedAt = $deletedAt;
}
public function setProduct(\ProductBundle\Entity\Product $product) {
$this->product = $product;
}
public function getProduct() {
return $this->product;
}
public function setCompany(\CompanyBundle\Entity\Company $company) {
$this->company = $company;
}
public function getCompany() {
return $this->company;
}
public function setKCondition(\StockBundle\Entity\NCondition $condition) {
$this->kcondition = $condition;
}
public function getKCondition() {
return $this->kcondition;
}
public function setValidTime($valid_time) {
$this->valid_time = $valid_time;
}
public function getValidTime() {
return $this->valid_time;
}
}
Then I create my KStockType.php with this code:
<?php
namespace StockBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use CatalogBundle\Form\KCatalogType;
class KStockType extends AbstractType {
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder->add('sku', 'text', array('required' => true, 'label' => 'SKU (Número de Referencia)'))
->add('price', 'money', array('label' => 'Precio', 'currency' => 'VEF'))
->add('unit', 'entity', array('label' => 'Moneda', 'class' => 'StockBundle:NUnit', 'property' => 'name', 'required' => true, 'multiple' => false, 'expanded' => false))
->add('amount', 'integer', array('label' => 'Cantidad'))
->add('status', 'entity', array('label' => 'Estado', 'class' => 'StockBundle:NStockStatus', 'property' => 'name', 'required' => true, 'multiple' => false, 'expanded' => false))
->add('condition', 'entity', array('label' => 'Condición del producto', 'class' => 'ProductBundle:NCondition', 'property' => 'name', 'required' => true, 'multiple' => false, 'expanded' => false))
->add('width', 'integer', array('required' => true, 'label' => 'Ancho'))
->add('height', 'integer', array('required' => true, 'label' => 'Alto'))
->add('length', 'integer', array('required' => true, 'label' => 'Largo'))
->add('nlength', 'entity', array('label' => 'Unidad de Medida', 'class' => 'ProductBundle:NLength', 'property' => 'name', 'required' => true, 'multiple' => false, 'expanded' => false))
->add('weight', 'integer', array('required' => true, 'label' => 'Peso'))
->add('nweight', 'entity', array('label' => 'Unidad de Peso', 'class' => 'ProductBundle:NWeight', 'property' => 'name', 'required' => true, 'multiple' => false, 'expanded' => false))
->add('description', 'textarea', array('label' => 'Descripción'))
->add('start_date', 'date', array('label' => 'Fecha (Inicio de la Publicación)', 'widget' => 'single_text', 'format' => 'yyyy-MM-dd', 'attr' => array('class' => 'dpicker')))
->add('warranty', 'textarea', array('required' => true, 'label' => 'Condiciones de Garantía'))
->add('valid_time', 'date', array('label' => 'Tiempo de Validez de la Garantía', 'widget' => 'single_text', 'format' => 'yyyy-MM-dd', 'attr' => array('class' => 'dpicker')));
}
/**
* #param OptionsResolverInterface $resolver
*/
public function setDefaultOptions(OptionsResolverInterface $resolver) {
$resolver->setDefaults(array(
'data_class' => 'StockBundle\Entity\KStock'
));
}
/**
* #return string
*/
public function getName() {
return 'stockbundle_kstock';
}
}
As you may notice I have some fields on that form that belongs to warranty. Then in my controller I have this method for edit action:
/**
* Stock
*
* #Route("/edit/{company_id}/{product_id}", name="stock_edit")
* #Method("GET")
*/
public function editAction($company_id, $product_id) {
$em = $this->getDoctrine()->getManager();
$entity = $em->getRepository('StockBundle:KStock')->findOneBy(array('company' => $company_id, 'product' => $product_id));
$response = array();
$response["response"] = true;
if (!$entity) {
$response['response'] = FALSE;
$response['message'] = 'Unable to find Stock entity.';
return new JsonResponse($response);
}
$product = $em->getRepository('ProductBundle:Product')->find($product_id);
$company = $em->getRepository('CompanyBundle:Company')->find($company_id);
if (!$product || !$company) {
$response['response'] = FALSE;
$response['message'] = 'Error not found product or company';
return new JsonResponse($response);
}
$editForm = $this->createForm(new KStockType(), $entity);
return $this->render("StockBundle:Stock:edit.html.twig", array('entity' => $entity, 'edit_form' => $editForm->createView()));
}
But when I call it I got this error:
Neither the property "start_date" nor one of the methods
"getStartDate()", "isStartDate()", "hasStartDate()", "_get()" or
"_call()" exist and have public access in class
"StockBundle\Entity\KStock".
What I am missing?

Take a look at this page: Symfony documentation
A little brief from that:
In more complex examples, you can embed entire forms, which is useful when
creating forms that expose one-to-many relationships
I think you have two possible aproaches:
1 - Look how to make forms with relational entities
2 - Make a form without a class and manage all information in your controller ( I wouldn't use this)

Try to replace your line about start_date in your form by:
->add('start_date', 'date', array('label' => 'Fecha (Inicio de la Publicación)', 'widget' => 'single_text', 'format' => 'yyyy-MM-dd', 'attr' => array('class' => 'dpicker'), 'mapped' => false))

Related

Not exprexted behaviour on symfony datatransformer

I created a datatransformer to store dates on unixtime on the database but show it as datetime on the form
this is the transformer
class IntegerToTimestampTransformer implements DataTransformerInterface
{
public function transform($timestamp)
{
/**
* This if sentenceis because when eidt $timestamp is a timestamp(unix)
* but when create is a DateTime ¿¿???
*/
if($timestamp instanceof \DateTime){
return $timestamp;
}
return (new \DateTime())->setTimestamp($timestamp);
}
public function reverseTransform($datetime)
{
if ($datetime === null) {
return $datetime;
}
return $datetime->getTimestamp();
}
}
this is the entity
/**
* Popup
*
* #ORM\Table(name="popup")
* #ORM\Entity(repositoryClass="AppBundle\Repository\PopUpRepository")
*/
class Popup
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #Gedmo\Translatable()
* #ORM\Column(name="title", type="string", length=255)
*/
private $title;
/**
* #var string
*
* #Gedmo\Translatable()
* #ORM\Column(name="description", type="text")
*/
private $description;
/**
* #var int
*
* #ORM\Column(name="activation_date", type="integer", nullable=true)
*/
private $activationDate;
/**
* #var int
*
* #ORM\Column(name="deactivation_date", type="integer", nullable=true)
*/
private $deactivationDate;
/**
* #var boolean
*
* #ORM\Column(name="active", type="boolean")
*/
private $active;
/**
* #var Collection
*
* #ORM\ManyToMany(targetEntity="AppBundle\Entity\Role")
* #ORM\JoinTable(name="popup_role", joinColumns={#ORM\JoinColumn(name="popup_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="role_id", referencedColumnName="id")})
*/
private $roles;
public function __construct()
{
$this->roles = new ArrayCollection();
}
/**
* #return int
*/
public function getId(): int
{
return $this->id;
}
/**
* #param int $id
*/
public function setId(int $id): void
{
$this->id = $id;
}
/**
* #return string
*/
public function getTitle():? string
{
return $this->title;
}
/**
* #param string $title
*/
public function setTitle(string $title): void
{
$this->title = $title;
}
/**
* #return string
*/
public function getDescription(): ?string
{
return $this->description;
}
/**
* #param string $description
*/
public function setDescription(string $description): void
{
$this->description = $description;
}
/**
* #return int
*/
public function getActivationDate(): ?int
{
return $this->activationDate;
}
/**
* #param int $activationDate
*/
public function setActivationDate(int $activationDate): void
{
$this->activationDate = $activationDate;
}
/**
* #return int
*/
public function getDeactivationDate(): ?int
{
return $this->deactivationDate;
}
/**
* #param int $deactivationDate
*/
public function setDeactivationDate(int $deactivationDate): void
{
$this->deactivationDate = $deactivationDate;
}
/**
* #return bool
*/
public function isActive(): ?bool
{
return $this->active;
}
/**
* #param bool $active
*/
public function setActive(bool $active): void
{
$this->active = $active;
}
/**
* Add role
*
* #param Role $role
*
* #return Role
*/
public function addRole(Role $role)
{
$this->roles[] = $role;
return $this;
}
/**
* Remove role
*
* #param Role $role
*/
public function removeRole(Role $role)
{
$this->roles->removeElement($role);
}
/**
* Get role
*
* #return Collection
*/
public function getRoles()
{
return $this->roles;
}
}
and the formtype
class PopupType extends AbstractType
{
private $tranformer;
public function __construct(IntegerToTimestampTransformer $tranformer){
$this->tranformer = $tranformer;
}
/**
* {#inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$date_array = [
'label_attr' => [
'class' => 'font-weight-bold'
],
'required' => false
];
if(!$options['edit']){
$date_array['data'] = new \DateTime();
}
$builder
->add('title', TextType::class, [
'label_attr' => [
'class' => 'font-weight-bold'
],
])
->add('description', TextareaType::class, [
'label_attr' => [
'class' => 'font-weight-bold'
],
'required' => false
])
->add('activation_date', DateTimeType::class, $date_array)
->add('deactivation_date', DateTimeType::class,$date_array)
->add('active', CheckboxType::class, [
'label_attr' => [
'class' => 'font-weight-bold'
],
'required' => false
])
->add('roles', EntityType::class, [
'class' => Role::class,
'expanded' => true,
'multiple' => true,
'required' => true,
'label_attr' => [
'class' => 'font-weight-bold'
],
])
;
$builder->get('activation_date')->addModelTransformer($this->tranformer);
$builder->get('deactivation_date')->addModelTransformer($this->tranformer);
}
/**
* {#inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => Popup::class,
'edit' => false
));
}
/**
* {#inheritdoc}
*/
public function getBlockPrefix()
{
return 'appbundle_popup';
}
}
I debugged and I checked that in the transform function when I try to create on the crud the parameter passed to transform is a Datetime the there is no need to transformate the data but when I try to edit the parameter passed to transform is a unixtime integer the the transformation is needed, I don't understant that behaviour.

Symfony3 error with ManyToMany with attributes

We have an error when trying to create a relationship within 2 tables like this
Llamadas -||--|<- LlamadaDerivada ->|--||- PersonaDerivada
And we are trying to create an only one create form with the "LlamadaDerivada" into it.
Inside Llamada entity
<?php
namespace xxxxBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
class Llamada {
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="SEQUENCE")
* #ORM\SequenceGenerator(sequenceName="llamada_id_seq", allocationSize=1, initialValue=1)
*/
private $id;
/**
* #var string
* #Assert\Length(
* max = 50,
* maxMessage = "Your first name cannot be longer than {{ limit }} characters",
* )
* #ORM\Column(name="nombre", type="string", length=150, nullable=false)
*/
private $nombre;
/**
*
* #ORM\OneToMany(targetEntity="LlamadaDerivado", mappedBy="llamada")
*/
private $derivados;
function __construct() {
$this->derivados = new ArrayCollection();
}
function getId() {
return $this->id;
}
function getNombre() {
return $this->nombre;
}
function setId($id) {
$this->id = $id;
}
function setNombre($nombre) {
$this->nombre = $nombre;
}
function getDerivados(){
return $this->derivados;
}
function setDerivados($derivados){
$this->derivados = $derivados;
}
}
Then inside LlamadaDerivado Entity we have this
<?php
namespace xxxBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* LlamadaDerivado
*
* #ORM\Table(name="llamada_derivado")
* #ORM\Entity
*/
class LlamadaDerivado
{
/**
* #var \AgendaBundle\Entity\Llamada
*
* #ORM\ManyToOne(targetEntity="AgendaBundle\Entity\Llamada",inversedBy="derivados",cascade={"persist"})
* #ORM\Id
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="id_llamada", referencedColumnName="id")
* })
*/
private $llamada;
/**
* #var \AgendaBundle\Entity\PersonaDerivado
*
* #ORM\ManyToOne(targetEntity="AgendaBundle\Entity\PersonaDerivado",inversedBy="llamadas",cascade={"persist"})
* #ORM\Id
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="id_derivado", referencedColumnName="id")
* })
*/
private $derivado;
/**
* #var DateTime
*
* #ORM\Column(name="fecha_derivacion", type="date", nullable=false)
*/
private $fechaDerivacion;
function getLlamada(){
return $this->llamada;
}
function getDerivado(){
return $this->derivado;
}
function getFechaDerivacion() {
return $this->fechaDerivacion;
}
function setLlamada( $llamada) {
$this->llamada = $llamada;
}
function setDerivado( $derivado) {
$this->derivado = $derivado;
}
function setFechaDerivacion($fechaDerivacion) {
$this->fechaDerivacion = $fechaDerivacion;
}
}
And inside PersonaDerivado entity
<?php
namespace xxxBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
/**
* ReunionLugar
*
* #ORM\Table(name="persona_derivado")
* #ORM\Entity
*/
class PersonaDerivado
{
public function __construct() {
$this->llamadas = new ArrayCollection();
}
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="SEQUENCE")
* #ORM\SequenceGenerator(sequenceName="reunion_lugar_id_seq", allocationSize=1, initialValue=1)
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="nombre", type="string", length=150, nullable=false)
*/
private $nombre;
/**
* #ORM\OneToMany(targetEntity="LlamadaDerivado", mappedBy="derivado")
*/
private $llamadas;
function getId() {
return $this->id;
}
function getNombre() {
return $this->nombre;
}
function setId($id) {
$this->id = $id;
}
function setNombre($nombre) {
$this->nombre = $nombre;
}
function setLlamadas($llamadas) {
$this->llamadas = $llamadas;
}
}
And the LlamadaType is
class LlamadaDto extends AbstractType {
public function buildForm(FormBuilderInterface $builder, array $options) {
$disabled = $options['disabled'];
$builder
->add('id', HiddenType::class)
->add('nombre', TextType::class, array(
'disabled' => $disabled,
'attr' => ['maxlength'=>'50']
))->add('apellido', TextType::class, array(
'disabled' => $disabled,
'attr' => ['maxlength'=>'50']
))->add('fecha', DateType::class, array(
'format' => 'dd/MM/yyyy',
'disabled' => $disabled,
'widget' => 'single_text',
'attr' => ['class' => 'datepicker']
))->add('hora', TimeType::class, array(
'disabled' => $disabled
))->add('motivo', TextareaType::class, array(
'disabled' => $disabled,
'attr' => ['maxlength'=>'400']
))->add('telefonoContacto', TextType::class, array(
'disabled' => $disabled,
'attr' => ['maxlength'=>'9']
))->add('derivados', EntityType::class, array(
'class' => 'AgendaBundle:PersonaDerivado',
'choice_label' => 'apellidoNombre',
'placeholder' => 'Seleccionar un derivado',
'multiple' => true,
));
}
public function configureOptions(OptionsResolver$resolver) {
$resolver->setDefaults(array('data_class' => Llamada::class));
}
}
Inside the controller we have this code
<?php
/**
* #Route("/Llamada/save",name="saveLlamada")
*/
public function saveLlamadaAction(Request $request) {
$llamadaService = $this->get('llamadaService');
$derivadoService = $this->get('derivadoService');
$form = $this->createForm(LlamadaDto::class);
$form->handleRequest($request);
$editar = TRUE;
$llamada = $form->getData();
$derivados = $request->request->get("llamada_dto")["derivados"];
$derivadosActuales = $derivadoService->getLlamadaDerivados($llamada->getId());
foreach ($derivados as $key1 => $d) {
foreach ($derivadosActuales as $key2 => $da) {
if($da->getDerivado()->getId()==$d){
array_splice($derivados, array_search($d, $derivados),1);
}
}
}
if ($llamadaService->saveLlamada($llamada)) {
$this->addFlash(
'exitoLlamada', 'Datos de llamada guardados exitosamente'
);
$derivadoService->saveDerivados($derivados,$llamada);
} else {
$this->addFlash(
'errorLlamada', 'Disculpe, hubo un error en el registro de la llamada'
);
}
return new RedirectResponse($this->generateUrl('listaLlamadas', array(), UrlGeneratorInterface::RELATIVE_PATH));
}
And the services called are this ones:
public function saveLlamada($llamada ){
try{
if($llamada->getId()){
$this->em->merge($llamada);
}else{
$this->em->persist($llamada);
}
$this->em->flush();
return TRUE;
} catch (Exception $ex){
return FALSE;
}
}
public function saveDerivados($derivados,$llamada){
foreach ($derivados as $key => $derivado) {
$llamadaDerivado = new LlamadaDerivado();
$personaLlamada = $this->getDerivado($derivado);
$llamadaDerivado->setLlamada($llamada);
$llamadaDerivado->setDerivado($personaLlamada);
$llamadaDerivado->setFechaDerivacion(new \DateTime('now', (new \DateTimeZone('America/Argentina/Ushuaia'))));
$this->em->persist($llamadaDerivado);
$this->em->flush();
}
}
This is the error that we are getting:
Uncaught PHP Exception Doctrine\ORM\ORMInvalidArgumentException: "Expected value of type "Doctrine\Common\Collections\Collection|array" for association field "xxxBundle\Entity\Llamada#$derivados", got "xxxBundle\Entity\PersonaDerivado" instead." at project\vendor\doctrine\orm\lib\Doctrine\ORM\ORMInvalidArgumentException.php line 206
We've been 1 week with this.
Many thanks in advance
You can try to use a CollectionType instead of EntityType in your formtype, although I have a piece of code in front of me that works just fine with EntityType and the multiple flag for a OneToMany relationship.
look at Llamada entity, $derivados is an array collection of LlamadaDerivado and in your LlamadaType you make it entityType of AgendaBundle:PersonaDerivado, that is why you got this error. Think to use Collection Type to add everytime all the object LlamadaDerivado to well respect the mapping.

One to One relationship in SonataAdmin

i trying to make admin page for Product which has relationship 1:1 with image.
Product
/**
* #ORM\Entity
* #ORM\Table(name="products")
class Product
{
/**
* #ORM\Column(type="integer")
* #ORM\GeneratedValue
* #ORM\Id
* #var int
*/
private $id = 0;
/**
* #ORM\OneToOne(targetEntity="Image", mappedBy="product")
*/
private $image;
/**
* #return Image
*/
public function getImage(): ?Image
{
return $this->image;
}
/**
* #param Image $image
*/
public function setImage(Image $image)
{
$this->image = $image;
return $this;
}
}
Image
/**
* #ORM\Entity
* #ORM\Table(name="images")
*/
class Image
{
/**
* #ORM\Column(type="integer")
* #ORM\GeneratedValue
* #ORM\Id
* #var int
*/
private $id = 0;
/**
* #ORM\OneToOne(targetEntity="Product", inversedBy="image")
* #ORM\JoinColumn(name="product_id", referencedColumnName="id")
*/
private $product;
/**
* #return mixed
*/
public function getProduct()
{
return $this->product;
}
public function setProduct(Product $product)
{
$this->product = $product;
}
}
ProductAdmin
class ProductAdmin extends AbstractAdmin
{
protected function configureFormFields(FormMapper $formMapper)
{
$formMapper->add('image', 'sonata_type_admin', array('label' => 'Okładka', 'by_reference' => false,));
}
ImageAdmin
class ImageAdmin extends AbstractAdmin
{
protected function configureFormFields(FormMapper $formMapper)
{
$formMapper
->add('file', 'file', array('label' => 'Okładka', 'required' => false))
->add('path', 'text', array('label' => 'Scieżka do pliku', 'required' => false));
}
I setuped services correctly, but i can't edit product and after saving new one i geting error
unable to find the object with id : 0
Try to not initialize your $id
private $id = 0; // =====> this is a private $id;
You have several mistakes. Let's try to correct your code.
Just follow the tutorials and put right annotation for $id:
/**
* #var integer $id
*
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
Hope, that this is just a typo with "?Image":
/**
* #return Image
*/
public function getImage() : Image
{
return $this->image;
}
And finally.
/**
* Class ProductAdmin
*/
class ProductAdmin extends AbstractAdmin
{
protected function configureFormFields(FormMapper $formMapper)
{
$formMapper
->add('image', 'sonata_type_model_list', [
'btn_add' => true, //Or you can specify a custom label
'btn_list' => 'list button!', //which will be translated
'btn_delete' => false, //or hide the button.
'btn_catalogue' => 'messages', //Custom translation domain for buttons
'label' => 'My image',
], [
'placeholder' => $this->trans('messages.no_images_message'),
'edit' => 'standard',
'inline' => 'standard',
'sortable' => 'id',
])
;
}
}

Symfony: don't save entity if a field is empty, don't block validation

I have to entities Project and Image. They are linked by OneToOne relation.
Here is the property definition in the project:
/**
* #Exclude
* #ORM\OneToOne(targetEntity="SensoBundle\Entity\Image", cascade={"persist", "remove"})
* #ORM\JoinColumn(nullable=true, onDelete="SET NULL")
*/
private $image;
Here is the image entity
<?php
namespace SensoBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\Validator\Constraints as Assert;
/**
* Image
*
* #ORM\Table(name="image")
* #ORM\HasLifecycleCallbacks()
* #ORM\Entity(repositoryClass="SensoBundle\Repository\ImageRepository")
*/
class Image
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="fileName", type="string", length=255)
*/
private $fileName;
/**
* #var string
*
* #ORM\Column(name="parentType", type="string", length=255, nullable=true)
*/
private $parentType;
private $tempFilename;
private $file;
private $fileNamePrefix;
private $fileExtension;
private $hashForName;
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set fileName
*
* #param string $fileName
* #return Image
*/
public function setFileName($fileName)
{
$this->fileName = $fileName;
return $this;
}
/**
* Get fileName
*
* #return string
*/
public function getFileName()
{
return $this->fileName;
}
public function getFile()
{
return $this->file;
}
public function setFile($file = null)
{
$this->file = $file;
if (null !== $this->fileName) {
$this->tempFilename = $this->fileName;
$this->fileName = null;
}
}
public function getUploadDir()
{
return '/uploads/img';
}
public function getUploadRootDir()
{
return __DIR__ . '/../../../web' . $this->getUploadDir();
}
/**
* #ORM\PrePersist()
* #ORM\PreUpdate()
*/
public function preUpload()
{
if (null === $this->file) {
return;
}
if (null !== $this->fileName) {
$this->tempFilename = $this->fileName;
}
$this->fileExtension = $this->file->guessExtension();
}
/**
* #ORM\PostPersist()
* #ORM\PostUpdate()
*/
public function upload()
{
if (null === $this->file) {
return;
}
if (null !== $this->tempFilename) {
$oldFile = $this->getUploadRootDir() . '/' . $this->tempFilename;
if (file_exists($oldFile)) {
unlink($oldFile);
}
}
$this->file->move(
$this->getUploadRootDir(),
$this->fileName
);
}
/**
* #ORM\PreRemove()
*/
public function preRemoveUpload()
{
$this->tempFilename = $this->getUploadRootDir() . '/' . $this->fileName;
}
/**
* #ORM\PostRemove()
*/
public function removeUplaod()
{
if (file_exists($this->tempFilename)) {
unlink($this->tempFilename);
}
}
public function setFileNamePrefix($prefix)
{
$this->fileNamePrefix = $prefix;
}
public function setHashForname($hash)
{
$this->hashForName = $hash;
}
/**
* Set parentType
*
* #param string $parentType
* #return Image
*/
public function setParentType($parentType)
{
$this->parentType = $parentType;
return $this;
}
/**
* Get parentType
*
* #return string
*/
public function getParentType()
{
return $this->parentType;
}
public function getFileExtension() {
return $this->fileExtension;
}
public function getFilenamePrefix() {
return $this->fileNamePrefix;
}
}
Here is the part of controller which is concerned :
/**
* #Route("/project/{id}/presentation/edit", name="project_presentation_edit")
* #Method({"GET", "POST"})
*/
public function editPresentationAction(Request $request, $id)
{
$entity = $this->get('creasenso.project_repo')->find($id);
if (!$entity) {
throw $this->createNotFoundException('The project does not exist');
}
$this->denyAccessUnlessGranted('editProject', $entity);
$form = $this->createForm('CreasensoBundle\Form\Project\ProjectPresentationType', $entity);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($entity);
$em->flush();
$this->get('session')->getFlashBag()->add('success', 'project.presentation.feedback');
$redirectRoute = ($request->request->get('nextSubmit')) ? 'project_details_edit' : 'project_presentation_edit';
return $this->redirectToRoute($redirectRoute, array('id' => $id));
}
return $this->render('CreasensoBundle:project:editPresentation.html.twig', array(
'form' => $form->createView(),
'entity' => $entity
));
}
Here is the form called in the controller above
<?php
// src/Infinite/InvoiceBundle/Form/Type/InvoiceType.php
namespace CreasensoBundle\Form\Project;
use Infinite\FormBundle\Form\Type\PolyCollectionType;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class ProjectPresentationType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('image', 'SensoBundle\Form\Image\ImageSimpleEditType', array(
'label' => 'Cover',
'attr' => array(
'help_text' => 'project.cover.help'
),
'display_image_format' => 'projectCoverSmall',
'imagesConstraints' => array(
'minWidth' => 300
)
))
->add('trans_name_fr', 'SensoBundle\Form\Type\FieldTranslationType', array(
'mapped' => false,
'cascade_validation' => true,
'fieldName' => 'name',
'fieldLocale' => 'fr',
'fieldOptions' => array(
'label' => 'French name',
'required' => true
)
))
->add('trans_name', 'SensoBundle\Form\Type\FieldTranslationType', array(
'mapped' => false,
'cascade_validation' => true,
'fieldName' => 'name',
'fieldLocale' => 'en',
'fieldOptions' => array(
'label' => 'English name',
'required' => true
)
))
;
}
public function getBlockPrefix()
{
return 'projectContent';
}
/**
* #param OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'CreasensoBundle\Entity\Project'
));
}
}
Here is my generic Image Entity Field
<?php
namespace SensoBundle\Form\Image;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormView;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\Form\Extension\Core\Type\FileType;
use Symfony\Component\Validator\Constraints\Image as ImageValidator;
use SensoBundle\Entity\Image;
class ImageSimpleEditType extends AbstractType
{
private $imageUrl = null;
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->addEventListener(FormEvents::POST_SET_DATA, function ($event) use ($builder, $options) {
$form = $event->getForm();
$imageFieldParams = array(
'label' => false,
'display_image_format' => $options['display_image_format'],
'required' => false
);
if (!empty($options['imagesConstraints'])) {
// Image format
$constraints = $options['imagesConstraints'];
$validatorParams = array();
if (!empty($constraints['format'])) {
switch ($constraints['format']) {
case 'square':
$squareErrMsg = "image.edit.squareError";
$validatorParams['allowSquare'] = true;
$validatorParams['allowLandscape'] = false;
$validatorParams['allowPortrait'] = false;
$validatorParams['allowLandscapeMessage'] = $squareErrMsg;
$validatorParams['allowPortraitMessage'] = $squareErrMsg;
break;
case 'landscape':
$landscapeErrMsg = "image.edit.landscapeError";
$validatorParams['allowSquare'] = false;
$validatorParams['allowLandscape'] = true;
$validatorParams['allowPortrait'] = false;
$validatorParams['allowLandscapeMessage'] = $landscapeErrMsg;
$validatorParams['allowPortraitMessage'] = $landscapeErrMsg;
break;
case 'portrait':
$portraitErrMsg = "image.edit.portraitError";
$validatorParams['allowSquare'] = false;
$validatorParams['allowLandscape'] = false;
$validatorParams['allowPortrait'] = true;
$validatorParams['allowLandscapeMessage'] = $portraitErrMsg;
$validatorParams['allowPortraitMessage'] = $portraitErrMsg;
break;
}
// Min width
if (!empty($constraints['minWidth'])) {
$validatorParams['minWidth'] = $constraints['minWidth'];
}
}
$imageFieldParams['constraints'] = new ImageValidator($validatorParams);
}
$form->add('file', 'SensoBundle\Form\Image\ImageInputType', $imageFieldParams);
});
}
/**
* #param OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'SensoBundle\Entity\Image',
'display_image_format' => 'original',
'imagesConstraints' => null
));
}
public function getBlockPrefix()
{
return 'sensoImage';
}
}
And finally, here is my generic image file input (used in the image entity form)
<?php
namespace SensoBundle\Form\Image;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormView;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\Form\Extension\Core\Type\FileType;
use SensoBundle\Entity\Image;
use Symfony\Component\Validator\Constraints\Image as ImageValidator;
class ImageInputType extends AbstractType
{
private $imageManager;
public function __construct($imageManager)
{
$this->imageManager = $imageManager;
}
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
}
//
public function buildView(FormView $view, FormInterface $form, array $options)
{
$view->vars['image_url'] = null;
$imageEntity = $form->getParent()->getData();
if ($imageEntity) {
$this->imageManager->setEntity($imageEntity);
$view->vars['image_url'] = $this->imageManager->getRelativeUrl();
}
$view->vars['display_image_format'] = $options['display_image_format'];
}
/**
* #param OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'display_image_format' => 'original',
'format' => null
));
}
public function getBlockPrefix()
{
return 'sensoImageInput';
}
public function getParent()
{
return 'Symfony\Component\Form\Extension\Core\Type\FileType';
}
}
My form structure is like that
-- Project form (class ProjectPresentationType)
---- Image entity field (class ImageSimpleEditType)
------ Image input (class ImageInputType)
My problem is an image entity is created even if no file is uploaded.
What I would like is to be able to create a new project with this "features":
Image upload is not required
If image field is empty, don't create an Image entity
Thank you for your help.

Symfony2 try to insert ManyToMany relation before insert one part of both sides

I have this Entities:
<?php
namespace ProductBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Gedmo\Mapping\Annotation as Gedmo;
use ProductBundle\DBAL\Types\StatusType;
use ProductBundle\DBAL\Types\FieldType;
use Fresh\Bundle\DoctrineEnumBundle\Validator\Constraints as DoctrineAssert;
/**
* #ORM\Entity
* #ORM\Table(name="product_detail")
*/
class ProductDetail {
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\ManyToOne(targetEntity="ProductDetail")
* #ORM\JoinColumn(name="parent", referencedColumnName="id")
*/
protected $parent;
/**
* #ORM\Column(type="string", length=255)
*/
protected $description;
/**
* #ORM\Column(type="string", length=255)
*/
protected $label;
/**
* #var string $field_type
* #DoctrineAssert\Enum(entity="ProductBundle\DBAL\Types\FieldType")
* #ORM\Column(name="field_type", type="FieldType", nullable=false)
*/
protected $field_type;
/**
* #ORM\Column(name="values_text", type="string")
*/
protected $values_text;
/**
* #ORM\Column(type="string", length=255)
*/
protected $measure_unit;
/**
* #var string $status
* #DoctrineAssert\Enum(entity="ProductBundle\DBAL\Types\StatusType")
* #ORM\Column(name="status", type="StatusType", nullable=false)
*/
protected $status;
/**
* #Gedmo\Timestampable(on="create")
* #ORM\Column(name="created", type="datetime")
*/
protected $created;
/**
* #Gedmo\Timestampable(on="update")
* #ORM\Column(name="modified", type="datetime")
*/
protected $modified;
/**
* #ORM\ManyToMany(targetEntity="CategoryBundle\Entity\Category", inversedBy="pd_detail")
* #ORM\JoinTable(name="product_detail_has_category",
* joinColumns={#ORM\JoinColumn(name="category", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="detail", referencedColumnName="id")}
* )
*/
protected $category;
/**
* #ORM\ManyToMany(targetEntity="ProductBundle\Entity\DetailGroup", inversedBy="productDetail")
* #ORM\JoinTable(name="detail_group_has_product_detail",
* joinColumns={#ORM\JoinColumn(name="group", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="detail", referencedColumnName="id")}
* )
*/
protected $detail_group;
public function __construct() {
$this->detail_group = new \Doctrine\Common\Collections\ArrayCollection();
$this->category = new \Doctrine\Common\Collections\ArrayCollection();
}
public function getId() {
return $this->id;
}
public function setParent(ProductDetail $parent = null) {
$this->parent = $parent;
}
public function getParent() {
return $this->parent;
}
public function setDescription($description) {
$this->description = $description;
}
public function getDescription() {
return $this->description;
}
public function setLabel($label) {
$this->label = $label;
}
public function getLabel() {
return $this->label;
}
public function setFieldType($field_type) {
$this->field_type = $field_type;
}
public function getFieldType() {
return $this->field_type;
}
public function setValuesText($values_text) {
$this->values_text = $values_text;
}
public function getValuesText() {
return $this->values_text;
}
public function setMeasureUnit($measure_unit) {
$this->measure_unit = $measure_unit;
}
public function getMeasureUnit() {
return $this->measure_unit;
}
public function setStatus($status) {
$this->status = $status;
}
public function getStatus() {
return $this->status;
}
public function setCreated($param) {
$this->created = $param;
return true;
}
public function getCreated() {
return $this->created;
}
public function setModified($param) {
$this->modified = $param;
return true;
}
public function getModified() {
return $this->modified;
}
public function setCategory(Category $category) {
$this->category[] = $category;
}
public function getCategory() {
return $this->category;
}
public function setDetailGroup(DetailGroup $detailGroup) {
$this->detail_group[] = $detailGroup;
}
public function getDetailGroup() {
return $this->detail_group;
}
}
namespace ProductBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Gedmo\Mapping\Annotation as Gedmo;
use Fresh\Bundle\DoctrineEnumBundle\Validator\Constraints as DoctrineAssert;
/**
* #ORM\Entity
* #ORM\Table(name="detail_group")
*/
class DetailGroup {
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\ManyToOne(targetEntity="DetailGroup")
* #ORM\JoinColumn(name="parent", referencedColumnName="id")
*/
protected $parent;
/**
* #ORM\Column(type="string", length=255)
*/
protected $name;
/**
* #ORM\Column(type="string", length=255)
*/
protected $description;
/**
* #Gedmo\Timestampable(on="create")
* #ORM\Column(name="created", type="datetime")
*/
protected $created;
/**
* #Gedmo\Timestampable(on="update")
* #ORM\Column(name="modified", type="datetime")
*/
protected $modified;
/**
* #ORM\ManyToMany(targetEntity="ProductBundle\Entity\ProductDetail", mappedBy="detail_group", cascade={"all"})
*/
protected $productDetail;
public function __construct() {
$this->productDetail = new \Doctrine\Common\Collections\ArrayCollection();
}
public function getId() {
return $this->id;
}
public function setParent(DetailGroup $parent = null) {
$this->parent = $parent;
}
public function getParent() {
return $this->parent;
}
public function setName($name) {
$this->name = $name;
}
public function getName() {
return $this->name;
}
public function setDescription($description) {
$this->description = $description;
}
public function getDescription() {
return $this->description;
}
public function setCreated($param) {
$this->created = $param;
return true;
}
public function getCreated() {
return $this->created;
}
public function setModified($param) {
$this->modified = $param;
return true;
}
public function getModified() {
return $this->modified;
}
public function setProductDetail(ProductDetail $productDetail) {
$this->productDetail[] = $productDetail;
}
public function getProductDetail() {
return $this->productDetail;
}
}
This is my ProductDetailType.php form:
<?php
namespace ProductBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class ProductDetailType extends AbstractType {
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder
->add('category', 'entity', array('class' => 'CategoryBundle:Category', 'property' => 'name', 'required' => false, 'multiple' => true, 'expanded' => false))
->add('detail_group', 'entity', array('class' => 'ProductBundle:DetailGroup', 'property' => 'name', 'required' => false, 'multiple' => true, 'expanded' => false))
->add('parent', 'entity', array('class' => 'ProductBundle:ProductDetail', 'property' => 'label', 'required' => false))
->add('description', 'text', array('required' => false))
->add('label')
->add('field_type', 'choice', ['choices' => \ProductBundle\DBAL\Types\FieldType::getChoices()])
->add('values_text', 'collection', array('type' => 'text', 'allow_add' => true, 'allow_delete' => true, 'by_reference' => false))
->add('measure_unit', 'text', array('required' => false))
->add('status', 'choice', ['choices' => \ProductBundle\DBAL\Types\StatusType::getChoices()]);
}
public function setDefaultOptions(OptionsResolverInterface $resolver) {
$resolver->setDefaults(array(
'data_class' => 'ProductBundle\Entity\ProductDetail'
));
}
public function getName() {
return 'prod_detail_create';
}
}
And this is my createAction():
/**
* Handle product details creation
*
* #Route("/product_detail/create", name="product_detail_create")
* #Method("POST")
* #Template("ProductBundle:ProductDetail:new.html.twig")
*/
public function createAction(Request $request) {
$entity = new ProductDetail();
$form = $this->createForm(new ProductDetailType(), $entity);
$form->handleRequest($request);
if ($form->isValid()) {
$em = $this->getDoctrine()->getManager();
$entity->setValuesText(serialize($form->get('values_text')->getData()));
$em->persist($entity);
$em->flush();
return $this->redirect($this->generateUrl('product_detail_list'));
}
return $this->render('ProductBundle:ProductDetail:new.html.twig', array(
'entity' => $entity,
'form' => $form->createView(),
));
}
When I try to insert new records I get this errors:
An exception occurred while executing 'INSERT INTO
product_detail_has_category (category, detail) VALUES (?, ?)' with
params [7, 2]:
SQLSTATE[23000]: Integrity constraint violation: 1452 Cannot add or
update a child row: a foreign key constraint fails
(product_detail_has_category, CONSTRAINT
fk_product_detail_has_category_product_detail1 FOREIGN KEY
(detail) REFERENCES product_detail (id) ON UPDATE CASCADE)
I check the logs and see this:
DEBUG - "START TRANSACTION"
DEBUG - INSERT INTO product_detail (description, label, field_type, values_text, measure_unit, status, created, modified, parent) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
DEBUG - INSERT INTO product_detail_has_category (category, detail) VALUES (?, ?)
DEBUG - "ROLLBACK"
I don't know why it doesn't insert product_detail and the tries to create the relation, what is wrong?
You can either:
set on $category field set cascade={"PERSIST"} in #ManyToMany.
This requires no chnages to controller itself but it depends on whether you like defining cascades.
persist Category object manually before persisting ProductDetail.
$em = ...; // your entity manager
// first, manually persist any category that is found
foreach ( $productDetail->getCategory() as $c ){
$em->persist($c);
}
// then, persist top-level object
$em->persist($productDetail);
// finally, flush everything
$em->flush();
Hope this helps a bit...
I've found where the errors was, see the changes below in annotations:
/**
* #ORM\ManyToMany(targetEntity="CategoryBundle\Entity\Category", inversedBy="pd_detail", cascade={"persist"})
* #ORM\JoinTable(name="product_detail_has_category",
* joinColumns={#ORM\JoinColumn(name="detail", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="category", referencedColumnName="id")}
* )
*/
protected $category;
/**
* #ORM\ManyToMany(targetEntity="ProductBundle\Entity\DetailGroup", inversedBy="productDetail", cascade={"persist"})
* #ORM\JoinTable(name="detail_group_has_product_detail",
* joinColumns={#ORM\JoinColumn(name="detail", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="kgroup", referencedColumnName="id")}
* )
*/
protected $detail_group;
I've inverted the relations in joinColumns and inversedJoinColumns and all works fine, also has to rename group to kgroup due to MySQL query errors!!

Resources