When symfony collection fields are dynamicly added/removed in DOM with javascript, on save of this collection either validation or persisted collection elem ids get messed up. For example if I have Entity\User.php releation like:
/**
* #var \TestBundle\Entity\UserFollowers
*
* #ORM\OneToMany(targetEntity="TestBundle\Entity\UserFollowers", mappedBy="user", cascade={"persist"}, orphanRemoval=true)
* #Assert\Valid()
*/
protected $user_followers;
and backward Entity\UserFollower.php relation like:
/**
* #ORM\ManyToOne(targetEntity="TestBundle\Entity\User", inversedBy="user_followers")
* #ORM\JoinColumn(name="user_id", referencedColumnName="id")
*/
protected $user;
/**
* #var string
* #ORM\Column(name="name", type="string")
* #Assert\NotBlank()
*/
private $name;
And UserFormType like:
$builder->add('user_followers', CollectionType::class, array(
'entry_type' => UserFollowersType::class,
'allow_add' => true,
'allow_delete' => true,
'prototype' => true,
'prototype_name' => '__name__',
'by_reference' => false,
'label' => 'User followers'
))
and UserFollowersFormType like:
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder
->add('name', TextType::class, array(
'attr'=> [
'class' =>'input input-group-field'
],
'label' => false
));
}
when form is handled and user persisted to db either form validation attaches to wrong collection elem or persisted id to db gets wrong, different DOM manipulaiton gets different error, controller is like:
$form->handleRequest($request);
if ($form->isValid()) {
$em->persist($user);
$em->flush();
}
Why is that happening? its like form collection indexes gets messed up....
When you dynamicly add new UserFollowersType form you also have to replace prototype_name __name__ with length of the collection.
On collection init:
var $collection = $(your collection container of forms );
$collection.data('index', $collection.children().length);
var index = $collection.data('index');
//-------on add new form
// Replace '__name__' in the prototype's HTML to
// instead be a number based on how many items we have
var newForm = prototype.replace(prototypeName, index);
// increase the index with one for the next item
$collection.data('index', index + 1);
Related
I have 2 entities: Accounts and Patients:
class Patients
{
//..
/**
* #var Accounts
*
* #ORM\OneToOne(targetEntity="Accounts", mappedBy="patients")
* #Assert\Valid()
*/
private $accounts;
// ..
}
class Accounts {
// ...
/**
* #var Patients
*
* #ORM\OneToOne(targetEntity="Patients", inversedBy="accounts")
* #ORM\JoinColumn(name="patients_id", referencedColumnName="id")
*
*/
private $patients;
/**
* #var string
*
* #ORM\Column(name="email", type="string", length=100, nullable=true, unique=true)
* #Assert\NotBlank(groups={"emailValidation"})
* #Assert\Email(groups={"emailValidation"})
*
*/
private $email;
/// ...
}
I need to build a form with patients info (firstname, lastname, email) and validate it.
I did it like this:
class PatientsType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
// .....
->add('email', EmailType::class, array(
'mapped' => $options['formState'] == 'display' ? false : true,
'property_path' => 'accounts.email',
'data' => $options['email']
))
}
//....
$resolver->setDefaults(
array(
'formState' => 'display',
//...
}
The
'mapped' => $options['formState'] == 'display' ? false : true,
Is an ugly workaround that I had to make because, without that I had two situations:
1.
->add('email', EmailType::class, array(
'property_path' => 'accounts.email',
'data' => $options['email']
))
Will give the following error:
PropertyAccessor requires a graph of objects or arrays to operate on,
but it found type "NULL" while trying to traverse path
"accounts.email" at property "email".
->add('email', EmailType::class, array(
'mapped' => false,
'property_path' => 'accounts.email',
'data' => $options['email']
))
Is not taking into account the validation groups inside entities..
Is there an elegant way to validate the email inside the accounts entity?
My Symfony version is 2.7.3. I have Vehicle entity, which has WorldCountry field (unidirectional ManyToMany):
/**
* Vehicle
* #ORM\Table(name="vehicle")
*/
class Vehicle
{
// ...
/**
* #ORM\ManyToMany(targetEntity="AppBundle\Entity\WorldCountry", fetch="EAGER")
* #ORM\JoinTable(name="vehicle_country_join",
* joinColumns={#ORM\JoinColumn(name="vehicle_id", referencedColumnName="vehicle_id", onDelete="CASCADE")},
* inverseJoinColumns={#ORM\JoinColumn(name="country_id", referencedColumnName="country_id")}
* )
*/
protected $countries;
// ...
public function __construct()
{
// ...
$this->countries = new ArrayCollection();
}
}
I have form for vehicle creation/editing:
/**
* Class VehicleType
* #package AppBundle\Form\Vehicle
*/
class VehicleType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
// ...
->add('countries', 'entity', [
'class' => 'AppBundle:WorldCountry',
'choices' => $this->worldManager->getCountriesByRegionIds([1]),
'choice_label' => 'name',
'expanded' => false,
'multiple' => true,
'label' => false
])
->add('submit', 'submit');
// ...
}
}
Vehicle creation works fine, for example as result I have:
The problem is that on vehicle editing page countries are not preselected (“Kyrgyzstan” in that example), when “choices” option is used:
But without it countries are preselected:
$this->worldManager->getCountriesByRegionIds([1]) - returns array of WorldCountry from repository.
UPDATE
Accidentally I have updated Symfony up to v2.8.7 - now it works as expected :)
P.S. Please let me know if more information is required.
Hello i have a problem with my multiple embedded form (one to many).
One game have many prizes and one prize have many options. When i attemp to save this form get an error message
SQLSTATE[23000]: Integrity constraint violation: 1048 Column 'fk_prize' cannot be null
I have already set in my entity on cascade and in my form type set by_refference false but didnt work. All the other assigned foreign keys works perfectly.
UPDATED :
In controller when i do this form saved successfull. But i want to do this with doctrine. Is it bug in doctrine or something wrong in my code ?
Thanks for your time!
//Hacked code in controller to save the form
$prizes = $data->getPrizes();
foreach ($prizes as $prize) {
$prizeOptions = $prize->getPrizesOptions();
foreach ($prizeOptions as $prizeOption) {
$prizeOption->setPrize($prize);
}
}
$em->persist($data);
$em->flush();
<?php
class Game
{
/**
* #ORM\OneToMany(targetEntity="Prize", mappedBy="game", cascade={"persist"})
*/
protected $prizes;
public function __construct()
{
$this->gameUsers = new ArrayCollection();
$this->prizes = new ArrayCollection();
}
}
?>
<?php
class GameType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('alias', 'text' , [
'label'=>'Name'
])
->add('prizes' ,'collection', array(
'type' => new PrizeType($this->intention),
'allow_add' => true,
'allow_delete' => false,
'prototype' => true,
'by_reference' => false,
'label' => false,
))
->add('save', 'submit', [
'attr' => [
'class' => 'btn btn-primary'
]
]);
}
}
<?php
class Prize
{
/**
* The Game
* #ORM\ManyToOne(targetEntity="Game")
* #ORM\JoinColumn(name="game_id", referencedColumnName="id")
*/
protected $game;
/**
* #ORM\OneToMany(targetEntity="PrizeOptions", mappedBy="prize", cascade={"persist"})
*/
protected $prizes_options;
/**
* Constructor
*/
public function __construct()
{
$this->prizes_options = new \Doctrine\Common\Collections\ArrayCollection();
}
}
class PrizeType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('prizes_options' ,'collection', array(
'type' => new PrizeOptionsType(),
'allow_add' => true,
'allow_delete' => true,
'by_reference' => false,
'label' => false,
))
;
}
}
<?php
class PrizeOptionsType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name', 'text' , [
'label'=>'Value'
])
;
}
}
Doctrine can handle changes in entities only on "owning" side. That means, you can modify relations only in entity where is defined join column/join table.
http://doctrine-orm.readthedocs.org/en/latest/reference/unitofwork-associations.html
Haven't found a solution to this problem through research yet but I am trying to save into the database with two forms in one (embedded/collection). I have entities that are related to each other and I want the form to submit and persist both entities into the database.
Main entity:
/**
* #var integer
* #ORM\OneToMany(targetEntity="Sub", mappedBy="mainId", cascade={"persist"})
*/
protected $sub;
public function __construct() {
$this->sub = new ArrayCollection();
}
Sub entity:
/**
* #var integer
*
* #ORM\Column(name="main_id", type="integer")
*/
protected $mainId;
.......
/**
* #ORM\ManyToOne(targetEntity="Main", inversedBy="sub")
* #ORM\JoinColumn(name="main_id", referencedColumnName="id")
*/
protected $main;
Here's my MainType form:
class MainType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('dano', 'text', array(
'label' => 'DA: ',
'disabled' => true
))
->add('partno','text', array(
'label' => 'Part: ',
'disabled' => true
))
->add('batchno', 'text', array(
'label' => 'Batch: ',
'disabled' => true
))
->add('sub', 'collection', array('type' => new SubType()))
->add('submit', 'submit');
}......
And my SubType form:
class SubType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('main_id','hidden')
->add('rackno','text', array(
'label' => 'Rack No(s) '
))
->add('diecode','text', array(
'label' => 'Die Code '
))
->add('heatcode','text', array(
'label' => 'Heat Code '
))
->add('inqty','integer', array(
'label' => 'Qty In '
))
->add('onhold','choice', array(
'label' => 'Hold',
'choices' => array(
'1' => 'On Hold',
'0' => 'Released'
),
'multiple' => false,
'expanded' => true
));
And my controller:
/**
* #param Request $request
* #Route("/{dano}", name="subpart_part")
*/
public function submitPartByDAAction(Request $request, $dano) {
$em = $this->getDoctrine()->getManager();
$entity = $em->getRepository('Bundle:Main')
->findOneByDano($dano);
$partno = $entity->getPartno();
$batchno = $entity->getBatchno();
$mainid = $entity->getId();
$main1 = new Main();
$main1->setDano($dano);
$main1->setPartno($partno);
$main1->setBatchno($batchno);
$sub1 = new Sub();
$sub1->setMainId($mainid);
$main1->getSub()->add($sub1);
$form = $this->createForm(new MainType(), $main1, array(
'method' => 'POST'
));
$form->handleRequest($request);
if($form->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($main1);
$em->flush();
return $this->redirect($this->generateUrl('subpart_home'));
}
return $this->render('Bundle:Parts:addparts.html.twig', array(
'form' => $form->createView()
));
}
Let me explain what I did here, at first I did not have the Sub's "main_id" field (which is related to Main's id) on but when I tried persisting the data it gave me the error:
An exception occurred while executing 'INSERT INTO sub
(main_id, rackno, heatcode, diecode, inqty, onhold) VALUES
(?, ?, ?, ?, ?, ?)' with params [null, "46", "eterte", "seteter", 3, 0]:
SQLSTATE[23000]: Integrity constraint violation: 1048 Column
'main_id' cannot be null
So then I made a field "main_id" with it being hidden, grabbed the id from Main by getId(); and passed it into the Sub's form's setMainId(); to persist and it still gives me the same error that "main_id" cannot be null.
What am I missing? Thanks!
You are defining your entities wrong. First understand the concept of ORM and relations. Your Sub entity does not need to have the integer main_id. Simply map it to Main entity. Your Main entity should look like
/**
* #var Sub
* this value is just integer in database, but doc should point it to Sub
* #ORM\OneToMany(targetEntity="Sub", mappedBy="mainId", cascade={"persist"})
*/
protected $sub;
public function __construct() {
$this->sub = new ArrayCollection();
}
And your Sub entity
/**
* #ORM\ManyToOne(targetEntity="Main", inversedBy="sub")
* #ORM\JoinColumn(name="main_id", referencedColumnName="id")
*/
protected $main;
You dont need main_id. The ORM will handle that for you. The MainType form is good. Just get rid of the main_id in SubType form.
You should reference to entities by object rather than their IDs. In your controller also rather than using
$sub1->setMainId($mainid);
You should set the object.
$sub1->setMain($main1);
Your main form is also a little weird. I do not say it is not valid, but you should consider replacing this line:
->add('sub', 'collection', array('type' => new SubType()))
With something like this:
->add('sub', new SubType(), array())
I think it is way more appropriate if you have only "ONE" item. You use collection when you want many items.
I would suggest you look into the form component... how the form is represented as a tree...
Also never make fields like "main_id", unless it is necessary. Try not to work id's and work with associations.
I'm getting the following exception when trying to update an entity:
The form's view data is expected to be of type scalar, array or an instance of \ArrayAccess, but is an instance of class Proxies__CG__\Acme\DemoBundle\Entity\TicketCategory. You can avoid this error by setting the "data_class" option to "Proxies__CG__\Acme\DemoBundle\Entity\TicketCategory" or by adding a view transformer that transforms an instance of class Proxies__CG__\Acme\DemoBundle\Entity\TicketCategory to scalar, array or an instance of \ArrayAccess.
When creating, no problem occurs and the relationship is OK. However, when updating, this strange exception comes up. My entities are setup like this:
class Ticket
{
// ...
/**
* #var TicketCategory
*
* #ORM\ManyToOne(targetEntity="TicketCategory")
* #ORM\JoinColumn(name="category_id", referencedColumnName="id")
*/
protected $category;
// ...
}
class TicketCategory
{
/**
* #var integer $id
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string $title
*
* #ORM\Column(name="title", type="string", length=255)
* #Assert\NotBlank()
*/
private $title;
// ...
}
Form
class TicketType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('title', 'text', array(
'error_bubbling' => true,
)
)
->add('category', 'text', array(
'error_bubbling' => true,
)
)
;
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Acme\DemoBundle\Entity\Ticket',
'csrf_protection' => false,
));
}
public function getName()
{
return '';
}
}
Any ideas, guys?
Problem is:
$builder
->add('category', 'text', array(
'error_bubbling' => true,
)
)
;
Field category is declared of type "text", thus you can pass only scalars (string, bool, etc) to it. That is you can only specify properties (of Ticket class) that are scalars.
In Ticket class category it's an entity, so the error occurs.
Without knowing what you want to accomplish, i guess you want to make the user choose a category for the ticket, so i'll do:
$builder
->add('category', 'entity', array(
'label' => 'Assign a category',
'class' => 'Acme\HelloBundle\Entity\TicketCategory',
'property' => 'title',
'multiple' => false
)
)
;
More on entity field type.
EDIT: don't know if you omitted it, but Ticket has no property named "title".