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]
Related
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
I'm using the Gedmo Doctrine Extensions to handle Categories as a nested set.
I'm building a REST API and I have a route to create a Category.
I want to be able to create root Categories or child Categories.
Here is the entity
<?php
namespace AppBundle\Entity;
use Gedmo\Mapping\Annotation as Gedmo;
use Doctrine\ORM\Mapping as ORM;
/**
* #Gedmo\Tree(type="nested")
* #ORM\Table(name="bo_categories")
* use repository for handy tree functions
* #ORM\Entity(repositoryClass="Gedmo\Tree\Entity\Repository\NestedTreeRepository")
*/
class Category
{
/**
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue
*/
private $id;
/**
* #ORM\Column(name="name", type="string", length=64)
*/
private $name;
/**
* #Gedmo\TreeLeft
* #ORM\Column(name="lft", type="integer")
*/
private $lft;
/**
* #Gedmo\TreeLevel
* #ORM\Column(name="lvl", type="integer")
*/
private $lvl;
/**
* #Gedmo\TreeRight
* #ORM\Column(name="rgt", type="integer")
*/
private $rgt;
/**
* #Gedmo\TreeRoot
* #ORM\Column(name="root", type="integer", nullable=true)
*/
private $root;
/**
* #Gedmo\TreeParent
* #ORM\ManyToOne(targetEntity="Category", inversedBy="children")
* #ORM\JoinColumn(name="parent_id", referencedColumnName="id", onDelete="CASCADE")
*/
private $parent;
/**
* #ORM\OneToMany(targetEntity="Category", mappedBy="parent")
* #ORM\OrderBy({"lft" = "ASC"})
*/
private $children;
public function getId()
{
return $this->id;
}
public function setName($name)
{
$this->name = $name;
}
public function getName()
{
return $this->name;
}
public function setParent(Category $parent = null)
{
$this->parent = $parent;
}
public function getParent()
{
return $this->parent;
}
}
Here is the form
<?php
namespace AppBundle\Form\Type\Classification;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class CategoryFormType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('name')
->add('parent', 'entity', array(
'class' => 'AppBundle:Category',
'property' => 'id',
))
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\Category',
'csrf_protection' => false,
));
}
public function getName()
{
return 'api_category';
}
}
And here is the controller
/**
* #Route("/create")
* #Security("has_role('ROLE_SUPER_ADMIN')")
* #Rest\View
*/
public function postCreateAction(Request $request)
{
$categoryManager = $this->get('app.manager.category');
$category = $categoryManager->createNew();
$form = $this->createForm(new CategoryFormType(), $category);
// $category->setParent(10);
$form->handleRequest($request);
if ($form->isValid()) {
$categoryManager->save($category);
return new Response('', 201);
} else {
return $this->view($form, 400);
}
}
If I want to create a child category, it works fine. But if I want to create a root category without removing the "parent" field in the form I get this error
An exception occurred while executing 'SELECT b0_.id AS id0, b0_.name AS name1, b0_.lft AS lft2, b0_.lvl AS lvl3, b0_.rgt AS rgt4, b0_.root AS root5, b0_.parent_id AS parent_id6 FROM bo_categories b0_ WHERE b0_.id IN (?)' with params [""]:\n\nSQLSTATE[22P02]: Invalid text representation: 7 ERROR: invalid input syntax for integer: "",
"class": "Doctrine\DBAL\DBALException
Why do I get this error ? Can't the "parent" value be empty/null in the form ?
There is absolutely no problem parent being NULL. Your current mapping on the other hand, doesn't allow that.
First, you should modify your mapping for $parent
#ORM\JoinColumn(name="parent_id", referencedColumnName="id", onDelete="CASCADE")
should allow null values:
#ORM\JoinColumn(name="parent_id", referencedColumnName="id", onDelete="CASCADE", nullable=true)
Don't forget to run app/console doctrine:schema:update --force when you change your mappings!
Next, your form looks okay, but it miss one property - empty_value. This won't force you to choose parent category.
->add('parent', 'entity', array(
'class' => 'AppBundle:Category',
'property' => 'id',
))
should be like this:
->add('parent', 'entity', array(
'class' => 'AppBundle:Category',
'property' => 'id',
'empty_value' => '-- Select parent --'
))
This should add extra option with no value and should be selected by default (when creating new category). Since your field is of type entity you will see all of your categories as well (when you need to select parent).
This should do the trick, give it a try.
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.
I have user model + user type, register model + register type... When i Execute it id doesn't validate user (inner data) model. Code below...
User model:
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Component\Security\Core\User\UserInterface;
use Serializable;
/**
* User
*
* #ORM\Table(name="user")
* #ORM\Entity(repositoryClass="Site\MainBundle\Entity\Repository\UserRepository")
*/
class User implements UserInterface, Serializable
{
/**
* #return array|\Symfony\Component\Security\Core\User\Role[]
*/
public function getRoles()
{
return array('ROLE_USER');
}
/**
*
*/
public function eraseCredentials()
{
}
/**
* #return string
*/
public function serialize()
{
return serialize(array($this->id));
}
/**
* #param string $serialized
*/
public function unserialize($serialized)
{
list ($this->id,) = unserialize($serialized);
}
/**
*
*/
public function __construct()
{
$this->isActive = false;
$this->salt = md5(uniqid(null, true));
}
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
public function getID()
{
return $this->id;
}
/**
* #var string
*
* #ORM\Column(name="username", type="string", length=255, nullable=false)
* #Assert\NotBlank(message="User name cannot be blank.")
*/
private $username;
public function setUserName($userName)
{
$this->username = $userName;
return $this;
}
public function getUserName()
{
return $this->username;
}
/**
* #var string
*
* #ORM\Column(name="password", type="string", length=255, nullable=false)
* #Assert\NotBlank(message="Password cannot be blank.")
*/
private $password;
public function setPassword($password)
{
$this->password = $password;
return $this;
}
public function getPassword()
{
return $this->password;
}
/**
* #var string
*
* #ORM\Column(name="salt", type="string", length=255, nullable=false)
*/
private $salt;
public function setSalt($salt)
{
$this->salt = $salt;
return $this;
}
public function getSalt()
{
return $this->salt;
}
/**
* #var string
*
* #ORM\Column(name="email", type="string", length=255, nullable=false)
* #Assert\NotBlank(message="E-Mail cannot be blank.")
* #Assert\Email(message="Invalid email address.")
*/
private $email;
public function setEmail($email)
{
$this->email = $email;
return $this;
}
public function getEmail()
{
return $this->email;
}
/**
* #var bool
*
* #ORM\Column(name="isActive", type="boolean", nullable=false)
*/
private $isActive;
public function setIsActive($isActive)
{
$this->isActive = $isActive;
return $this;
}
public function getIsActive()
{
return $this->isActive;
}
}
User Type:
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class UserType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('username', 'text', array('label' => 'User name'))
->add(
'password',
'repeated',
array(
'label' => 'Password',
'first_name' => 'password',
'second_name' => 'confirm',
'type' => 'password'
)
)
->add('email', 'text', array('label' => 'E-Mail'));
}
/**
* #param OptionsResolverInterface $resolver
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(
array(
'data_class' => 'Site\MainBundle\Entity\User',
'required' => false
)
);
}
/**
* #return string
*/
public function getName()
{
return 'main_user';
}
}
Register model:
use Symfony\Component\Validator\Constraints as Assert;
use Site\MainBundle\Entity\User;
/**
* Register
*/
class Register
{
/**
* #var Site\MainBundle\Entity\User
*
* #Assert\Type(type="Site\MainBundle\Entity\User")
* #Assert\Valid()
*/
protected $user;
public function setUser(User $user)
{
$this->user = $user;
return $this;
}
public function getUser()
{
return $this->user;
}
/**
* #var boolean
*
* #Assert\NotBlank(message="No terms accepted.")
* #Assert\True(message="You have to accept terms to be registered.")
*/
protected $terms;
public function setTerms($terms)
{
$this->terms = (Boolean)$terms;
return $this;
}
public function getTerms()
{
return $this->terms;
}
}
Register Type:
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Site\MainBundle\Form\UserType;
class RegisterType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('user', new UserType())
->add('terms', 'checkbox');
}
/**
* #param OptionsResolverInterface $resolver
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(
array(
'data_class' => 'Site\MainBundle\Entity\Register',
'required' => false
)
);
}
/**
* #return string
*/
public function getName()
{
return 'main_register';
}
}
And controller with twig. Part of twig:
{% block page_content %}
<form method="post" action="{{ path('main_register') }}">
{{ form_errors(formRegister.user.username) }}
{{ form_row(formRegister.user.username) }}
{{ form_errors(formRegister.user.password.password) }}
{{ form_row(formRegister.user.password.password) }}
{{ form_errors(formRegister.user.password.confirm) }}
{{ form_row(formRegister.user.password.confirm) }}
{{ form_errors(formRegister.user.email) }}
{{ form_row(formRegister.user.email) }}
{{ form_errors(formRegister.terms) }}
{{ form_row(formRegister.terms) }}
<button type="submit">Register</button>
</form>
{% endblock %}
Register Controller:
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Site\MainBundle\Form\RegisterType;
use Site\MainBundle\Entity\Register;
/**
* RegisterController
*/
class RegisterController extends Controller
{
public function defaultAction()
{
$formRegister = $this->createForm(new RegisterType(), new Register());
if ($this->getRequest()->isMethod('post')) {
$formRegister->handleRequest($this->getRequest());
if ($formRegister->isValid()) {
echo 'form is valid';
}
}
$twig = 'SiteMainBundle:register:default.html.twig';
$data = array(
'formRegister' => $formRegister->createView()
);
return $this->render($twig, $data);
}
}
Where can be problem? Help please with it.
Updated
echo "<pre>";
echo $formRegister->getErrorsAsString();
echo '</pre>';
says:
user:
username:
No errors
password:
password:
No errors
confirm:
No errors
email:
No errors
terms:
ERROR: No terms accepted.
ERROR: You have to accept terms to be registered.
As you can see model User is not validated AT ALL... =\
Found, finally.
Bug is in this part. I wanted to disable this way browser validation...
'required' => false
BUT. This "guys" (I mean developers),compare two things in one setting. So if we set 'required' to false our fields won't be validated. And if true it will be validate BUT ALSO WITH browser validator.. =\ Maybe someone know how to avoid it (browser validation) NOT by editing twig file (some options or other setting)?
PS: To avoid browser i changed twig.
<form method="post" action="{{ path('main_register') }}" novalidate>
If you wanna do it by code, in your Controller, your code should be like:
class RegisterController extends Controller
{
public function defaultAction()
{
$formRegister = $this->createForm(new RegisterType(), new Register(), array(
'attr'=> array('novalidate'=>'novalidate'),
));
With the therd parámeter of "createForm", you can modified the form (for example: acction, class, etc).
I normaly use:
$form = $this->createForm(new RegistroType(), $usuario, array(
'action' => $this->generateUrl('jgo_registro'),
'method' => 'POST',
'attr'=> array('novalidate'=>'novalidate'),
));
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.