Symfony: Embed collection of forms for ManyToOne-OneToMany relation - forms

I am running Symfony 2.3 with Doctrine and these three (relevant) entities: Publication, Author and AuthorPublication.
Both, Author and Publication have a Many-to-One relationship to AuthorPublication (so it is basically a Many-to-Many relation between Author and Publication but I need the AuthorPublication Entity to order the authors of a publication)
I want to have a form where a user can create a new publication and choose as many authors for that publication as he wants.
I studied this article: How to Embed a Collection of Forms but I do not understand how to apply that to my problem because of the AuthorPublication entity which lies in between.
Relevant Code:
Publication
<?php
namespace ind\PubBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass="ind\PubBundle\Repository\PublicationRepository")
* #ORM\Table("publications")
*/
class Publication {
/**
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $pid;
/**
* #ORM\OneToMany(targetEntity="AuthorPublication", mappedBy="publication")
* #ORM\OrderBy({"order_id" = "ASC"})
*/
protected $publicationAuthors;
//some more attributes + getters/seters
?>
Author
<?php
namespace ind\PubBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
* #ORM\Table("aid_author")
*/
class Author {
/**
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $aid;
/**
* #ORM\Column(type="string", length=255)
*/
protected $author_surname;
/**
* #ORM\Column(type="string", length=255)
*/
protected $author_forename;
/**
* #ORM\OneToMany(targetEntity="AuthorPublication", mappedBy="author")
*/
protected $authorPublication;
?>
AuthorPublication
<?php
namespace ind\PubBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
* #ORM\Table("aid_pid")
*/
class AuthorPublication {
/**
* #ORM\Id
* #ORM\Column(type="integer")
*/
protected $aid;
/**
* #ORM\Id
* #ORM\Column(type="integer")
*/
protected $pid;
/**
* #ORM\Column(type="integer")
*/
protected $order_id;
/**
* #ORM\ManyToOne(targetEntity="Publication", inversedBy="publicationAuthors")
* #ORM\JoinColumn(name="pid", referencedColumnName="pid")
*/
protected $publication;
/**
* #ORM\ManyToOne(targetEntity="Author", inversedBy="authorPublication")
* #ORM\JoinColumn(name="aid", referencedColumnName="aid")
*/
protected $author;
?>

You have to make an AuthorPublicationType form. You put field author as an 'entity' and your others fields...
You make your PublicationType including AuthorPublication (Embedded Forms).
Then you can add new AuthorPublication with prototype and very simple javascript.
Note that when you have to save your entity Publication with an authorPublication attribut null first. And after update Publication with authorPublication defined by using temporary ArrayCollection in Publication and LifecycleCallbacks for example.
EDIT : Example.
My entities are Sport OneToMany SportParam ManyToOne ConfigParam. SportParam is composed of a Sport, a ConfigParam and a value.
I want to create a new Sport with multiple ConfigParam :
SportParamType :
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('ConfigParam', 'entity', array(
'class' => 'PPHBSportScoringBundle:ConfigParam',
'property' => 'nom',
'multiple' => false,
'label'=>'Paramètre'
))
->add('valeur','number',array('precision'=>2))
;
}
SportType
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('nom')
->add('SportParams', 'collection', array(
'type'=> new SportParamType(),
'allow_add' => true,
'allow_delete' => true,
'by_reference'=> false
))
;
}
My form.html.twig :
{{ form_start(form) }}
{{ form_errors(form) }}
{{ form_row(form.nom) }}
<ul class=SportParams data-prototype="{{ form_widget(form.SportParams.vars.prototype)|e }}">
{% for param in form.SportParams %}
<li>
{{ form_errors(param.ConfigParam) }}
{{ form_widget(param.ConfigParam) }}
{{ form_errors(param.valeur) }}
{{ form_widget(param.valeur) }}
</li>
{% endfor %}
</ul>
<input type="submit" class="btn btn-primary" />
{{ form_end(form) }}
My Javascript refine because it contains more code (AJAX call). It maybe contains some mistake. If it's not clear have a look to the documentation :
<script type="text/javascript">
var $container = $('ul.SportParams');
// button to add a new SportParam
var $addSportParamLink = $('Ajouter un paramètre');
var $newLinkLi = $('<li></li>').append($addSportParamLink);
$(document).ready(function() {
//delete button on each existing SportParam
$container.find('li').each(function() {
addParamFormDeleteLink($(this));
});
//add button
$container.append($newLinkLi);
// adding a new form when cliking Add button
$addSportParamLink.on('click',function(e) {
e.preventDefault(); // évite qu'un #apparaisse dans l'URL
var index = $container.children().length-1;
addParamForm($container,$newLinkLi);
var bAffiche;
return false;
});
// adding a new form SportParam
function addParamForm($container, $newLinkLi) {
var $prototype = $container.attr('data-prototype');
var newForm = $prototype.replace(/__name__/g, $container.children().length-1);
var $newFormLi = $('<li></li>').append(newForm);
$newLinkLi.before($newFormLi);
addParamFormDeleteLink($newFormLi);
}
function addParamFormDeleteLink($paramFormLi){
var $removeFormA = $('Supprimer');
$paramFormLi.append($removeFormA);
$removeFormA.on('click', function(e) {
e.preventDefault();
$paramFormLi.remove();
});
}
});
</script>
Sport Entity call's back :
/**
* sport
*
* #ORM\Table()
* #ORM\Entity
* #ORM\HasLifecycleCallbacks
* #ORM\Entity(repositoryClass="SportRepository")
*/
class Sport
{
...Entity attribut...
/**
* #var ArrayCollection
*
* To save Sport without SportParams
*/
private $SportParamsTMP;
...getter and setter ...
/**
* #ORM\PrePersist
*/
public function saveSportParams()
{
$this->SportParamsTMP = $this->SportParams;
$this->SportParams = null;
}
/**
* #ORM\PostPersist
*/
public function restoreSportParams()
{
if ($this->SportParamsTMP == !null) {
$this->SportParams = $this->SportParamsTMP;
}
$this->SportParamsTMP = null;
}
}
Finally the controller 's function to add a new Sport :
public function addAction()
{
$sport = new Sport();
$form = $this->createForm(new SportType(), $sport);
$request = $this->getRequest();
if ($request->getMethod() == "POST") {
$form->bind($request);
if($form->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($sport);
//saving sport without parameter
$em->flush();
//updating sport with parameter
$em->flush();
return $this->redirect($this->generateUrl('pphb_sport_liste'));
}
}
return $this->render('PPHBSportScoringBundle:Championnat/Sport:add.html.twig', array(
'form' => $form->createView(),
));
}
I hope it help.
Don't know if it's the best way to do it, but it's working for me. If something to improve let me know please.

Related

Enable Select2Search in Symfony form

I want to enable select 2 search in my Symfony form what i tried so far:
In my form class i have this:
->add('parent', EntityType::class, [
'class' => Category::class,
'choice_label' => 'title',
'attr' => [
'class' => 'select2'
]
])
In my twig file this :
<head>
<link href="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.2-rc.1/css/select2.min.css" rel="stylesheet" />
<!-- Loading jquery here--><script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.0/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.2-rc.1/js/select2.min.js"></script>
</head>
{{ form_start(form) }}
<script type="text/javascript">
$('select').select2();
</script>
{{ form_widget(form) }}
{{ form_end(form) }}
But i do not get the dropdown with the search bar. Just the default dropdown menu of Symfony. What am I doing wrong
The main reason is that the field is created after you try and target it, by this line:
{{ form_widget(form) }}
The JavaScript must be executed after that in order to be able to target the field (besides, the HTML structure of your template is wrong).
Try this :
<!DOCTYPE html>
<html>
<head>
<title>Test form</title>
<link href="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.2-rc.1/css/select2.min.css" rel="stylesheet" />
<!-- Loading jquery here--><script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.0/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.2-rc.1/js/select2.min.js"></script>
</head>
<body>
{{ form_start(form) }}
{{ form_widget(form) }}
{{ form_end(form) }}
<script>
$('select').select2();
</script>
</body>
</html>
It's usually better to wait for the page to be loaded before executing scripts, using jQuery you could ensure that it's the case by changing the script to this:
<script>
$(document).ready(function(){
$('.select2').select2();
});
</script>
Notice that I also changed the jQuery selector to use the class you've added to the field in your form builder. This way you control the select field you want to target.
You are initiating the select2 components without configuration, so it doesn't know where is the data source.
Before start coding, you need to install and configure FOSJsRoutingBundle. This bundle will help you with access to ajax routes
For fully configured sync symfony-forms~select2 you could do something like this.
Entity Person
class Person
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer", nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="SEQUENCE")
* #ORM\SequenceGenerator(sequenceName="person_id_seq", allocationSize=1, initialValue=1)
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="name", type="string", nullable=true)
*/
private $name;
/**
* #var Country
*
* #ORM\ManyToOne(targetEntity="Country")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="country_id", referencedColumnName="id")
* })
*/
private $country;
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set name
*
* #param string $name
*
* #return Person
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Get name
*
* #return string
*/
public function getName()
{
return $this->name;
}
/**
* Set country
*
* #param \AppBundle\Entity\Country $country
*
* #return Person
*/
public function setCountry(\AppBundle\Entity\Country $country = null)
{
$this->country = $country;
return $this;
}
/**
* Get country
*
* #return \AppBundle\Entity\Country
*/
public function getCountry()
{
return $this->country;
}
public function __toString()
{
return $this->name;
}
}
Entity Country
class Country
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer", nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="SEQUENCE")
* #ORM\SequenceGenerator(sequenceName="country_id_seq", allocationSize=1, initialValue=1)
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="name", type="string", nullable=true)
*/
private $name;
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set name
*
* #param string $name
*
* #return Country
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Get name
*
* #return string
*/
public function getName()
{
return $this->name;
}
public function __toString()
{
return $this->name;
}
}
Country repository
class CountryRepository extends \Doctrine\ORM\EntityRepository
{
public function countriesSelect2($term)
{
$qb = $this->createQueryBuilder('c');
$qb->where(
$qb->expr()->like($qb->expr()->lower('c.name'), ':term')
)
->setParameter('term', '%' . strtolower($term) . '%');
return $qb->getQuery()->getArrayResult();
}
}
Country controller
Check how the route is exposed to the options parameter and returns a JsonResponse. You could also use a serializer too.
/**
* Country controller.
*
* #Route("countries")
*/
class CountryController extends Controller
{
/**
* Lists all person entities.
*
* #Route("/", name="countries",options={"expose"=true})
* #Method("GET")
*/
public function indexAction(Request $request)
{
$countryRepo = $this->getDoctrine()->getRepository('AppBundle:Country');
$data = $countryRepo->countriesSelect2($request->get('q', ''));
//$response = $this->get('serializer')->serialize($data,'json');
return new JsonResponse($data);
}
}
So far so good, now comes the good parts, let's go and configure our form
PersonType
class PersonType extends AbstractType
{
/**
* {#inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('name')
->add('country',EntityType::class,[
'class' => Country::class,
'attr' => [
'class' => 'select2', // the class to use with jquery
'data-source' => 'countries', //the exposed route name for data-soirce as attr
'data-allow-clear' => 'true'//another extra attr to customize
],
]);
}/**
* {#inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\Person'
));
}
/**
* {#inheritdoc}
*/
public function getBlockPrefix()
{
return 'appbundle_person';
}
}
JS, showing the select2
Remember, you have configured already the select2 options with attributes, you just have to use them properly
$(document).ready(function () {
$('.select2').each(function () {//using the select2 class
if (!$().select2) {//checking the script
return;
}
$.fn.select2.defaults.set("theme", "bootstrap");//some theming if you want
$($(this)).select2({
placeholder: "Select",
width: 'auto',
allowClear: $(this).attr("data-allow-clear") ? $(this).attr("data-allow-clear") : true, //using my options from the form
ajax: {
url: Routing.generate($(this).attr("data-source")), //here its the magic
dataType: 'json',
processResults: function (data) {
//console.log(data);
return {
results: $.map(data, function (item) {
return {
text: item.name, //you need to map this because the plugin accepts only id and text
id: item.id
}
})
};
}
}
});
});
});
after that, all is done. All the code is working as I tested my self
Hope it helps!
There is a nice bundle for it: TetranzBundle
You can configure your form field in FormType class like that:
->add('product', Select2EntityType::class, [
'label'=>'product',
'required'=>true,
'mapped'=>true,
'multiple' => false,
'remote_route' => 'product_select2_ajax',
'class' => 'AppBundle:Product',
// 'property' => 'name',
'minimum_input_length' => 0,
'page_limit' => 10,
'allow_clear' => true,
'delay' => 250,
'cache' => true,
'cache_timeout' => 60000, // if 'cache' is true
'language' => 'pl',
'placeholder' => "select.product",
])
Try this :
<script type="text/javascript">
$(document).ready(function() {
$('.select2').select2(); //instead $('select2').select2();
});
</script>
see How to select a class in Jquery and the basic usage exemple

VichUploader image upload in embedded form (Symfony 3)

How can I successfully embed a running image upload form inside a classified form with OneToOne relationship ?
first post ever on Stackoverflow so please be gentle (and I'm french ...^^)
I have a Symfony 3.3 project, running great. I use VichUploaderBundle for upload and LiipImagine for rendering. Currently, NO PROBLEM for a single image File upload !
I have an uploadtest page, I show one image file upload form, I upload the file ... it goes throught the controller and everything is fine. File uploaded and moved in directory, page shows new image, with all filters. Great !
My real problem comes when embedding the form inside another form.
I would like to embed my "image form" inside a classified form. I started by creating the field mainImage inside this classified form. The relation from the classified is OneToOne, and of course it has cascade="persist" & "remove".
But on validation, nothing is triggered and I got the following error on the image_name :
--- An exception occurred while executing 'INSERT INTO vehicle_ad (createdAt, updatedAt, published, publishedAt, friendsPriority,
friendsPriorityStart, purchaseDate, sellPrice, currency, odometerType,
currentOdometer, energy, gearbox, mainColor, nickname, description,
creator_id, vehicle_brand_id, vehicle_model_id, main_image_id,
vehicle_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
?, ?, ?, ?)' with params ["2018-01-23 17:15:39", "2018-01-23
17:15:39", 0, null, 0, null, null, 2650, "eur", "kilometers", 17340,
null, null, null, null, null, 1, 3, 1, null, null]:
SQLSTATE[23000]: Integrity constraint violation: 1048 Le champ
'main_image_id' ne peut être vide (null)
File is empty, vichuploader namer not triggered ... and I am stuck with it since a few days. I did a lot of research ... so any help will be greatly appreciated.
I try to give my code as clearly as possible. First, what is OK (single image upload) :
Image class
<?php
namespace Gac\MediasBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Component\HttpFoundation\File\File;
use Vich\UploaderBundle\Mapping\Annotation as Vich;
/**
* Image
*
* #ORM\Table(name="image")
* #ORM\Entity(repositoryClass="Gac\MediasBundle\Repository\ImageRepository")
* #Vich\Uploadable
*/
class Image
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/* ----------------------------------------------------------------------
--------- Connection with other entities
------------------------------------------------------------------------- */
/**
* #ORM\ManyToOne(targetEntity="Gac\UserBundle\Entity\User", inversedBy="images")
* #ORM\JoinColumn(nullable=false)
*/
private $creator;
/**
* #ORM\ManyToOne(targetEntity="Gac\AdsBundle\Entity\VehicleAd", inversedBy="images")
* #ORM\JoinColumn(nullable=true)
*/
private $vehicleAd;
// -------------------------------------------------------------------------------
/**
* #var string
*
* #ORM\Column(name="imageName", type="string", length=255)
*/
private $imageName;
/**
* #Vich\UploadableField(mapping="users_images", fileNameProperty="imageName")
* #var File
*/
private $imageFile;
/**
* #var \DateTime
*
* #ORM\Column(name="createdAt", type="datetime")
* #Assert\DateTime()
*/
private $createdAt;
/**
* #var \DateTime
*
* #ORM\Column(name="updatedAt", type="datetime")
* #Assert\DateTime()
*/
private $updatedAt;
/**
* #var \DateTime
*
* #ORM\Column(name="hiddenAt", type="datetime", nullable=true)
* #Assert\DateTime()
*/
private $hiddenAt;
public function __construct($thisuser)
{
$this->creator = $thisuser;
$this->createdAt = new \Datetime();
$this->updatedAt = new \Datetime();
}
/**
* Get id
*
* #return int
*/
public function getId()
{
return $this->id;
}
/**
* Set creator
*
* #param \Gac\UserBundle\Entity\User $creator
*
* #return Image
*/
public function setCreator(\Gac\UserBundle\Entity\User $creator)
{
$this->creator = $creator;
return $this;
}
/**
* Get creator
*
* #return \Gac\UserBundle\Entity\User
*/
public function getCreator()
{
return $this->creator;
}
/**
* Set imageName
*
* #param string $imageName
*
* #return Image
*/
public function setImageName($imageName)
{
$this->imageName = $imageName;
return $this;
}
/**
* Get imageName
*
* #return string
*/
public function getImageName()
{
return $this->imageName;
}
public function setImageFile(File $image = null)
{
$this->imageFile = $image;
// VERY IMPORTANT:
// 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
if ($image) {
$this->updatedAt = new \DateTime();
}
}
/**
* Get imageFile
*/
public function getImageFile()
{
return $this->imageFile;
}
/**
* Set createdAt
*
* #param \DateTime $createdAt
*
* #return Image
*/
public function setCreatedAt($createdAt)
{
$this->createdAt = $createdAt;
return $this;
}
/**
* Get createdAt
*
* #return \DateTime
*/
public function getCreatedAt()
{
return $this->createdAt;
}
/**
* Set updatedAt
*
* #param \DateTime $updatedAt
*
* #return Image
*/
public function setUpdatedAt($updatedAt)
{
$this->updatedAt = $updatedAt;
return $this;
}
/**
* Get updatedAt
*
* #return \DateTime
*/
public function getUpdatedAt()
{
return $this->updatedAt;
}
/**
* Set hiddenAt
*
* #param \DateTime $hiddenAt
*
* #return Image
*/
public function setHiddenAt($hiddenAt)
{
$this->hiddenAt = $hiddenAt;
return $this;
}
/**
* Get hiddenAt
*
* #return \DateTime
*/
public function getHiddenAt()
{
return $this->hiddenAt;
}
/**
* Set vehicleAd
*
* #param \Gac\AdsBundle\Entity\VehicleAd $vehicleAd
*
* #return Image
*/
public function setVehicleAd(\Gac\AdsBundle\Entity\VehicleAd $vehicleAd = null)
{
$this->vehicleAd = $vehicleAd;
return $this;
}
/**
* Get vehicleAd
*
* #return \Gac\AdsBundle\Entity\VehicleAd
*/
public function getVehicleAd()
{
return $this->vehicleAd;
}
}
Then the related IMAGE FORM :
<?php
namespace Gac\MediasBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Vich\UploaderBundle\Form\Type\VichImageType;
class ImageType extends AbstractType
{
/**
* {#inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('imageFile', VichImageType::class, [
'label' => false,
'required' => false,
'allow_delete' => false,
]);
}
/**
* {#inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Gac\MediasBundle\Entity\Image'
));
}
/**
* {#inheritdoc}
*/
public function getBlockPrefix()
{
//return 'gac_mediasbundle_image';
return 'ImageType';
}
}
And the controller
{% extends 'medias/layout.html.twig' %}
{% block bundle_content %}
<div class="jumbotron text-center">
<h1>Testing user's images UPLOAD</h1>
</div>
<p>-----------------</p>
{{ form_start(form) }}
{{ form_errors(form) }}
{{ form_row(form) }}
<button type="submit">Upload</button>
{{ form_end(form) }}
<p>-----------------</p>
{% for picture in pictures %}
<div class="row">
<div class="media">
<div class="media-left media-middle">
<img class="media-object" src="{{ picture.imageName | imagine_filter('tiny_test') }}" alt="{{ 'GearsAndCoffee-'~picture.imageName }}" /><br/>
</div>
<div class="media-body">
<h4 class="media-heading">Filter name :</h4>
<em>tiny_test</em>
</div>
</div>
<div class="media">
<div class="media-left media-middle">
<a href="{{ asset('uploads/images/'~picture.imageName) }}" />
<img class="media-object" src="{{ picture.imageName | imagine_filter('std_display') }}" alt="{{ 'GearsAndCoffee-'~picture.imageName }}" /><br/>
</a>
</div>
<div class="media-body">
<h4 class="media-heading">Filter name :</h4>
<em>std_display</em>
</div>
</div>
<p>
<strong>UPDATE IMAGE</strong>
</p>
<p>
<strong>DELETE IMAGE</strong>
</p>
</div>
<p>-----------------</p>
{% endfor %}
{% endblock bundle_content %}
As an information, the VichUploader Bundle config :
vich_uploader:
db_driver: orm
#templating: true
#twig: true
#form: true
mappings:
users_images:
uri_prefix: '%app.path.users_images%'
upload_destination: '%kernel.root_dir%/../web/uploads/images'
namer: vich_uploader.namer_uniqid
inject_on_load: true
delete_on_update: true
delete_on_remove: true
And now the code of what is NOT RUNNING on my app :
The classified class
<?php
namespace Gac\AdsBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
use Doctrine\Common\Collections\ArrayCollection;
/**
* VehicleAd
*
* #ORM\Table(name="vehicle_ad")
* #ORM\Entity(repositoryClass="Gac\AdsBundle\Repository\VehicleAdRepository")
* #ORM\HasLifecycleCallbacks
*/
class VehicleAd
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/* ----------------------------------------------------------------------
--------- Connection with other entities
------------------------------------------------------------------------- */
/**
* #ORM\ManyToOne(targetEntity="Gac\UserBundle\Entity\User")
* #ORM\JoinColumn(nullable=false)
*/
private $creator;
/**
* #ORM\ManyToOne(targetEntity="Gac\VehiclesBundle\Entity\VehicleBrand")
* #ORM\JoinColumn(nullable=false)
*/
private $vehicleBrand;
/**
* #ORM\ManyToOne(targetEntity="Gac\VehiclesBundle\Entity\VehicleModel")
* #ORM\JoinColumn(nullable=false)
*/
private $vehicleModel;
/**
* #ORM\OneToOne(targetEntity="Gac\MediasBundle\Entity\Image", cascade={"persist", "remove"})
* #ORM\JoinColumn(nullable=false)
*/
private $mainImage;
// -------------------------------------------------------------------------------
// ---------------- General status and dates for the AD -------------
/**
* #var \DateTime
*
* #ORM\Column(name="createdAt", type="datetime")
* #Assert\DateTime()
*/
private $createdAt;
/**
* #var \DateTime
*
* #ORM\Column(name="updatedAt", type="datetime")
* #Assert\DateTime()
*/
private $updatedAt;
// ---------------------- AD main content -----------------------
/**
* #var string
*
* #ORM\Column(name="description", type="text", nullable=true)
* #Assert\Type("string")
*/
private $description;
// ************************* FUNCTIONS ***************************
public function __construct($thisuser)
{
$this->creator = $thisuser;
$this->createdAt = new \Datetime();
$this->updatedAt = new \Datetime();
}
/**
* Change updatedAt field just before ORM update
*
* #ORM\PreUpdate
*/
public function changeUpdatedAt()
{
$this->updatedAt = new \Datetime();
}
/**
* Get id
*
* #return int
*/
public function getId()
{
return $this->id;
}
/**
* Set createdAt
*
* #param \DateTime $createdAt
*
* #return VehicleAd
*/
public function setCreatedAt($createdAt)
{
$this->createdAt = $createdAt;
return $this;
}
/**
* Get createdAt
*
* #return \DateTime
*/
public function getCreatedAt()
{
return $this->createdAt;
}
/**
* Set updatedAt
*
* #param \DateTime $updatedAt
*
* #return VehicleAd
*/
public function setUpdatedAt($updatedAt)
{
$this->updatedAt = $updatedAt;
return $this;
}
/**
* Get updatedAt
*
* #return \DateTime
*/
public function getUpdatedAt()
{
return $this->updatedAt;
}
/**
* Set creator
*
* #param \Gac\UserBundle\Entity\User $creator
*
* #return VehicleAd
*/
public function setCreator(\Gac\UserBundle\Entity\User $creator)
{
$this->creator = $creator;
return $this;
}
/**
* Get creator
*
* #return \Gac\UserBundle\Entity\User
*/
public function getCreator()
{
return $this->creator;
}
/**
* Set description
*
* #param string $description
*
* #return VehicleAd
*/
public function setDescription($description)
{
$this->description = $description;
return $this;
}
/**
* Get description
*
* #return string
*/
public function getDescription()
{
return $this->description;
}
/**
* Set vehicleBrand
*
* #param \Gac\VehiclesBundle\Entity\VehicleBrand $vehicleBrand
*
* #return VehicleAd
*/
public function setVehicleBrand(\Gac\VehiclesBundle\Entity\VehicleBrand $vehicleBrand)
{
$this->vehicleBrand = $vehicleBrand;
return $this;
}
/**
* Get vehicleBrand
*
* #return \Gac\VehiclesBundle\Entity\VehicleBrand
*/
public function getVehicleBrand()
{
return $this->vehicleBrand;
}
/**
* Set vehicleModel
*
* #param \Gac\VehiclesBundle\Entity\VehicleModel $vehicleModel
*
* #return VehicleAd
*/
public function setVehicleModel(\Gac\VehiclesBundle\Entity\VehicleModel $vehicleModel)
{
$linkedBrand = $vehicleModel->getModelBrand();
$this->vehicleBrand = $linkedBrand;
$this->vehicleModel = $vehicleModel;
return $this;
}
/**
* Get vehicleModel
*
* #return \Gac\VehiclesBundle\Entity\VehicleModel
*/
public function getVehicleModel()
{
return $this->vehicleModel;
}
/**
* Set mainImage
*
* #param \Gac\MediasBundle\Entity\Image $mainImage
*
* #return VehicleAd
*/
public function setMainImage(\Gac\MediasBundle\Entity\Image $mainImage)
{
$this->mainImage = $mainImage;
return $this;
}
/**
* Get mainImage
*
* #return \Gac\MediasBundle\Entity\Image
*/
public function getMainImage()
{
return $this->mainImage;
}
}
Of course the big one, my Classified Form !
<?php
namespace Gac\AdsBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\Extension\Core\Type\IntegerType;
use Symfony\Component\Form\Extension\Core\Type\DateType;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
use Gac\MediasBundle\Form\ImageType;
use Gac\VehiclesBundle\Entity\VehicleBrand;
use Gac\VehiclesBundle\Entity\VehicleModel;
class VehicleAdType extends AbstractType
{
/**
* {#inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$globalDatas = $options['globalDatas'];
$builder->add('vehicleBrand', EntityType::class, array(
'class' => 'GacVehiclesBundle:VehicleBrand',
'choice_label' => 'brandName',
'placeholder' => 'Please select a brand :',
))
->add('vehicleModel', EntityType::class, array(
'class' => 'GacVehiclesBundle:VehicleModel',
'choice_label' => 'modelName',
))
->add('description', TextareaType::class, array('required' => false));
$builder->add('mainImage', ImageType::class);
$formModifier = function (FormInterface $form, VehicleBrand $vehicleBrand = null) {
$models = null === $vehicleBrand ? array() : $vehicleBrand->getVehicleModels();
$form->add('vehicleModel', EntityType::class, array(
'class' => 'GacVehiclesBundle:VehicleModel',
'choice_label' => 'modelName',
'choices' => $models,
));
};
$builder->addEventListener(
FormEvents::PRE_SET_DATA,
function (FormEvent $event) use ($formModifier) {
// this would be your entity, i.e. Brand
$data = $event->getData();
$formModifier($event->getForm(), $data->getVehicleBrand());
}
);
$builder->get('vehicleBrand')->addEventListener(
FormEvents::POST_SUBMIT,
function (FormEvent $event) use ($formModifier) {
$vehicleBrand = $event->getForm()->getData();
$formModifier($event->getForm()->getParent(), $vehicleBrand);
}
);
}
/**
* {#inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Gac\AdsBundle\Entity\VehicleAd',
'globalDatas' => null,
));
}
/**
* {#inheritdoc}
*/
public function getBlockPrefix()
{
return 'gac_adsbundle_vehiclead';
}
}
The classified creation controller
<?php
namespace Gac\AdsBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Gac\MediasBundle\Entity\Image;
use Gac\MediasBundle\Form\ImageType;
use Gac\AdsBundle\Entity\VehicleAd;
use Gac\AdsBundle\Form\VehicleAdType;
use Gac\VehiclesBundle\Entity\Vehicle;
use Gac\VehiclesBundle\Form\VehicleType;
use Gac\VehiclesBundle\Entity\VehicleModel;
use Gac\VehiclesBundle\Entity\VehicleBrand;
class ManageController extends Controller
{
public function sellvehicleAction(Request $request)
{
$this->denyAccessUnlessGranted('ROLE_USER');
$thisuser = $this->get('security.token_storage')->getToken()->getUser();
$em = $this->getDoctrine()->getManager();
// Create new ad and load a "mainImage"
$classified = new VehicleAd($thisuser);
$oneImage = new Image($thisuser);
$classified->setMainImage($oneImage);
$globalDatas = $this->container->get('gac_general.globaldatas'); // Always pass globalDatas VAR to any created form in the application
$form = $this->createForm(VehicleAdType::class, $classified, array(
'globalDatas' => $globalDatas,
));
$form->handleRequest($request);
if ($request->isMethod('POST') && $form->isValid()) {
$em->persist($classified);
$em->flush();
$id = $classified->getId();
return $this->redirectToRoute('gac_ads_previewvehiclead', array('id' => $id));
}
return $this->render('ads/manage/sellvehicle.html.twig', array(
'form' => $form->createView()
));
}
}
And finally the view of my classified form
{% extends 'ads/layout.html.twig' %}
{% block bundle_content %}
<form action="{{ path('gac_ads_sellvehicle') }}" method="post">
<div class="row justify-content-center">
<div class="col-md-8">
{{ form_errors(form) }}
<div class="card mb-3">
<div class="card-body">
<p class="text-primary"><strong>{{ 'mandatory informations'|trans|capitalize }} :</strong></p>
{{ form_row(form.vehicleBrand, {'label': 'Vehicle Brand :'}) }}
{{ form_row(form.vehicleModel, {'label': 'Vehicle Model :'}) }}
</div>
</div>
<div class="card mb-3">
<div class="card-body">
<p class="text-primary">{{ 'image'|trans|capitalize }} :</p>
<div id="toto">
{{ form_row(form.mainImage.imageFile) }}
</div>
</div>
</div>
<div class="card mb-3">
<div class="card-body">
<p class="text-primary">{{ 'other parameters'|trans|capitalize }} ({{ 'optional'|trans }}) :</p>
<p class="text-primary">{{ 'tell us a few words about your vehicle'|trans|capitalize }} ({{ 'optional'|trans }}) :</p>
{{ form_row(form.description, {'label': 'Description :'}) }}
</div>
</div>
</div>
<div class="col-md-4">
<div class="card mb-3">
<div class="card-body">
{{ form_rest(form) }}
</div>
</div>
<button type="submit" class="btn btn-block btn-primary">{{ 'preview your ad'|capitalize }}</button>
</div>
</div>
</form>
{% endblock bundle_content %}
I must say that before asking, I did a lot of searching by myself (tests ...) and tons of google search. I know that there is some tricky stuff with Vich Uploader but I managed everything because ... it works for one file.
So the question is : can you help me make my upload and my form work when embedded in another one ?
Thanks a lot !
Regards
Try to do this in "classified" form: ...add('description', textAreaType::class) ->add('mainImage', ImageType::class); Maybe,using twice FormBuilder instance "$builder" causes problemes. In any case: see here Formulaires Imbriqués. I did so and it works great with embedded forms using vich

Persistence with embedded forms

I am trying to set up some embedded forms using this guide.
I have two models set up in my application, Lesson and Evaluation.
Each Lesson can have multiple Evaluations.
I have a form set up where the user can create a lesson and also as many evaluations as they want within that lesson. When the form is submitted, it creates the lesson record and all of the evaluation records successfully, however the evaluation records that get created are not linked to the parent lesson (the lesson_id field is just left blank).
Can anyone help?
Any advice appreciated.
Thanks.
My Model classes are set up like this:
Evaluation:
class Evaluation
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="Lesson", inversedBy="evaluations")
* #ORM\JoinColumn(name="lesson_id", referencedColumnName="id")
*/
protected $lesson;
/**
* Set lesson
*
* #param \LessonBundle\Entity\Lesson $lesson
* #return Evaluation
*/
public function setLesson(\LessonBundle\Entity\Lesson $lesson = null)
{
$this->lesson = $lesson;
return $this;
}
/**
* Get lesson
*
* #return \LessonBundle\Entity\Lesson
*/
public function getLesson()
{
return $this->lesson;
}
}
And the Lesson:
class Lesson
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\OneToMany(targetEntity="Evaluation", mappedBy="lesson", cascade={"persist"})
*/
protected $evaluations;
public function __construct()
{
$this->evaluations = new ArrayCollection();
}
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Add evaluations
*
* #param \DS\LessonBundle\Entity\Evaluation $evaluations
* #return Lesson
*/
public function addEvaluation(\LessonBundle\Entity\Evaluation $evaluations)
{
$this->evaluations[] = $evaluations;
return $this;
}
/**
* Remove evaluations
*
* #param \DS\LessonBundle\Entity\Evaluation $evaluations
*/
public function removeEvaluation(\LessonBundle\Entity\Evaluation $evaluations)
{
$this->evaluations->removeElement($evaluations);
}
/**
* Get evaluations
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getEvaluations()
{
return $this->evaluations;
}
public function setEvaluations(ArrayCollection $evaluations)
{
foreach ($evaluations as $evaluation) {
$evaluation->setLesson($this);
}
$this->evaluations = $evaluations;
}
}
My Controller method:
public function newAction()
{
$lesson = new Lesson;
$evaluation1 = new Evaluation();
$lesson->getEvaluations()->add($evaluation1);
$form = $this->createForm(new LessonType(), $lesson);
$request = $this->getRequest();
if ($request->getMethod() == 'POST') {
$form->bindRequest($request);
if ($form->isValid()) {
$em = $this->getDoctrine()->getEntityManager();
$em->persist($lesson);
$em->flush();
return $this->redirect($this->generateUrl('lesson_list'));
}
}
return $this->render('LessonBundle:Lesson:new.html.twig', array('form' => $form->createView()));
}
And my forms:
class LessonType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('evaluations', 'collection', array(
'type' => new EvaluationType(),
'allow_add' => true,
'by_reference' => false,
));
}
public function getDefaultOptions(array $options)
{
return array(
'data_class' => 'LessonBundle\Entity\Lesson',
);
}
public function getName()
{
return 'Lesson';
}
}
And:
class EvaluationType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('report');
}
public function getDefaultOptions(array $options)
{
return array(
'data_class' => 'LessonBundle\Entity\Evaluation',
);
}
public function getName()
{
return 'Evaluation';
}
}
And finally, my form twig template:
{% extends '::base.html.twig' %}
{% block content %}
<form class="vertical" action="" method="post" {{ form_enctype(form) }}>
{{ form_errors(form) }}
<ul class="collectionholder" data-prototype="{{ form_widget(form.evaluations.vars.prototype)|e }}">
{% for evaluation in form.evaluations %}
<li>{{ form_row(evaluation) }}</li>
{% endfor %}
</ul>
{{ form_rest(form) }}
<input type="submit" />
</form>
{% endblock %}
In your class Lesson entity add:
/**
* Add evaluations
*
* #param \DS\LessonBundle\Entity\Evaluation $evaluations
* #return Lesson
*/
public function addEvaluation(\LessonBundle\Entity\Evaluation $evaluations)
{
$this->evaluations[] = $evaluations;
$evaluations->setLesson($this);
return $this;
}

Symfony 2 Doctrime 2 and form validate ( unique field )

Hello I have small problem. I've never done form validator in sf2 so I don't know where I should start. I have one field 'username' and it is unique in database so how can I try it?
My Code :
-> ENTITY
/**
* #var string $nick_allegro
*
* #ORM\Column(name="nick_allegro", type="string", length=255, unique=true, nullable=true)
*/
private $nick_allegro;
-> FORM
public function buildForm(FormBuilder $builder, array $options)
{
$builder
->add('nick_allegro')
;
}
public function getDefaultOptions(array $options) {
return array(
'data_class' => 'My\FrontendBundle\Entity\Licence',
);
}
-> Controller
/**
* Displays a form to create a new Licence entity.
*
* #Route("/new", name="licence_new")
* #Template()
*/
public function newAction()
{
$entity = new Licence();
$form = $this->createForm(new LicenceType(), $entity);
return array(
'entity' => $entity,
'form' => $form->createView()
);
}
/**
* Creates a new Licence entity.
*
* #Route("/create", name="licence_create")
* #Method("post")
* #Template("MyFrontendBundle:Licence:new.html.twig")
*/
public function createAction()
{
$entity = new Licence();
$request = $this->getRequest();
$form = $this->createForm(new LicenceType(), $entity);
$form->bindRequest($request);
if ($form->isValid()) {
$em = $this->getDoctrine()->getEntityManager();
$em->persist($entity);
$em->flush();
return $this->redirect($this->generateUrl('licence_show', array('id' => $entity->getId())));
}
return array(
'entity' => $entity,
'form' => $form->createView()
);
}
-> View
<form action="{{ path('licence_create') }}" method="post" {{
form_enctype(form) }}>
{{ form_widget(form) }}
<p>
<button type="submit">Create</button>
</p> </form>
You need to use Unique Entity in symfony to validate that a particular field in a model is unique.
To help you a little bit (if you have a field called nick):
1/ In your entity
use Symfony\Component\Validator\Constraints as Assert;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
/**
* #ORM\Entity
* #UniqueEntity("nick")
*/
class User
{
/**
* #var string $email
*
* #ORM\Column(name="nick", type="string", length=255, unique=true)
*/
private $nick;
Validation will directly take effect as you asserted the constraints in your entity..
Therefore, you can already check the validaiton in your controller.
2/ In your controller
if ( 'POST' === $request->getMethod()) {
$form->bind($request);
if ($form->isValid())
{
//do something if the form is valid
}
}
It's very simple. Enough add in file Entity #ORM\Column this "unique=true"
Example:
class User
{
/**
* #var string $email
*
* #ORM\Column(name="email", type="string", length=255, unique=true)
* #Assert\Email()
*/
protected $email;
}
Bear in mind that form handling in sf2.1 changed a little bit, so be sure to check the right documentation:
Forms in sf 2.1
Forms in sf 2.0
How to upgrade from 2.0 to 2.1 - Form chapter
Validation is done in many ways, among the others with annotations over entity fields, and in your case you need the UniqueEntity annotation.
Be sure to check all the symfony2 docs online because it's the best way to get into the matter.

Embedded form in Symfony2 and saving to database

In my Symfony2 application, there are two entities: Address and Town. The Address entity has a 4-digit postal code property ("pcNum"), which refers to the id of a Town entity. An address can only have one postal code and hence refer to one town only (however the reverse is possible: a town could have more postal codes).
For both entities I have created a form called TownType and AddressType. Users can enter and save a Town (this works fine). The form for the Address entity allows users to fill in an address, including a postal code. The postal code is linked to the id of a Town entity.
However, when I try to persist a new Address entity retrieved from the AddressType form, I receive the MySql error that the pc_num field (pcNum in the entity) is empty:
SQLSTATE[23000]: Integrity constraint violation: 1048 Column 'pc_num' cannot be null
(This is the field of the Address entity that should have contained a ref key to a Town.) Somehow its value is lost before persisting the Address entity. If I fill the field manually in my controller, regardless of the user input in the form, I can persist the new Address without the error. If I drop the link to the Town entity and use an unaware form field, I can also safe without error as long as the postal code happens to exist. But in the way it should be, using two forms and entity-association, I cannot get it to work. I have tried many different things over the past weeks and I am out of ideas. Below is my code. Feel free to comment on anything, maybe my whole approach is wrong.
This is my Town entity:
namespace Xx\xBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Check;
/**
*
* Xx\xBundle\Entity\Town
*
* #ORM\Table(name="lib_towns")
* #ORM\Entity
*/
class Town
{
#id
/**
* #ORM\Id
* #ORM\Column(type="integer", length="4", nullable=false)
* #ORM\GeneratedValue(strategy="AUTO")
* #Check\NotBlank()
* #Check\MaxLength(4)
*/
protected $id;
#name
/**
* #ORM\Column(name="name", type="string", length="100", nullable=true)
* #Check\NotBlank()
* #Check\MaxLength(100)
*/
protected $name;
//some more properties
#getters and setters
/**
* Set id
*
* #param integer $id
*/
public function setId($id)
{
$this->id = $id;
}
/**
* Get id
*
* #return integer $id
*/
public function getId()
{
return $this->id;
}
/**
* Set name
*
* #param string $name
*/
public function setName($name)
{
$this->name = $name;
}
/**
* Get name
*
* #return string $name
*/
public function getName()
{
return $this->name;
}
//some more getters ans setters
}
This is my Address entity:
namespace Xx\xBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Check;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use Xx\xBundle\Entity\Town;
/**
*
* Xx\xBundle\Entity\Address
*
* #ORM\Entity
* #ORM\Table(name="addresses")
*/
class Address
{
#id
/**
* #ORM\Id
* #ORM\Column(type="integer", length="6", nullable=false)
* #ORM\GeneratedValue(strategy="AUTO")
* #Check\NotBlank()
* #Check\MaxLength(6)
*/
protected $id;
#town
/**
* #orm\OneToOne(targetEntity="town")
* #ORM\JoinColumn(name="pc_num", referencedColumnName="id", nullable=false)
*/
protected $town;
//some other properties...
#getters and setters
/**
* Set id
*
* #param integer $id
*/
public function setId($id)
{
$this->id = $id;
}
/**
* Get id
*
* #return integer $id
*/
public function getId()
{
return $this->id;
}
/**
* Set town entity linked to this address
*
*/
public function setTown(town $town = null)
{
$this->town = $town;
}
/**
* Get town entity linked to this address
*
*/
public function getTown()
{
return $this->town;
}
//some other functions...
}
Next, this is the form I've created for the Address:
namespace Xx\xBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilder;
use Doctrine\ORM\EntityRepository;
class AddressType extends AbstractType
{
public function buildForm(Formbuilder $builder, array $options)
{
$builder->add('id', 'hidden'); //necessary for updates
$builder->add('town', 'entity', array
(
'label' => 'Postal code (4 digits):*',
'class' => 'XxxBundle:Town',
'query_builder' => function(EntityRepository $er) {
return $er->createQueryBuilder('t')
->orderBy('t.id', 'ASC'); },
//'empty_value' => '----',
'property'=> 'Id',
'property_path'=> false,
'expanded' => false,
'multiple' => false,
'required' => true
));
$builder->add('street', 'text', array
(
'label' => 'Street:*',
'required' => true
));
//some more fields...
}
public function getDefaultOptions(array $options)
{
return array(
'data_class' => 'Xx\xBundle\Entity\Address'
);
}
public function getName()
{
return 'address';
}
}
This is the relevant action function in my Address controller:
public function editAddressAction($id = null)
{
$em = $this->getDoctrine()->getEntityManager();
$address = new Address();
$isNew = is_null($id);
//this also tests positve after a form has been sent
if($isNew)
{
#fill address object with some defaults
$address->setCreated(new \DateTime("now"));
} else
{
#fill address object with existing db data
$address = $em->getRepository('XxxBundle:Address')->find($id);
}
#create form and fill it with address object data
$form = $this->createForm(new AddressType(), $address);
#if form sent: check input
$request = $this->get('request');
if ($request->getMethod() == 'POST')
{
$form->bindRequest($request); //calls setters
if ($form->isValid())
{
//if I leave the following lines in: no error (but also no sense)
//because the pcNum should be retrieved from the form
$pcNum = 2222;
$town = $em->getRepository('XxxBundle:Town')->find($pcNum);
$address->setTown($town);
//end
#persist and flush
$id_unknown = is_null($address->getId());
if($id_unknown)
{
#insert address
$em->persist($address);
} else
{
#update address
$address->setModified(new \DateTime("now"));
$em->merge($address);
}
#commit
$em->flush();
#get id of update or insert and redirect user
$address_id = $address->getId();
return $this->redirect($this->generateUrl('displayAddress', array('id'=>$address_id)));
}
}
return $this->render('XxxBundle:Forms:address.html.twig', array('form'=>$form->createView(), 'new' => $isNew, 'id' => $id));
}
To conclude, this is the relevant Twig snippet:
<form action="{{ path('addAddress') }}" method="post" {{ form_enctype(form) }}>
{{ form_errors(form) }}
<div class="form_row">
{{ form_errors(form.town) }}
{{ form_label(form.town, 'Postal code:*') }}
{{ form_widget(form.town, {'attr': { 'class': 'form_pcNum', 'maxlength': '4', 'size': '4' } } ) }}
</div>
<div class="form_row">
<!-- some more fields here -->
</div>
<div class="form_row">
<button name="btn_add" id="do_add" type="submit" class="" value="btn_add" title="Ok!">Ok</button>
</div>
{{ form_rest(form) }}
</form>
Any help is appreciated...
If you know of a relevant (working) example, that would also be great. Cheers!
I had similar problem with foreign key violation and I solved. Please read Symfony2 form and Doctrine2 - update foreign key in assigned entities fails [solved]