I have the following relations:
[entity]SesiuneUtilitati-|one_to_one|->[entity]UtilitatiElectricitateCoeficient-|one_to_many|->[entity]UtilitatiElectricitate
I want to create a form that includes all 3 entities
And the code:
<?php
namespace Mnv\CoreBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
/**
* UtilitatiElectricitateCoeficient
*
* #ORM\Table(name="utilitati_electricitate_coeficient")
* #ORM\Entity(repositoryClass="Mnv\CoreBundle\Entity\Repository\UtilitatiElectricitateCoeficientRepository")
* #ORM\HasLifecycleCallbacks()
*/
class UtilitatiElectricitateCoeficient
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var decimal
*
* #ORM\Column(name="kwh", type="decimal", precision=20, scale=2, nullable=true)
*/
private $kwh;
/**
* #var decimal
*
* #ORM\Column(name="lei", type="decimal", precision=20, scale=2, nullable=true)
*/
private $lei;
/**
* #var decimal
*
* #ORM\Column(name="leikw", type="decimal", precision=20, scale=2, nullable=true)
*/
private $leikw;
/**
* #var decimal
*
* #ORM\Column(name="coeficient", type="decimal", precision=20, scale=2, nullable=true)
*/
private $coeficient;
/**
* #var string
*
* #ORM\Column(name="data_modificat", type="datetime", nullable=true)
*/
private $dataModificat;
/**
* #var string
*
* #ORM\Column(name="editat_de", type="string", length=50, nullable=true)
*/
private $editatDe;
/**
* #ORM\OneToOne(targetEntity="SesiuneUtilitati", inversedBy="utilitatiElectricitateCoeficient")
* #ORM\JoinColumn(name="id_sesiune", referencedColumnName="id_sesiune", onDelete="CASCADE")
*/
protected $sesiuneUtilitati;
/**
* #ORM\ManyToOne(targetEntity="Clienti", inversedBy="utilitatiElectricitateCoeficient")
* #ORM\JoinColumn(name="id_client", referencedColumnName="id_client")
*/
protected $clienti;
/**
* #ORM\OneToMany(targetEntity="UtilitatiElectricitate", mappedBy="utilitatiElectricitateCoeficient", cascade={"persist", "remove"}, orphanRemoval=true)
*/
private $utilitatiElectricitate;
public function __construct() {
$this->utilitatiElectricitate = new ArrayCollection();
}
...
}
<?php
namespace Mnv\CoreBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
/**
* UtilitatiElectricitate
*
* #ORM\Table(name="utilitati_electricitate")
* #ORM\Entity(repositoryClass="Mnv\CoreBundle\Entity\Repository\UtilitatiElectricitateRepository")
* #ORM\HasLifecycleCallbacks()
*/
class UtilitatiElectricitate
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var decimal
*
* #ORM\Column(name="kwh", type="decimal", precision=20, scale=2, nullable=true)
*/
private $kwh;
/**
* #var decimal
*
* #ORM\Column(name="pret", type="decimal", precision=20, scale=2, nullable=true)
*/
private $pret;
/**
* #var decimal
*
* #ORM\Column(name="total", type="decimal", precision=20, scale=2, nullable=true)
*/
private $total;
/**
* #var string
*
* #ORM\Column(name="data_modificat", type="datetime", nullable=true)
*/
private $dataModificat;
/**
* #var string
*
* #ORM\Column(name="editat_de", type="string", length=50, nullable=true)
*/
private $editatDe;
/**
* #ORM\ManyToOne(targetEntity="UtilitatiElectricitateCoeficient", inversedBy="utilitatiElectricitate")
* #ORM\JoinColumn(name="id_coeficient", referencedColumnName="id")
*/
protected $utilitatiElectricitateCoeficient;
/**
* #ORM\ManyToOne(targetEntity="dateClienti", inversedBy="utilitatiElectricitate")
* #ORM\JoinColumn(name="id_contract", referencedColumnName="id")
*/
protected $dateClienti;
...
}
<?php
namespace Mnv\CoreBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class UtilitatiElectricitateCoeficientType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('lei','text', array('required' => false));
$builder->add('coeficient','text', array('required' => false));
$builder->add('kwh','text', array('required' => false));
$builder->add('leikw','text', array('required' => false));
$builder->add('utilitatiElectricitate', 'collection', array(
'type' => new UtilitatiElectricitateType(),
'allow_add' => true,
'prototype' => false,
'by_reference' => false
));
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Mnv\CoreBundle\Entity\UtilitatiElectricitateCoeficient',
'cascade_validation' => true,
));
}
public function getName()
{
return 'utilitati_electricitate_coeficient';
}
}
<?php
namespace Mnv\CoreBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class UtilitatiElectricitateType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('kwh','text', array('required' => false));
$builder->add('pret','text', array('required' => false));
$builder->add('total','text', array('required' => false));
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Mnv\CoreBundle\Entity\UtilitatiElectricitate',
'cascade_validation' => true,
));
}
public function getName()
{
return 'utilitati_electricitate';
}
}
public function indexAction(Request $request, $idSocietate)
{
$societate = $this->getSocietate($idSocietate);
$clienti = $this->getClienti($idSocietate);
$sesiuneUtilitati = new SesiuneUtilitati();
$dateClienti = new DateClienti();
$utilitatiElectricitateCoeficient = new UtilitatiElectricitateCoeficient();
$utilitatiElectricitate = new UtilitatiElectricitate();
$utilitatiElectricitateCoeficient->getUtilitatiElectricitate()->add($utilitatiElectricitate);
...
$form = $this->createForm(new SesiuneUtilitatiType(), $sesiuneUtilitati);
$request = $this->get('request');
if ($request->getMethod() == 'POST') {
$form->bind($request);
if ($form->isValid()) {
// Persist objects to database
$usr = $this->get('security.context')->getToken()->getUser();
$now = new \DateTime();
$date = $now->format("d-m-Y");
$dt = explode('-', $date);
$em = $this->getDoctrine()->getManager();
$sesiuneUtilitati->setSocietati($societate);
$sesiuneUtilitati->setAdaugataDe($usr->getUsername());
$sesiuneUtilitati->setZiua($dt[0]);
$sesiuneUtilitati->setLuna($dt[1]);
$sesiuneUtilitati->setAnul($dt[2]);
$em->persist($sesiuneUtilitati);
$em->flush();
$this->get('session')->getFlashBag()->set('tabel-util-notice', 'Datele au fost salvate cu succes.');
// Redirect - This is important to prevent users re-posting
// the form if they refresh the page
return $this->redirect($this->generateUrl('tabel_utilitati',array('idSocietate' => $idSocietate)));
}
}
return $this->render('MnvCoreBundle:Page:utilitati.html.twig', array(
'societate' => $societate,
'form' => $form->createView(),
'clienti' => $clienti,
));
}
What am i doing wrong here? The fields for the collection field utilitatiElectricitate are not available in twig.
For example if i try form.utilitatiElectricitateCoeficient.utilitatiElectricitate[0].kwh i get an error with key 0 does not exist.
SesiuneUtilitatiType is created to include all other forms like this:
<?php
namespace Mnv\CoreBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class SesiuneUtilitatiType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('utilitatiTotal', new UtilitatiTotalType());
$builder->add('utilitatiElectricitateCoeficient', new UtilitatiElectricitateCoeficientType());
.
.
.
$builder->add('Salveaza datele si genereaza facturi', 'submit');
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Mnv\CoreBundle\Entity\SesiuneUtilitati',
'cascade_validation' => true,
));
}
public function getName()
{
return 'sesiune_utilitati';
}
}
And UtilitatiElectricitateCoeficientType:
<?php
namespace Mnv\CoreBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class UtilitatiElectricitateCoeficientType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('lei','text', array('required' => false));
$builder->add('coeficient','text', array('required' => false));
$builder->add('kwh','text', array('required' => false));
$builder->add('leikw','text', array('required' => false));
$builder->add('utilitatiElectricitate', 'collection', array(
'type' => new UtilitatiElectricitateType(),
'allow_add' => true,
'prototype' => true,
'by_reference' => false
));
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Mnv\CoreBundle\Entity\UtilitatiElectricitateCoeficient',
'cascade_validation' => true,
));
}
public function getName()
{
return 'utilitati_electricitate_coeficient';
}
}
And UtilitatiElectricitateType:
<?php
namespace Mnv\CoreBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class UtilitatiElectricitateType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('kwh','text', array('required' => false));
$builder->add('pret','text', array('required' => false));
$builder->add('total','text', array('required' => false));
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Mnv\CoreBundle\Entity\UtilitatiElectricitate',
'cascade_validation' => true,
));
}
public function getName()
{
return 'utilitati_electricitate';
}
}
You have to create a SesiuneUtilitatiType which you will embed into the UtilitatiElectricitateCoeficientType, now this last one will be embedded into UtilitatiElectricitateType. You should read the docs for embedded forms to have a clear view of what rules to follow
Related
Summary
I have three entities:
User, Organisation and OrganistionUser.
I created an Organisation form with an embedded collection of OrganisationUsers. This is working. What i can't get to work is that only Users without an assocciation (to an organisation) are 'queried' and show up in the selectbox in the OrganisationForm.
Details
Entitie 1: Organisation
class Organisation
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
*
* #ORM\Column(type="string", nullable=true)
*/
protected $name;
/**
* #ORM\OneToMany(targetEntity="OrganisationUser", mappedBy="organisation_id", cascade={"persist", "remove"}, orphanRemoval=true)
*
* #Expose
*/
private $users;
Entitie 2: User
(I extended the FOSUserBundle)
<?php
/**
* #ORM\Entity(repositoryClass="AppBundle\Repository\UserRepository")
* #ORM\Table(name="fos_user")
*/
class User extends BaseUser
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #var string
*/
protected $username;
/**
* #var \Doctrine\Common\Collections\ArrayCollection
* #ORM\OneToMany(targetEntity="OrganisationUser", mappedBy="user_id", cascade={"ALL"}, orphanRemoval=true)
*/
protected $organisationusers;
I have succesfully embedded a collection of forms. In my 'create-new-organisation-form' i can add many users, and they are persisted to the database.
They are persisted in the OrganisationUser table (because i only want to associate EXISTING users to an organisation).
The OrganisationUser entity (so actually i have three entities) looks as:
Entitie 3: OrganisationUser
<?php
class OrganisationUser
{
protected $id;
/**
* #var ProjectId
* #ORM\ManyToOne(targetEntity="Organisation", inversedBy="organisationusers")
* #ORM\JoinColumn(name="organisation_id", referencedColumnName="id", onDelete="CASCADE")
*/
protected $organisation_id;
/**
* #var ProjectId
* #ORM\ManyToOne(targetEntity="User", inversedBy="organisationusers")
* #ORM\JoinColumn(name="user_id", referencedColumnName="id", onDelete="CASCADE")
*/
protected $user_id;
NOW, in my form (OrganisionType) i embedded a collection of OrganisationUser. But i want to manipulate the data (i do not want to show all users. Only the user that are NOT associated to an organisation). How can i achieve this. I already looked here and here but it's no solution for this question so i created a new question.
The OrganistionType:
<?php
class OrganisationType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name')
->add('address')
->add('postal')
->add('city')
->add('phone')
->add('email')
->add('role');
$builder->add('users', 'collection', array(
'entry_type' => new OrganisationUserType(),
'allow_add' => true,
'allow_delete' => true,
'by_reference' => false,
'required' => false,
));
}
/**
* #param OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\Organisation',
));
}
}
And the OrganisationUserType:
<?php
class OrganisationUserType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('user_id', null ,array(
'label' => 'User'
))
;
}
/**
* #param OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\OrganisationUser'
));
}
}
The controller
<?php
/**
* Organisation controller.
*
* #Route("/organisation")
*/
class OrganisationController extends Controller
{
/**
* Creates a new Organisation entity.
*
* #Route("/new", name="organisation_new")
* #Method({"GET", "POST"})
*/
public function newAction(Request $request)
{
$organisation = new Organisation();
$form = $this->createForm('AppBundle\Form\OrganisationType', $organisation);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($organisation);
$em->flush();
return $this->redirectToRoute('organisation_show', array('id' => $organisation->getId()));
}
return $this->render('organisation/new.html.twig', array(
'organisation' => $organisation,
'form' => $form->createView(),
));
}
This is how it looks like when its rendered:
And, let's say if testuser3 is already associated with an organisation i dont want him to show up in the dropdown :)
Ok, i found it. In my OrganisationType i use the OrganisationUserType to embed the collection of users. So in the OrganisationUserType i had to query for the result:
Off course this is not the right query but i know now where to manipulate the data. Now i can go and search for the right query :)
<?php
namespace AppBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Doctrine\ORM\EntityRepository;
class OrganisationUserType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('user_id', 'entity', array(
'class' => 'AppBundle:User',
'query_builder' => function (EntityRepository $er) {
return $er->createQueryBuilder('u')
->where('u.id > :id')
->setParameter('id', '1')
->orderBy('u.username', 'ASC');
},
'choice_label' => 'username',
'required' => false,
));
}
/**
* #param OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\OrganisationUser',
));
}
}
I already have to ask a question about a similar assert problem with FosUserBundle configuation.
But now I'm trying to run a simple form, but even with a esiest form => nothing is happening.
when i click on submit :
1) isValid() stay to false
2) No Assert message appears when input name is blank/empty
UserTmp.php (entity)
<?php
namespace BISSAP\UserBundle\Entity;
use FOS\UserBundle\Model\User as BaseUser;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
/**
* BISSAP\UserBundle\Entity\User
*
* #ORM\Table()
* #ORM\Entity(repositoryClass="BISSAP\UserBundle\Entity\UserRepository")
*/
class Usertmp
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\Column(name="name", type="string", length=255)
*
* #Assert\NotBlank(message="Please enter your name.", groups={"Registration", "Profile"})
* #Assert\Length(
* min=3,
* max=255,
* minMessage="The name is too short.",
* maxMessage="The name is too long.",
* groups={"Registration", "Profile"}
* )
*/
private $name;
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set name
*
* #param string $name
* #return Usertmp
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Get name
*
* #return string
*/
public function getName()
{
return $this->name;
}
}
UserType.php
<?php
namespace BISSAP\ForumBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class UserType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name', 'text', array('required' => false))
->add('Envoyer', 'submit', array(
'attr' => array(
'class' => 'btn right-flt'
)));
}
/**
* #param OptionsResolverInterface $resolver
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'BISSAP\UserBundle\Entity\Usertmp'
));
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array( 'data_class' => $this->class, 'intention' => 'Registration', ));
}
/**
* #return string
*/
public function getName()
{
return 'bissap_forumbundle_user';
}
}
TController.php
<?php
namespace BISSAP\ForumBundle\Controller;
use Doctrine\Common\DataFixtures\FixtureInterface;
use Doctrine\Common\Persistence\ObjectManager;
use BISSAP\ForumBundle\Form\UserType;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use BISSAP\UserBundle\Entity\Usertmp;
class TController extends Controller
{
public function indexAction()
{
$entity = new Usertmp();
$form = $this->createForm(new UserType(), $entity);
if ($form->isValid())
{
return $this->redirectToRoute('bissap_forum_index');
}
return $this->render('BISSAPForumBundle:T:index.html.twig', array('form'=> $form->createView(), 'errors_tmp' => $this->getErrorMessages($form)));
}
private function getErrorMessages(\Symfony\Component\Form\Form $form)
{
$errors = array();
foreach ($form->getErrors(true, false) as $error) {
// My personnal need was to get translatable messages
// $errors[] = $this->trans($error->current()->getMessage());
$errors[] = $error->current()->getMessage();
}
return $errors;
}
}
?>
index.html.twig
----> {{form( form )}}
{% for error in errors_tmp %}
<div>error : {{ error }}</div>
{% endfor %}
So, form didn't work cause : $form->handleRequest($request); missed in TController.php
In your UserType.php, try:
$resolver->setDefaults(array( 'data_class' => $this->class, 'intention' => 'Registration', 'validation_groups' => array('registration'),));
You can also set the validation group in your TController.php instead of hard-coding it on your UserType:
$form = $this->createForm(new UserType(), $entity, array('validation_groups' => 'registration'));
Source: Validation Groups
Also in your UserType.php you are saying that the name is not required, but at the same time you want to assert if it is not blank:
->add('name', 'text', array('required' => false))
Try removing that option too.
I have 3 form types (SearchForm - SearchField - SearchFieldType), each one including next like this:
SearchFormType:
<?php
namespace AppBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class SearchFormType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('fields', 'collection', array('type' => new SearchFieldType(),
'allow_add' => true,
'allow_delete' => true,
'by_reference' => false))
->add('submit', 'submit', array('label' => "Buscar"))
;
}
/**
* #param OptionsResolverInterface $resolver
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\SearchForm',
'allow_extra_fields' => true,
'csrf_protection' => false,
'validation_groups' => false,
));
}
/**
* #return string
*/
public function getName()
{
return 'appbundle_searchform';
}
}
SearchFieldType:
<?php
namespace AppBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class SearchFieldType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name', 'hidden')
->add('slug', 'hidden')
->add('value')
->add('choices')
->add('type', new SearchFieldTypeType())
->add('actionFilter')
->add('actionHighlight')
->add('actionShow')
;
}
/**
* #param OptionsResolverInterface $resolver
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\SearchField'
));
}
/**
* #return string
*/
public function getName()
{
return 'appbundle_searchfield';
}
}
SearchFieldTypeType:
<?php
namespace AppBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use AppBundle\Entity\SearchOperator;
class SearchFieldTypeType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$entity = $builder->getData();
$builder
->add('name', 'hidden')
->add('operators', 'entity', array('class' => 'AppBundle:SearchOperator',
'multiple' => false,
'expanded' => false))
;
}
/**
* #param OptionsResolverInterface $resolver
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\SearchFieldType'
));
}
/**
* #return string
*/
public function getName()
{
return 'appbundle_searchfieldtype';
}
}
The form renders properly, but when I submit and try to do $form->handleRequest($request) I get an exception:
Neither the property "operators" nor one of the methods "addOperator()"/"removeOperator()", "setOperators()", "operators()", "__set()" or "__call()" exist and have public access in class "AppBundle\Entity\SearchFieldType"
That's not true actually, as those methods exist and work correctly:
AppBundle\Entity\SearchFieldType:
<?php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use AppBundle\Entity\SearchOperator;
/**
* SearchField
*
* #ORM\Table()
* #ORM\Entity
*/
class SearchFieldType
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="name", type="string", length=100, nullable=true)
*/
private $name;
/**
* #ORM\ManyToMany(targetEntity="SearchOperator", cascade={"persist", "remove"})
* #ORM\JoinTable(
* joinColumns={#ORM\JoinColumn(name="type_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="operator_id", referencedColumnName="id")}
* )
**/
private $operators;
/**
* Constructor
*/
public function __construct()
{
$this->operators = new \Doctrine\Common\Collections\ArrayCollection();
}
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set name
*
* #param string $name
* #return SearchFieldType
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Get name
*
* #return string
*/
public function getName()
{
return $this->name;
}
/**
* Add operator
*
* #param SearchOperator $operator
*
* #return SearchFieldType
*/
public function addOperator(SearchOperator $operator)
{
$this->operators[] = $operator;
return $this;
}
/**
* Remove operator
*
* #param SearchOperator $operator
*/
public function removeOperator(SearchOperator $operator)
{
$this->operators->removeElement($operator);
}
/**
* Get operator
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getOperators()
{
return $this->operators;
}
public function __toString()
{
return $this->name;
}
}
Stack trace:
in vendor/symfony/symfony/src/Symfony/Component/PropertyAccess/PropertyAccessor.php at line 460 +
at PropertyAccessor ->writeProperty (object(SearchFieldType), 'operators', object(SearchOperator))
in vendor/symfony/symfony/src/Symfony/Component/PropertyAccess/PropertyAccessor.php at line 104 +
at PropertyAccessor ->setValue (object(SearchFieldType), object(PropertyPath), object(SearchOperator))
in vendor/symfony/symfony/src/Symfony/Component/Form/Extension/Core/DataMapper/PropertyPathMapper.php at line 93 +
at PropertyPathMapper ->mapFormsToData (object(RecursiveIteratorIterator), object(SearchFieldType))
in vendor/symfony/symfony/src/Symfony/Component/Form/Form.php at line 633 +
at Form ->submit (array('operators' => '156', 'name' => 'string'), true)
in vendor/symfony/symfony/src/Symfony/Component/Form/Form.php at line 577 +
at Form ->submit (array('type' => array('operators' => '156', 'name' => 'string'), 'value' => 'felipe', 'name' => 'Nombre', 'slug' => 'nombre'), true)
in vendor/symfony/symfony/src/Symfony/Component/Form/Form.php at line 577
EDIT:
Controller Code :
$searchFormEntity = new SearchForm();
$searchFormWithValues = $this->createForm(new SearchFormType(), $searchFormEntity, array(
'action' => $this->generateUrl('candidato'),
'method' => 'POST'
));
$searchFormWithValues->add('submit', 'submit', array('label' => 'Buscar'));
$searchFormWithValues->handleRequest($request);
Well you have a ManyToMany relation, so it would make sense to have the operators field be a collection. However you defined it as an entity, so now the form expects to have the setOperators and getOperators methods as entity implies a ManyToOne or OneToOne relationship.
I think you need to change the statement in the class SearchFieldTypeType where adding the operators attribute to be the same as what you did before for fields in SearchFormType if you want to keep the ManyToMany relationship.
I have 2 "simple" entities, and i want to do the classical form embedding
but i have this error : "Neither the property "itemcode" nor one of the methods "getItemcode()", "itemcode()", "isItemcode()", "hasItemcode()", "__get()" exist and have public access in class "NWA\ItemSelectorBundle\Entity\ItemSelector"."
I've seen many posts with this error, but none provided the solution
In the entities i have getItemCode() but why would it be public ?
What is wrong with my construction?
Thank you in advance
Here are my entities (parts relevant to the properties at fault)
class ItemSelector
{
/**
* #var Items[]
*
* #ORM\OneToMany(targetEntity="NWA\ItemSelectorBundle\Entity\Item", mappedBy="itemselector", cascade={"all"})
*/
protected $items;
/**
* Class constructor
*/
public function __construct()
{
$this->items = new ArrayCollection();
}
/**
* Add item
*
* #param \NWA\ItemSelectorBundle\Entity\Item $item
*
* #return ItemSelector
*/
public function addItem(\NWA\ItemSelectorBundle\Entity\Item $item)
{
$this->items[] = $item;
//$item->setItemselector($this);
return $this;
}
/**
* Remove item
*
* #param \NWA\ItemSelectorBundle\Entity\Item $item
*/
public function removeItem(\NWA\ItemSelectorBundle\Entity\Item $item)
{
$this->items->removeElement($item);
}
/**
* Get items
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getItems()
{
return $this->items;
}
}
and
class Item
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="itemcode", type="string", length=255)
*/
protected $itemcode;
/**
* #var ItemSelector
*
* #ORM\ManyToOne(targetEntity="NWA\ItemSelectorBundle\Entity\ItemSelector", inversedBy="items")
* #ORM\JoinColumn(name="itemselector_id", referencedColumnName="id")
*/
protected $itemselector;
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set itemcode
*
* #param string $itemcode
*
* #return Item
*/
public function setItemcode($itemcode)
{
$this->itemcode = $itemcode;
return $this;
}
/**
* Get itemcode
*
* #return string
*/
public function getItemcode()
{
return $this->itemcode;
}
/**
* Set itemselector
*
* #param \NWA\ItemSelectorBundle\Entity\ItemSelector $itemselector
*
* #return Item
*/
public function setItemselector(\NWA\ItemSelectorBundle\Entity\ItemSelector $itemselector = null)
{
$this->itemselector = $itemselector;
return $this;
}
/**
* Get itemselector
*
* #return \NWA\ItemSelectorBundle\Entity\ItemSelector
*/
public function getItemselector()
{
return $this->itemselector;
}
}
Then the Form constructors
class ItemSelectorType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add(
'itemcode', 'collection', array(
'type' => new ItemType(),
'prototype' => true,
'allow_add' => true,
'allow_delete' => true
)
);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'NWA\ItemSelectorBundle\Entity\ItemSelector',
'translation_domain' => 'resource'
));
}
/**
* #return string
*/
public function getName()
{
return 'nwa_itemselector';
}
}
and
class ItemType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add(
'itemcode', 'text', array(
'label' => 'Code'
)
);
;
}
/**
* #param OptionsResolverInterface $resolver
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'NWA\ItemSelectorBundle\Entity\Item'
));
}
/**
* #return string
*/
public function getName()
{
return 'nwa_itemselectorbundle_item';
}
}
And finally the call in the Controller
public function chooseAction(Request $request, ItemSelector $itemSelector)
{
$form = $this->get('form.factory')
->create(new ItemSelectorType(), $itemSelector);
$form->handleRequest($request);
if ($form->isValid()) {
}
return array(
'_resource' => $itemSelector,
'form' => $form->createView(),
);
}
Maybe you need to rename your field name itemcode to items in ItemSelectorType.
->add(
'items', 'collection', array(
'type' => new ItemType(),
'prototype' => true,
'allow_add' => true,
'allow_delete' => true
)
);
I am making an invoicing system with an embedded form. A form where you can add a date, choose a customer company from a dropdown list (-> customer is an entity) and add details to the invoice item (-> details is also an entity, with properties like price, amount,...) - with javascript. This works just fine, but at saving the form I get an error.
I have 3 entities: InvoicingCustomer, InvoiceItem, InvoiceItemDetail.
(Sorry; this is going to be a long post)
InvoicingCustomer.php (with properties like street, address,...) =
/**
* #ORM\Table(name="invoicing_customer")
* #ORM\Entity
*/
class InvoicingCustomer
{
/**
* #ORM\OneToMany(targetEntity="Invoicing\InvoicingBundle\Entity\InvoiceItem", mappedBy="customer")
*/
private $invoice;
public function __construct()
{ $this->invoice = new ArrayCollection();}
public function getInvoice()
{ return $this->invoice; }
public function getAllInvoices()
{
$invoices = $this->getInvoice()->toArray();
return $invoices;
}
/**
* #var integer
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
/**
* #var string
* #ORM\Column(name="company_name", type="string", length=50, nullable=false)
* #Assert\NotBlank()
*/
private $companyName;
//idem for next properties:
private $firstName;
private $lastName;
private $street;
private $number;
private $postalCode;
private $city;
}
And off course the getters and setters.
InvoiceItem.php =
/**
* #ORM\Table(name="invoicing_invoice_item")
* #ORM\Entity
*/
class InvoiceItem
{
/**
* #ORM\OneToMany(targetEntity="Invoicing\InvoicingBundle\Entity\InvoiceItemDetail", mappedBy="item_nr", cascade={"ALL"}, fetch="EAGER", orphanRemoval=true)
*/
private $item_detail;
public function __construct()
{ $this->item_detail = new ArrayCollection(); }
/**
* #return mixed
*/
public function getItemDetail()
{ return $this->item_detail; }
/**
* #param mixed $item_detail
*/
public function setItemDetail(Collection $item_detail)
{
foreach ($item_detail as $v)
{
if (is_null($v->getId()))
{
$v->getId($this);
}
}
$this->item_detail = $item_detail;
}
public function addDetail(InvoiceItemDetail $detail){
$detail->$this->setItemDetail($this);
$this->detail[] = $detail;
return $this;
}
public function removeDetail(InvoiceItemDetail $detail){
//
}
/**
* #var integer
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
/**
* #var \DateTime
* #ORM\Column(name="date", type="date", nullable=false)
*/
private $date;
/**
* #ORM\ManyToOne(targetEntity="Invoicing\CustomerBundle\Entity\InvoicingCustomer", inversedBy="invoice")
* #ORM\JoinColumn(onDelete="CASCADE", nullable=false)
* #Assert\Type(type="Invoicing\CustomerBundle\Entity\InvoicingCustomer")
* #Assert\Valid()
*
*/
private $customer;
// here also getters and setters
}
InvoiceItemDetail.php =
/**
* #ORM\Table(name="invoicing_invoice_itemdetail")
* #ORM\Entity
*/
class InvoiceItemDetail
{
/**
* #var integer
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
/**
* #ORM\Column(name="description", type="text", length=200, nullable=false)
*/
private $description;
/**
* #var string
* #ORM\Column(name="price", type="decimal", precision=10, scale=0, nullable=false)
*/
private $price;
/**
* #var integer
* #ORM\Column(name="amount", type="decimal", precision=10, scale=0, nullable=false)
*/
private $amount;
/**
* #ORM\ManyToOne(targetEntity="Invoicing\InvoicingBundle\Entity\InvoiceItem", inversedBy="item_detail" )
* #ORM\JoinColumn(onDelete="CASCADE", nullable=false, name="item_nr_id", referencedColumnName="id")
* #Assert\Type(type="Invoicing\InvoicingBundle\Entity\InvoiceItem")
* #Assert\Valid()
*/
private $item_nr;
// + getters and setters
}
Then, I got the types.
InvoiceItemType.php =
class InvoiceItemType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('date', 'date', array(
'format' => 'dd-MM-yyyy',
'empty_value' => array('year' => 'Year', 'month' => 'Month', 'day' => 'Day'),
'years' => range(date('Y') -1, date('Y')),
))
->add('customer', null, array(
'empty_value' => 'Choose a company',
'label' => 'Company',
'required' => true,
))
->add('item_detail', 'collection', array(
'type' => new InvoiceItemDetailType(),
'allow_add' => true,
'constraints' => new NotBlank(),
'by_reference' => false,
));
}
public function getName()
{ return 'invoiceitem'; }
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Invoicing\InvoicingBundle\Entity\InvoiceItem',
));
}
}
InvoicingCustomerType.php =
class InvoicingCustomerType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('companyName', 'text');
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Invoicing\CustomerBundle\Entity\InvoicingCustomer',
));
}
public function getName()
{ return 'customer'; }
}
InvoiceItemDetailType.php =
class InvoiceItemDetailType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('description', 'text')
->add('price', 'number', array(
'label' => 'Price - €',
))
->add('amount', 'number');
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Invoicing\InvoicingBundle\Entity\InvoiceItemDetail',
));
}
public function getName()
{ return 'detail'; }
}
In my controller I have this (InvoiceItemController.php):
/** InvoiceItem controller */
class InvoiceItemController extends Controller
{
/**
* Creates a new invoiceitem entity.
*/
public function createAction(Request $request)
{
$entity = new InvoiceItem();
$form = $this->createCreateForm($entity);
$form->handleRequest($request);
if ($form->isValid()) {
$em = $this->getDoctrine()->getManager();
// hack to work around handleRequest not using class methods to populate data
foreach($entity->getItemDetail() as $detail){
foreach($detail as $i){
// if i didn't made a second loop, I get an error: "object could not be converted to string..."
$i->this->setItemNr($entity);
$em->persist($i);
}
}
$em->persist($entity);
$em->flush();
return $this->redirect($this->generateUrl('invoiceitem_show', array('id' => $entity->getId())));
}
return $this->render('InvoicingBundle:InvoiceItem:new.html.twig', array(
'entity' => $entity,
'form' => $form->createView(),
));
}
}
In my twig it's just like:
{% block body -%}
<h1>Invoice item creation</h1>
{{ form(form) }}
{% endblock %}
Everything in the form is displayed good (and with javascript I can add several details to one invoice item). But when I submit the form, symfony throws an error:
An exception occurred while executing 'INSERT INTO invoicing_invoice_itemdetail (description, price, amount, item_nr_id) VALUES (?, ?, ?, ?)' with params ["test", 300, 1, null]:
SQLSTATE[23000]: Integrity constraint violation: 1048 Column 'item_nr_id' cannot be null
I searched around on the docs of symfony (http://symfony.com/doc/current/cookbook/form/form_collections.html ) and on stackoverflow (for example: Saving embedded collections ), but none of these give me the right solution.
I know this is a long post: I am sorry. But I don't know how to sort this problem out (+ I am new in learning symfony2 & new in asking questions here).
I believe your problem is in InvoiceItem entity. Try to create method addItemDetail (or maybe addInvoiceItemDetail) instead addDetail. You can also delete method setItemDetail and maybe you will see good explanation what method is Symfony looking for.
public function addItemDetail(InvoiceItemDetail $detail){
$detail->setItemNr($this);
$this->item_detail[] = $detail;
return $this;
}
And delete the hack from controller.
// hack to work around handleRequest not using class methods to populate data
foreach($entity->getItemDetail() as $detail){
foreach($detail as $i){
// if i didn't made a second loop, I get an error: "object could not be converted to string..."
$i->this->setItemNr($entity);
$em->persist($i);
}
}
I hope it helps but it is a little hard to answer this question without live code.
The concept of Symfony forms is that for relations you can also specify the related entity in the form type.
In your case, you didn't add InvoiceItemType to the Type ItemInvoiceDetail:
I would expect the following code in your InvoiceDetailType:
class InvoiceItemDetailType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('description', 'text')
->add('invoice_item', 'invoice_item', array('error_bubbling' => true, 'mapped' => true))
->add('price', 'number', array(
'label' => 'Price - €',
))
->add('amount', 'number');
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Invoicing\InvoicingBundle\Entity\InvoiceItemDetail',
));
}
public function getName()
{ return 'detail'; }
Notice i set the type of the form element as invoice_item.
you can achieve that by defining it as a service:
service_name:
class: invoiceitemfullclasspath
arguments: []
tags:
- { name: form.type, alias: invoice_item }