Handling dd/mm/yyyy format in symfon2/Doctrine - symfony

What should happen here is this:
Only dd/mm/yyyy format will be accepted for DOB.
If different format given then "The DoB field must have a valid format." message should read on the screen BUT this message should be coming from the ENTITY, not form TYPE set with 'invalid_message' attribute.
JFYI: I can define $dob as 'string' in the entity and as 'text' in the form type to make whole process work but it is not good practise. The reason is I don't want varchar field for $dob in database, I want date field.
PERSON ENTITY (Note: This is the only place I want form validation takes place):
namespace Se\HirBundle\Entity;
use Symfony\Component\Validator\Constraints as Assert;
use Doctrine\ORM\Mapping as ORM;
/**
* Person
*
* #ORM\Entity
* #ORM\Table(name="person")
* #ORM\HasLifecycleCallbacks
*/
class Person
{
/**
* #var integer $id
*
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #var string $firstname
*
* #Assert\NotBlank(message = "The Firstname field should not be blank.")
* #Assert\Length(max = "100", maxMessage = "The Firstname field cannot be longer than {{ limit }} characters length.")
*
* #ORM\Column(type = "string", length = 100)
*/
protected $firstname;
/**
* #var date $dob
*
* #Assert\NotBlank(message = "The DoB field should not be blank.")
* #Assert\Regex(pattern = "/^(0[1-9]|[12][0-9]|3[01])[\/\-](0[1-9]|1[012])[\/\-]\d{4}$/", message = "The DoB field must have a valid format.")
*
* #ORM\Column(type = "date", length = 10)
*/
protected $dob;
/**
* #var datetime $created
*
* #ORM\Column(type="datetime")
*/
protected $created;
/**
* #var datetime $updated
*
* #ORM\Column(type="datetime", nullable = true)
*/
protected $updated;
/**
* Gets triggered only on insert
*
* #ORM\PrePersist
*/
public function onPrePersist()
{
$this->created = new \DateTime("now");
}
/**
* Gets triggered every time on update
*
* #ORM\PreUpdate
*/
public function onPreUpdate()
{
$this->updated = new \DateTime("now");
}
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set firstname
*
* #param string $firstname
* #return Person
*/
public function setFirstname($firstname)
{
$this->firstname = $firstname;
return $this;
}
/**
* Get firstname
*
* #return string
*/
public function getFirstname()
{
return $this->firstname;
}
/**
* Set dob
*
* #param string $dob
* #return Person
*/
public function setDob($dob)
{
$this->dob = $dob;
return $this;
}
/**
* Get dob
*
* #return string
*/
public function getDob()
{
return $this->dob;
}
}
Form TYPE file:
namespace Se\HirBundle\Form\Type\Person;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class CreateType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->setAction($options['action'])
->setMethod('POST')
->add('firstname', 'text',
array('label' => 'Firstname', 'error_bubbling' => true))
->add('dob', 'date',
array('label' => 'DoB', 'widget' => 'single_text',
'format' => 'dd/MM/yyyy', 'input' => 'datetime', 'error_bubbling' => true))
->add('create', 'submit',
array('label' => 'Create Person'));
}
public function getName()
{
return 'personcreate';
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array('data_class' => 'Se\HirBundle\Entity\Person'));
}
}
CONTROLLER:
namespace Se\HirBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Se\HirBundle\Entity\Person;
use Se\HirBundle\Form\Type\Person\CreateType;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
class CrudController extends Controller
{
public function createAction()
{
$person = new Person();
$form = $this->createForm(new CreateType(), $person,
array('action' => $this->generateUrl('create_submit')));
return $this->render('SeHirBundle:Default:create.html.twig',
array('page' => 'Create', 'form' => $form->createView()));
}
public function createPersonAction(Request $request)
{
if ($request->getMethod() != 'POST')
{
return new Response('Only POST method is accepted');
}
$person = new Person();
$form = $this->createForm(new CreateType(), $person,
array('action' => $this->generateUrl('create_submit')));
$form->handleRequest($request);
if ($form->isValid())
{
$submission = $form->getData();
$em = $this->getDoctrine()->getManager();
$person = new Person();
$person->setFirstname($submission->getFirstname());
$person->setDob($submission->getDob());
$em->persist($person);
$em->flush();
$this->get('session')->getFlashBag()->add('message', 'Person successfully created!');
return $this->redirect($this->generateUrl('message'));
}
return $this->render('SeHirBundle:Default:create.html.twig',
array('page' => 'Create', 'form' => $form->createView()));
}
}
TWIG:
{% extends '::base.html.twig' %}
{% block title %}{{ page }}{% endblock %}
{% block body %}
<b>{{ page|upper }}</b>
<hr />
{{ form_start(form, {attr: {novalidate:'novalidate'}}) }}
{% if form_errors(form) != '' %}
<div>{{ form_errors(form) }}</div>
{% endif %}
<div>
{{ form_label(form.firstname) }}
{{ form_widget(form.firstname) }}
</div>
<div>
{{ form_label(form.dob) }}
{{ form_widget(form.dob, {'type':'text'}) }}
</div>
<br />
<div>
{{ form_widget(form.create) }}
</div>
{{ form_end(form)}}
{% endblock %}

Here is a solution based on the Symfony2 documentation:
Controller
Start of the file
use Symfony\Component\HttpFoundation\Response;
Function in the controller
This controller will display the dob field, since this is a Datetime object it requires to use format() in order to display it. This is just an example to show that Symfony2 recognizes the date and transform it internally.
Uncommenting the lines starting with // would be sufficient to persist the Person entity with the dob.
public function testAction(Request $request)
{
$person = new Person();
$form = $this->createFormBuilder($person)
->add('dob', 'date',
array(
'label' => 'DoB',
'widget' => 'single_text',
'format' => 'dd/MM/yyyy',
'invalid_message' => 'Validation error goes here',
'error_bubbling' => true,
'input' => 'datetime' # return a Datetime object (*)
)
)
->getForm();
$form->handleRequest($request);
if ($form->isValid()) {
# perform some action, such as saving the task to the database
//$em = $this->getDoctrine()->getManager();
//$em->persist($person);
//$em->flush();
return new Response($person->getDob()->format('d-m-Y'));
}
return $this->render(
'YourBundle:Default:test.html.twig',
array(
'form' => $form->createView()
)
);
}
(*): http://symfony.com/fr/doc/master/reference/forms/types/date.html#input
Twig file
{{ form(form) }}
routing.yml
test:
pattern: /test/
defaults: { _controller: YourBundle:Default:test }

SOLUTION in Controller:
$dob = date('Y-m-d', strtotime(str_replace('/', '-', $submission->getDob())));
$person->setDob($dob);

Related

Symfony2 Doctrine ORM: id field remains NULL

I am using CollectionType Field in my Symfony project, to be able to generate as many similar form items as user needs. The problem I have appears while form is being submitted - it passes all the values directly to db, but the foreign key id (household_app_id) remains null, even though relations seem to be correct.
What can I do to prevent this bug? And why is this happening?
My project code is below:
Both Entities:
<?php //HouseholdApp.php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\HttpFoundation\File\File;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Vich\UploaderBundle\Mapping\Annotation as Vich;
/**
* HouseholdApp
*
* #ORM\Table(name="household_application")
* #ORM\Entity(repositoryClass="AppBundle\Repository\HouseholdAppRepository")
* #Vich\Uploadable
*
*/
class HouseholdApp
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* One Application has One Household.
* #ORM\OneToOne(targetEntity="AppBundle\Entity\Household", inversedBy="householdApp")
* #ORM\JoinColumn(name="household_id", referencedColumnName="id")
*/
private $household;
/**
* #var File
*
* #Vich\UploadableField(mapping="union_register", fileNameProperty="unionRegisterName")
*/
private $unionRegister;
/**
* #var File
*
* #Vich\UploadableField(mapping="apt_owners_decision", fileNameProperty="aptOwnersDecisionName")
*/
private $aptOwnersDecision;
/**
* #var File
*
* #Vich\UploadableField(mapping="mulaptinventory", fileNameProperty="multiAptInventoryName")
*/
private $multiAptInventory;
/**
* #var File
*
* #Vich\UploadableField(mapping="buildtechsurvey", fileNameProperty="buildingTechnicalSurveyName")
*/
private $buildingTechnicalSurvey;
/**
* #var File
*
* #Vich\UploadableField(mapping="defectact", fileNameProperty="defectActName")
*/
private $defectAct;
/**
* #var File
*
* #Vich\UploadableField(mapping="activitycost", fileNameProperty="activityCostName")
*/
private $activityCost;
/**
* #ORM\OneToMany(targetEntity="AppBundle\Entity\DefectAct", mappedBy="householdApp", cascade={"persist"}, orphanRemoval=true)
*/
protected $actOfDefects;
/**
* Set unionRegister
*
* #param File|UploadedFile $unionRegister
*
* #return HouseholdApp
*/
public function setUnionRegister(File $unionRegister = null)
{
$this->unionRegister = $unionRegister;
if ($unionRegister) {
// It is required that at least one field changes if you are using doctrine
// otherwise the event listeners won't be called and the file is lost
$this->uUpdatedAt = new \DateTime('now');
}
return $this;
}
/**
* Get unionRegister
*
* #return File|UploadedFile
*/
public function getUnionRegister()
{
return $this->unionRegister;
}
/**
* Set aptOwnersDecision
*
* #param File|UploadedFile $aptOwnersDecision
*
* #return HouseholdApp
*/
public function setAptOwnersDecision(File $aptOwnersDecision = null)
{
$this->aptOwnersDecision = $aptOwnersDecision;
if ($aptOwnersDecision) {
// It is required that at least one field changes if you are using doctrine
// otherwise the event listeners won't be called and the file is lost
$this->aUpdatedAt = new \DateTime('now');
}
return $this;
}
/**
* Get aptOwnersDecision
*
* #return File|UploadedFile
*/
public function getAptOwnersDecision()
{
return $this->aptOwnersDecision;
}
/**
* Set multiAptInventory
*
* #param File|UploadedFile $multiAptInventory
*
* #return HouseholdApp
*/
public function setMultiAptInventory(File $multiAptInventory = null)
{
$this->multiAptInventory = $multiAptInventory;
if ($multiAptInventory) {
// It is required that at least one field changes if you are using doctrine
// otherwise the event listeners won't be called and the file is lost
$this->mUpdatedAt = new \DateTime('now');
}
return $this;
}
/**
* Get multiAptInventory
*
* #return File|UploadedFile
*/
public function getMultiAptInventory()
{
return $this->multiAptInventory;
}
/**
* Set buildingTechnicalSurvey
*
* #param File|UploadedFile $buildingTechnicalSurvey
*
* #return HouseholdApp
*/
public function setBuildingTechnicalSurvey(File $buildingTechnicalSurvey = null)
{
$this->buildingTechnicalSurvey = $buildingTechnicalSurvey;
if ($buildingTechnicalSurvey) {
// It is required that at least one field changes if you are using doctrine
// otherwise the event listeners won't be called and the file is lost
$this->bUpdatedAt = new \DateTime('now');
}
return $this;
}
/**
* Get buildingTechnicalSurvey
*
* #return File|UploadedFile
*/
public function getBuildingTechnicalSurvey()
{
return $this->buildingTechnicalSurvey;
}
/**
* Set defectAct
*
* #param File|UploadedFile $defectAct
*
* #return HouseholdApp
*/
public function setDefectAct(File $defectAct = null)
{
$this->defectAct = $defectAct;
if ($defectAct) {
// It is required that at least one field changes if you are using doctrine
// otherwise the event listeners won't be called and the file is lost
$this->dUpdatedAt = new \DateTime('now');
}
return $this;
}
/**
* Get defectAct
*
* #return File|UploadedFile
*/
public function getDefectAct()
{
return $this->defectAct;
}
/**
* Set activityCost
*
* #param File|UploadedFile $activityCost
*
* #return HouseholdApp
*/
public function setActivityCost(File $activityCost = null)
{
$this->activityCost = $activityCost;
if ($activityCost) {
// It is required that at least one field changes if you are using doctrine
// otherwise the event listeners won't be called and the file is lost
$this->acUpdatedAt = new \DateTime('now');
}
return $this;
}
/**
* Get activityCost
*
* #return File|UploadedFile
*/
public function getActivityCost()
{
return $this->activityCost;
}
}
<?php //DefectAct.php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* DefectAct
*
* #ORM\Table(name="defect_act")
* #ORM\Entity(repositoryClass="AppBundle\Repository\DefectActRepository")
*
*/
class DefectAct
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(type="string", length=255, nullable=true)
*/
private $defectWork;
/**
* #var string
*
* #ORM\Column(type="string", length=255, nullable=true)
*/
private $defectDescription;
/**
* #var string
*
* #ORM\Column(type="string", length=255, nullable=true)
*/
private $preventionDeadline;
/**
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\HouseholdApp", inversedBy="actOfDefects")
*/
private $householdApp;
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set defectWork
*
* #param string $defectWork
*
* #return DefectAct
*/
public function setDefectWork($defectWork)
{
$this->defectWork = $defectWork;
return $this;
}
/**
* Get defectWork
*
* #return string
*/
public function getDefectWork()
{
return $this->defectWork;
}
/**
* Set defectDescription
*
* #param string $defectDescription
*
* #return DefectAct
*/
public function setDefectDescription($defectDescription)
{
$this->defectDescription = $defectDescription;
return $this;
}
/**
* Get defectDescription
*
* #return string
*/
public function getDefectDescription()
{
return $this->defectDescription;
}
/**
* Set preventionDeadline
*
* #param string $preventionDeadline
*
* #return DefectAct
*/
public function setPreventionDeadline($preventionDeadline)
{
$this->preventionDeadline = $preventionDeadline;
return $this;
}
/**
* Get preventionDeadline
*
* #return string
*/
public function getPreventionDeadline()
{
return $this->preventionDeadline;
}
/**
* Set householdApp
*
* #param \AppBundle\Entity\HouseholdApp $householdApp
*
* #return DefectAct
*/
public function setHouseholdApp(\AppBundle\Entity\HouseholdApp $householdApp = null)
{
$this->householdApp = $householdApp;
return $this;
}
/**
* Get householdApp
*
* #return \AppBundle\Entity\HouseholdApp
*/
public function getHouseholdApp()
{
return $this->householdApp;
}
}
Controller:
/**
* #Security("has_role('ROLE_HOUSEHOLD')")
* #param \Symfony\Component\HttpFoundation\Request $request
* #return \Symfony\Component\HttpFoundation\RedirectResponse|\Symfony\Component\HttpFoundation\Response
*/
public function householdApplicationAction(Request $request) {
$application = $this->getUser()->getRhousehold()->getHouseholdApp();
$flag = true;
if(is_null($application)) {
$flag = false;
}
$form = $this->createForm(HouseholdAppType::class, $application, ['disabled' => false]);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$appData = $form->getData();
$em = $this->get("doctrine.orm.default_entity_manager");
$household = $this->getUser()->getRhousehold();
$appData->setHousehold($household);
$em->persist($appData);
$em->flush();
return $this->redirectToRoute("householder_app");
}
return $this->render("#App/household_application.html.twig",
['form' => $form->createView(), 'householdapp' => $application]);
}
Forms:
<?php //HouseholdAppType
namespace AppBundle\Form;
use AppBundle\Entity\HouseholdApp;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\Extension\Core\Type\IntegerType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Vich\UploaderBundle\Form\Type\VichFileType;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
class HouseholdAppType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('multiapthouseaddress', TextType::class, ['label' => 'form.multiapthouseaddress'])
->add('cadastralnumberofbuilding', TextType::class, ['label' => 'form.cadastralnumberofbuilding'])
->add('totalbuildingarea', TextType::class, ['label' => 'form.totalbuildingarea'])
->add('house_constructive_solution', TextType::class, ['label' => 'form.house_constructive_solution'])
->add('exploitation_start_year', IntegerType::class, ['label' => 'form.exploitation_start_year'])
->add('non_residential_area', TextType::class, ['label' => 'form.non_residential_area'])
->add('has_heat_supply_system', CheckboxType::class, ['label' => 'form.has_heat_supply_system'])
->add('apts_with_individual_heating', TextType::class, ['label' => 'form.apts_with_individual_heating'])
->add('authorized_person_data', TextType::class, ['label' => 'form.authorized_person_data'])
->add('is_vatpayer', CheckboxType::class, ['label' => 'form.is_vatpayer'])
->add('personal_code', TextType::class, ['label' => 'form.personal_code'])
->add('declared_address', TextType::class, ['label' => 'form.declared_address'])
->add('contact_person_data', TextType::class, ['label' => 'form.contact_person_data'])
->add('unionRegister', VichFileType::class, ['required' => false, 'label' => 'form.unionRegister'])
->add('aptOwnersDecision', VichFileType::class, ['required' => false, 'label' => 'form.aptOwnersDecision'])
->add('multiAptInventory', VichFileType::class, ['required' => false, 'label' => 'form.multiAptInventory'])
->add('buildingTechnicalSurvey', VichFileType::class, ['required' => false, 'label' => 'form.buildingTechnicalSurvey'])
->add('defectAct', VichFileType::class, ['required' => false, 'label' => 'form.defectAct'])
->add('activityCost', VichFileType::class, ['required' => false, 'label' => 'form.activityCost'])
->add('actOfDefects', CollectionType::class, [
'label' => true,
'entry_type' => DefectsActCollectionType::class,
'allow_add' => true,
'prototype' => true,
'allow_delete' => true,
'by_reference' => false,
'delete_empty' => true,
'required' => false
])
->add('save', SubmitType::class, [
'label' => 'form.save',
'attr' => [
'class' => 'btn2'
]]);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => HouseholdApp::class,
'translation_domain' => 'main',
));
}
}
<?php //DefectActCollectionType
namespace AppBundle\Form;
use AppBundle\Entity\DefectAct;
use AppBundle\Entity\HouseholdApp;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class DefectsActCollectionType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('defectWork', TextType::class)
->add('defectDescription', TextType::class)
->add('preventionDeadline', TextType::class)
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver
->setDefaults([
'translation_domain' => 'main',
'data_class' => DefectAct::class
]);
}
public function getBlockPrefix()
{
return 'app_bundle_defects_act_collection_type';
}
}
Template:
{% extends '#App/templates/base.html.twig' %}
{% trans_default_domain "main" %}
{% block main %}
{% include '#App/templates/progressbar.html.twig' %}
{% include '#App/templates/tabs.html.twig' %}
{% include '#App/h_apply_menu.html.twig' %}
<section id='cabinet_household_app'>
<div class='container'>
<div class="tab-content">
<div id="h-apply" class="tab-pane fade in active form_page">
{{ form_start(form)}}
{{ form_row(form._token) }}
<h3>{{ 'form.house_data'|trans }}</h3>
<div class='field'><div class="form-group"> {{ form_row(form.multiapthouseaddress)}}</div></div>
<div class='field'><div class="form-group"> {{ form_row(form.cadastralnumberofbuilding)}}</div></div>
<div class='field'><div class="form-group"> {{ form_row(form.totalbuildingarea)}}</div></div>
<div class='field'><div class="form-group">{{ form_row(form.house_constructive_solution)}}</div></div>
<div class='field'><div class="form-group">{{ form_row(form.exploitation_start_year)}}</div></div>
<div class='field'><div class="form-group">{{ form_row(form.non_residential_area)}}</div></div>
<div class='field'><div class="form-group">{{ form_row(form.has_heat_supply_system, {'required': false})}}</div></div>
<div class='field'><div class="form-group">{{ form_row(form.apts_with_individual_heating)}}</div></div>
<h3>{{ 'form.app_applying_person'|trans }}</h3>
<div class='field'><div class="form-group">{{ form_row(form.authorized_person_data)}}</div></div>
<div class='field'><div class="form-group">{{ form_row(form.is_vatpayer, {'required': false})}}</div></div>
<div class='field'><div class="form-group">{{ form_row(form.personal_code)}}</div></div>
<div class='field'><div class="form-group">{{ form_row(form.declared_address)}}</div></div>
<div class='field'><div class="form-group">{{ form_row(form.contact_person_data)}}</div></div>
<h3>{{ 'form.apply_documents'|trans }}</h3>
<section id='cabinet_household_inner_app'>
{% if householdapp is null %}
<h4>{{ 'form.unionRegister'|trans }}</h4>
<div class='field'><div class="form-group">{{ form_widget(form.unionRegister) }}</div></div>
<h4>{{ 'form.aptOwnersDecision'|trans }}</h4>
<div class='field'><div class="form-group">{{ form_widget(form.aptOwnersDecision) }}</div></div>
<h4>{{ 'form.multiAptInventory'|trans }}</h4>
<div class='field'><div class="form-group">{{ form_widget(form.multiAptInventory) }}</div></div>
<h4>{{ 'form.buildingTechnicalSurvey'|trans }}</h4>
<div class='field'><div class="form-group">{{ form_widget(form.buildingTechnicalSurvey) }}</div></div>
<h4>{{ 'form.defectAct'|trans }}</h4>
<div class='field'><div class="form-group">{{ form_widget(form.defectAct) }}</div></div>
<h4>{{ 'form.activityCost'|trans }}</h4>
<div class='field'><div class="form-group">{{ form_widget(form.activityCost) }}</div></div>
{% else %}
<h4>{{ 'form.unionRegister'|trans }}</h4>
Download
<h4>{{ 'form.aptOwnersDecision'|trans }}</h4>
Download
<h4>{{ 'form.multiAptInventory'|trans }}</h4>
Download
<h4>{{ 'form.buildingTechnicalSurvey'|trans }}</h4>
Download
<h4>{{ 'form.defectAct'|trans }}</h4>
Download
<h4>{{ 'form.activityCost'|trans }}</h4>
Download
{% endif %}
<div class="container center" data-prototype="{{ form_widget(form.actOfDefects.vars.prototype)|e('html_attr') }}">
{% for actOfDefect in form.actOfDefects %}
<div class="row document">{{ form_row(actOfDefect) }}</div>
{% endfor %}
<div class="row">
<div class="col-xs-12 center">
<div id="add" class="btn2">{{ "common.addFile"|trans }}</div>
</div>
</div>
</div>
</section>
<div class="center">{{ form_row(form.save) }}</div>
{{ form_end(form, {'render_rest': false}) }}
</div>
</div>
</div>
</section>
{% endblock main %}
{% block js_bottom %}
<script>
var $collectionHolder;
$(document).ready(function () {
// Get the ul that holds the collection of tags
$collectionHolder = $('div.container.center');
// add a delete link to all of the existing tag form li elements
$collectionHolder.find('li').each(function() {
addTagFormDeleteLink($(this));
});
// add the "add a tag" anchor and li to the tags ul
//$collectionHolder.append($newLinkLi);
// count the current form inputs we have (e.g. 2), use that as the new
// index when inserting a new item (e.g. 2)
$collectionHolder.data('index', $collectionHolder.find(':input').length);
$('#add').on('click', function (e) {
// prevent the link from creating a "#" on the URL
e.preventDefault();
addTagForm($collectionHolder);
});
});
function addTagFormDeleteLink($tagFormLi) {
var $removeFormA = $('{{ "common.cancel"|trans }}');
$tagFormLi.append($removeFormA);
$removeFormA.on('click', function(e) {
// prevent the link from creating a "#" on the URL
e.preventDefault();
// remove the li for the tag form
$tagFormLi.remove();
});
}
function addTagForm($collectionHolder) {
var prototype = $collectionHolder.data('prototype');
// get the new index
var index = $collectionHolder.data('index');
// Replace '__name__' in the prototype's HTML to
// instead be a number based on how many items we have
var newForm = prototype.replace(/__name__/g, index);
// increase the index with one for the next item
$collectionHolder.data('index', index + 1);
// Display the form in the page in an li, before the "Add a tag" link li
var $newFormBlock = $('<div class="row document"></div>').append(newForm);
$collectionHolder.append($newFormBlock);
addTagFormDeleteLink($newFormBlock);
}
</script>
{% endblock %}
In your class HouseholdApp, you must have :
public function addActOfDefects(\AppBundle\Entity\DefectAct $actOfDefect)
{
$this->actOfDefects[] = $actOfDefect;
$actOfDefect->setHouseholdApp($this);
return $this;
}
public function removeActOfDefects(\AppBundle\Entity\DefectAct $actOfDefect)
{
$this->actOfDefects->removeElement($actOfDefect);
}

Getting to request result double into the controller

I have the follow problem i send this form to the twig but to obtain the request from the view in the controller i must put two "get", how can fix and do better the code in the controller.
//I have this builder RolType.php:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('nombreRol','entity',
array('class' => 'gdrgdrBundle:Rol',
'choice_label' => 'nombreRol',
'required' => false,
)) ;
}
//My controller mainController.php
$rol = new Rol();
$formRol = $this->createForm(new RolType(), $rol);
$em = $this->getDoctrine()->getManager();
if ($request->isMethod('POST')) {
$formRol->handleRequest($request);
if ($formRol->isSubmitted() && $formRol->isValid()) {
$rolResult = $rol->getNombreRol()->getNombreRol()));
}
}
//View Rol.html.twig
<form action="{{ path("rol")}}" method="post" role="form">
{{ form_errors(nuevoRolForm) }}
{{ form_widget(nuevoRolForm._token) }}
{{ form_rest(nuevoRolForm) }}
</form>
//Entity Rol.php
class Rol
{
private $id;
private $nombreRol;
/**
* Set nombreRol
*
* #param string $nombreRol
* #return Rol
*/
public function setNombreRol($nombreRol)
{
$this->nombreRol = $nombreRol;
return $this;
}
/**
* Get nombreRol
*
* #return string
*/
public function getNombreRol()
{
return $this->nombreRol;
}
}

FormView in Twig lose submitted data when render the form

I use the symfony 2.7.6 full stack framework.
When i submit a form, in the controller i get all submitted data, but in the view there is nothing, an empty form is rendered,something is wrong, i should display the submitted form with errors. but no data and no errors are shown in the twig form...
After hours of debuging of symfony, i can not resolve the problem.
Here is the form Type which is decalred as service:
<?php
namespace Skonsoft\ModelBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
use Doctrine\Common\Persistence\ObjectManager;
use Skonsoft\UserBundle\Entity\User;
class ConversationType extends AbstractType
{
/**
* #var \Doctrine\Common\Persistence\ObjectManager
*/
private $om;
/**
* #var TokenStorageInterface
*/
protected $tokenStorage;
/**
* Constructor
*
* #param ObjectManager $om
* #param TokenStorageInterface $tokenStorage
*/
public function __construct(ObjectManager $om,
TokenStorageInterface $tokenStorage)
{
$this->om = $om;
$this->tokenStorage = $tokenStorage;
}
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('message')
->add('advert', 'entity_hidden',
array(
'class' => 'SkonsoftModelBundle:Advert',
'data_class' => null,
'data' => $options['data']->getAdvert()
))
;
$addUser = function(FormBuilderInterface $builder, User $user, $call) {
$builder->addEventListener(FormEvents::PRE_SET_DATA,
function(FormEvent $event) use ($user, $call) {
$entity = $event->getData();
$entity->$call($user);
});
};
$from = $options['data']->getFrom();
if (!$from) {
$from = $this->om->getRepository('SkonsoftUserBundle:User')->find($this->tokenStorage->getToken()->getUser()->getId());
}
$to = $options['data']->getTo();
if (!$to) {
$advert = $this->om->getRepository('SkonsoftModelBundle:Advert')->find($options['data']->getAdvert());
$to = $advert->getUser();
}
$addUser($builder, $from, 'setFrom');
$addUser($builder, $to, 'setTo');
}
/**
* #param OptionsResolverInterface $resolver
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Skonsoft\ModelBundle\Entity\Conversation'
));
}
/**
* #return string
*/
public function getName()
{
return 'ss_conversation';
}
}
An this is the controller action:
/**
* Creates a new Conversation entity.
*
* #Route("/{id}", name="conversation_create")
* #Method("POST")
* #ParamConverter("advert", class="SkonsoftModelBundle:Advert")
* #Template("SkonsoftUserBundle:Conversation:new.html.twig")
*/
public function createAction(Request $request, Advert $advert)
{
$entity = new Conversation();
$entity->setAdvert($advert);
$form = $this->createCreateForm($entity);
$form->handleRequest($request);
$t = $this->get('translator');
if ($form->isValid()) {
$manager = $this->get('skonsoft_model.conversation_manager');
$manager->persist($entity);
$this->addFlash('success', $t->trans('message.send.success'));
return $this->redirect($request->headers->get('referer'));
}
$this->addFlash('danger', $t->trans('message.send.error'));
return array(
'entity' => $entity,
'form' => $form->createView(),
);
}
/**
* Creates a form to create a Conversation entity.
*
* #param Conversation $entity The entity
*
* #return \Symfony\Component\Form\Form The form
*/
private function createCreateForm(Conversation $entity)
{
$form = $this->createForm('ss_conversation', $entity);
return $form;
}
and this is the twig:
{{form_start(form, {'action': path('conversation_create', {'id': entity.advert.id}), 'method': 'POST', 'class':'form-horizontal', 'role':'form' } )}}
<div class="form-group row">
<div class="col-sm-12 col-xs-12">
{{ form_widget(form.message, {'attr': {'class': 'form-control pull-right', 'placeholder':"message.text_holder" | trans, 'rows':'6' } }) }}
</div>
<div class="widget_error col-sm-12 col-xs-12">
{{ form_errors(form.message) }}
</div>
</div>
<div class="form-group row">
<button type="submit" class="btn btn-success btn-lg {{'css.pull.right'|trans}}">{{ "Send" | trans }} </button>
</div>
{{form_end(form)}}
When i dump the FormView in the controller, i get the submitted data and their errors... but not in the wiew
Any Body has any idea what's wrong ?
Thank you
I'm sorry, it was my mistake, not related to symfony.
So, here is the problem:
With this configuration, everything works fine, just i used the render_esi helper to render the form and i forget that render_esi is a new request to make, so when i submit the form, first master request get and process the form, but i lose all form's data after calling the render_esi helper.

How to submit multiple forms of same type with one button in symfony2

I have the todolist where i display three forms of task type
$task1 = new Task();
$form1 = $this->createForm(new MyForm('f1'), $task1);
$task2 = new Task('fo');
$form2 = $this->createForm(new MyForm('f2'), $task2);
$task3 = new Task();
$form3 = $this->createForm(new MyForm('f3'), $task3);
Now the problem is i have one submit button only . How can i persist these three tasks within one controller. and user can add more forms dynamically as well.
so what the way to solve this
Create a Form Model class — like TaskList — that holds a collection of Tasks. Then create TaskListType that holds a collection of TaskTypes. This way you'll have one form with as many tasks as you want.
For the sake of completeness find below a complete example.
You should create a new Model that represents the desired form. The point is that you probably don't want to affect Doctrine (eg. see doctrine:schema:update command). It might try to create a table for an entity that doesn't really exist. To avoid that, just put your model class under the Model folder (\src\Acme\Bundle\DemoBundle\Model\TaskList.php).
Assume that the following is your TaskType form class:
<?php
namespace Acme\Bundle\DemoBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class TaskType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('id', null, array('read_only' => true))
->add('name');
}
/**
* #param OptionsResolverInterface $resolver
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(
array(
'data_class' => 'Acme\Bundle\DemoBundle\Entity\Task'
)
);
}
/**
* #return string
*/
public function getName()
{
return 'acme_demo_task';
}
}
This should be your TaskList model class:
<?php
namespace Acme\Bundle\DemoBundle\Model;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
/**
* Class TaskList
* #package Acme\Bundle\DemoBundle\Model
*
* #ORM\Entity()
*/
class TaskList
{
/**
* #var \Doctrine\Common\Collections\ArrayCollection
* #ORM\ManyToMany(targetEntity="\Acme\Bundle\DemoBundle\Entity\Task")
*/
private $tasks;
public function __construct()
{
$this->tasks = new ArrayCollection();
}
/**
* #param \Acme\Bundle\DemoBundle\Entity\Task $task
* #return $this
*/
public function addTask(\Acme\Bundle\DemoBundle\Entity\Task $task)
{
$this->tasks[] = $task;
return $this;
}
/**
* #param \Acme\Bundle\DemoBundle\Entity\Task $task
* #return $this
*/
public function removeTask(\Acme\Bundle\DemoBundle\Entity\Task $task)
{
$this->tasks->remove($task);
return $this;
}
/**
* #return ArrayCollection
*/
public function getTasks()
{
return $this->tasks;
}
/**
* #param \Doctrine\Common\Collections\Collection $tasks
* #return $this
*/
public function setTasks(\Doctrine\Common\Collections\Collection $tasks)
{
$this->tasks = $tasks;
return $this;
}
/**
* #param \Knp\Component\Pager\Pagination\PaginationInterface $pagination
* #return $this
*/
public function setFromPagination(\Knp\Component\Pager\Pagination\PaginationInterface $pagination)
{
foreach ($pagination as $task) {
$this->addTask($task);
}
return $this;
}
}
And find below the TaskListType class:
<?php
namespace Acme\Bundle\DemoBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class TaskListType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add(
'tasks',
'collection',
array(
'type' => new \Acme\Bundle\DemoBundle\Form\TaskType(),
)
)
->add('save', 'submit');
}
/**
* #param OptionsResolverInterface $resolver
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(
array(
'data_class' => 'Acme\Bundle\DemoBundle\Model\TaskList'
)
);
}
/**
* #return string
*/
public function getName()
{
return 'acme_demo_task_list';
}
}
And your services.yml (optional):
services:
acme.demo.form.type.task_list:
class: Acme\Bundle\DemoBundle\Form\TaskListType
tags:
- { name: form.type, alias: acme_demo_task_list }
And a sample controller:
public function indexAction($page)
{
ini_set('xdebug.max_nesting_level', 300); // this might be useful with deeply nested forms
$search = $this->getRequest()->get(
'search',
array(
'name' => '',
'date' => '',
'lang' => $this->container->getParameter('acme_core.default_lang')
)
);
/**
* #var \Doctrine\ORM\EntityManager $em
*/
$em = $this->getDoctrine()->getManager();
$paginator = $this->get('knp_paginator');
$pagination = $paginator->paginate(
$em->getRepository('AcmeDemoBundle:Task')->getQueryFilteringByLangNameAndDate(
$search['lang'],
$search['name'],
$search['date'] != '' ? new \DateTime($search['date']) : null
),
$page,
$this->getRequest()->get('elementsPerPage', 10)
);
$taskList = new TaskList();
$taskList->setFromPagination($pagination);
$form = $this->createForm('acme_demo_task_list', $taskList); // "acme_demo_task_list" has been defined in the services.yml file
$form->handleRequest($this->getRequest());
if ($form->isValid()) {
foreach ($form->getData() as $task) {
$em->merge($task);
}
$em->flush();
}
return $this->render(
'AcmeDemoBundle:Task:index.html.twig',
array(
'search' => $search,
'pagination' => $pagination,
'form' => $form->createView()
)
);
}
I hope this helps!
We followed the exapmle shown by 'Francesco Casula' and it worked perfectly.
For orur purposes we didn't need pagination, so this is how we filled our collection (in the controller):
$entities = $em->getRepository('AcmeBundle:Stock')->findAll();
$stockList = new StockList(); // This is our model, what Francesco called 'TaskList'
foreach ($entities as $entity) {
$stockList->addStock($entity);
}
// StockListType() is what Francesco called TaskListType
$form = $this->createForm(new StockListType(), $stockList, array(
'action' => $this->generateUrl('stock_take_update'),
'method' => 'POST',
'attr' => array('class' => 'form-horizontal'),
));
For those who needs to customise the output of the form collections, we managed to access the subform by iterating on {% for form in forms.children.stocks %}. 'Stocks' being the name of the field in our form builder (in StockListType):
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add(
'stocks', 'collection', array(
'type' => new StockType(),
)
)
->add('submit', 'submit')
;
}
This is what we ended up using in our twig view:
{{ form_start(forms) }}
<table class="table table-stripped table-hover">
<thead>
<th>#</th>
<th>Name</th>
<th>Code</th>
<th>Location</th>
<th>Total</th>
<th>Updated Toal</th>
</thead>
<tbody>
{% set counter = 1 %}
{% for form in forms.children.stocks %}
<tr>
<div class="hidden">
{{ form_widget(form.name) }}
{{ form_widget(form.code) }}
{{ form_widget(form.location) }}
{{ form_widget(form.available) }}
{{ form_widget(form.assigned) }}
{{ form_widget(form.minLevel) }}
{{ form_widget(form.type) }}
{{ form_widget(form.colourCode) }}
</div>
<td>{{ counter }}</td>
<td>
{% if form.vars.data.name is defined %}
{{ form.vars.data.name }}
{% endif %}
</td>
<td>
{% if form.vars.data.code is defined %}
{{ form.vars.data.code }}
{% endif %}
</td>
<td>
{% if form.vars.data.location is defined %}
{{ form.vars.data.location }}
{% endif %}
</td>
<td>
{% if form.vars.data.total is defined %}
{{ form.vars.data.total }}
{% endif %}
</td>
<td>{{ form_widget(form.total) }}</td>
</tr>
{% set counter = counter + 1 %}
{% endfor %}
</tbody>
</table>
{{ form_widget(forms.submit) }}
{{ form_end(forms) }}

symfony2 - error when trying to persist form data used in one-to-many/many-to-one

I have a form for submitting an article. The form has a category multi-select box that pulls categories from the DB using my Category.php entity. The form generates fine, but I get the following error when trying to submit:
Property "categories" is not public in class "Natknow\EditorBundle\Entity\Article". Maybe you should create the method "setCategories()"?
Template Form Code:
[...]
<div id="artFormG1">
<?php echo $view['form']->row($form['title']) ?>
<?php echo $view['form']->row($form['description']) ?>
<?php echo $view['form']->row($form['source']) ?>
</div>
<div id="artFormG2">
<?php echo $view['form']->row($form['categories']) ?>
</div>
<?php echo $view['form']->row($form['body']) ?>
<br />
<input class="button" type="submit" formnovalidate = "true" />
<?php echo $view['form']->rest($form) ?>
[...]
ArticleType.php - Form Class
[...]
$builder->add('title', 'text', array(
'attr' => array('class' => 'artFormLeft')
));
$builder->add('description', 'text', array(
'attr' => array('class' => 'artFormLeft')
));
$builder->add('source', 'text', array(
'attr' => array('class' => 'artFormLeft')
));
$builder->add('categories', 'entity',
array('class' => 'NatknowEditorBundle:Category',
'property' => 'name',
'expanded' => false,
'multiple' => true,
)
);
$builder->add('body', 'textarea', array(
'attr' => array('class' => 'tinymce'),
));
[...]
Article.php - article Table Entity
[...]
/**
* Add categories
*
* #param Natknow\EditorBundle\Entity\ArticleCategory $categories
*/
public function addArticleCategory(\Natknow\EditorBundle\Entity\ArticleCategory $categories)
{
$this->categories[] = $categories;
}
/**
* Get categories
*
* #return Doctrine\Common\Collections\Collection
*/
public function getCategories()
{
return $this->categories;
}
[...]
ArticleCatigory.php - article_category Table Entity
[...]
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\ManyToOne(targetEntity="Article", inversedBy="categories", cascade={"all"})
*/
protected $article;
/**
* #ORM\ManyToOne(targetEntity="Category", inversedBy="articles", cascade={"all"})
*/
protected $category;
/**
* #ORM\Column(type="smallint")
*/
protected $isParent = 0;
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set isParent
*
* #param smallint $isParent
*/
public function setIsParent($isParent)
{
$this->isParent = $isParent;
}
/**
* Get isParent
*
* #return smallint
*/
public function getIsParent()
{
return $this->isParent;
}
/**
* Set article
*
* #param Natknow\EditorBundle\Entity\Article $article
*/
public function setArticle(\Natknow\EditorBundle\Entity\Article $article)
{
$this->article = $article;
}
/**
* Get article
*
* #return Natknow\EditorBundle\Entity\Article
*/
public function getArticle()
{
return $this->article;
}
/**
* Set category
*
* #param Natknow\EditorBundle\Entity\Category $category
*/
public function setCategory(\Natknow\EditorBundle\Entity\Category $category)
{
$this->category = $category;
}
/**
* Get category
*
* #return Natknow\EditorBundle\Entity\Category
*/
public function getCategory()
{
return $this->category;
}
[...]
Category.php - category Table Entity
[...]
/**
* Add articles
*
* #param Natknow\EditorBundle\Entity\ArticleCategory $articles
*/
public function addArticleCategory(\Natknow\EditorBundle\Entity\ArticleCategory $articles)
{
$this->articles[] = $articles;
}
/**
* Get articles
*
* #return Doctrine\Common\Collections\Collection
*/
public function getArticles()
{
return $this->articles;
}
[...]
The Controller:
[...]
$art = new Article();
$em = $this->getDoctrine()
->getEntityManager();
$repo = $this->getDoctrine()
->getRepository('NatknowEditorBundle:Article');
$success = "<h3>Use this form to add an article:</h3>";
$form = $this->createForm(new ArticleType(), $art);
if ($request->getMethod() == 'POST') {
$form->bindRequest($request);
if ($form->isValid()) {
$em = $this->getDoctrine()->getEntityManager();
$em->persist($art);
$em->flush();
$success = "You have successfully added the article '"
. $art->getName() . "'!";
$articles = $repo->findAllPastDay();
}else {
$success = "There was an error validating the form data!";
}
return $this->render('NatknowEditorBundle:Default:insertArticle.html.php',
array('form' => $form->createView(), 'status' => $success, 'arts' => $articles,)
);
}
[...]
Heads-up: I used the command line utility to generate the ArticleCategory.php entity, without error. However, I'm not 100% certain it's set up to do what I would like.
You need to add a setCatagories() to the Article entity to get past the posted error.
Could run into other issues. Getting many-many forms to work as desired can be tricky.
Become very familiar with:
http://symfony.com/doc/current/cookbook/form/form_collections.html
You should name addArticleCategory() as addCategory(), then Symfony (master) will recognize it just fine.
Don't forget about using:
Doctrine\Common\Collections\ArrayCollection;
and:
public function __construct()
{
$this->categories = new ArrayCollection();
}
(Article.php)
Not sure if this was the problem, but I just ran into something similar.

Resources