Symfony 4 form: three dynamic select box - forms

Hello I am using Symfony 4.
I have managed to link up to two select box with form events, but I need to have three dynamic select box.
This is the relation between my entities:
Country -> Province -> City.
These are linked to a Person entity like this
When I add a new person I should be able to select a Country and have the Province dropdown updated in accordance to Country selection; same thing for the City dropdown after I have selected a Province.
I have made things working for Country and Province following the official Symfony guide here
https://symfony.com/doc/current/form/dynamic_form_modification.html#dynamic-generation-for-submitted-forms
How should I manage adding the third dropdown?
This is my Country entity:
<?php
namespace App\Entity\Geo;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
/**
* #ORM\Entity(repositoryClass="App\Repository\Geo\CountryRepository")
*/
class Country
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=255)
* #Assert\NotBlank()
*/
private $name;
/**
* #ORM\OneToMany(targetEntity="App\Entity\Geo\Province", mappedBy="country")
* #ORM\JoinColumn(nullable=false)
*/
private $provinces;
/**
* #ORM\OneToMany(targetEntity="App\Entity\Geo\City", mappedBy="country")
* #ORM\JoinColumn(nullable=false)
*/
private $cities;
/**
* #ORM\OneToMany(targetEntity="App\Entity\Geo\Person", mappedBy="country")
* #ORM\JoinColumn(nullable=false)
*/
private $persons;
/**
* #return mixed
*/
public function getId()
{
return $this->id;
}
/**
* #return mixed
*/
public function getName()
{
return $this->name;
}
/**
* #param mixed $name
*/
public function setName($name): void
{
$this->name = $name;
}
/**
* #return mixed
*/
public function getProvinces()
{
return $this->provinces;
}
/**
* #param mixed $provinces
*/
public function setProvinces($provinces): void
{
$this->provinces = $provinces;
}
/**
* #return mixed
*/
public function getCities()
{
return $this->cities;
}
/**
* #param mixed $cities
*/
public function setCities($cities): void
{
$this->cities = $cities;
}
/**
* #return mixed
*/
public function getPersons()
{
return $this->persons;
}
/**
* #param mixed $persons
*/
public function setPersons($persons): void
{
$this->persons = $persons;
}
}
This is my Province entity:
<?php
namespace App\Entity\Geo;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
/**
* #ORM\Entity(repositoryClass="App\Repository\Geo\ProvinceRepository")
*/
class Province
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=255)
* #Assert\NotBlank()
*/
private $name;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Geo\Country", inversedBy="provinces")
* #ORM\JoinColumn(nullable=false)
*/
private $country;
/**
* #ORM\OneToMany(targetEntity="App\Entity\Geo\City", mappedBy="province")
* #ORM\JoinColumn(nullable=false)
*/
private $cities;
/**
* #ORM\OneToMany(targetEntity="App\Entity\Geo\Person", mappedBy="province")
* #ORM\JoinColumn(nullable=false)
*/
private $persons;
public function __toString() {
return $this->name;
}
/**
* #return mixed
*/
public function getId()
{
return $this->id;
}
/**
* #return mixed
*/
public function getName()
{
return $this->name;
}
/**
* #param mixed $name
*/
public function setName($name): void
{
$this->name = $name;
}
/**
* #return mixed
*/
public function getCountry()
{
return $this->country;
}
/**
* #param mixed $country
*/
public function setCountry($country): void
{
$this->country = $country;
}
/**
* #return mixed
*/
public function getCities()
{
return $this->cities;
}
/**
* #param mixed $cities
*/
public function setCities($cities): void
{
$this->cities = $cities;
}
/**
* #return mixed
*/
public function getPersons()
{
return $this->persons;
}
/**
* #param mixed $persons
*/
public function setPersons($persons): void
{
$this->persons = $persons;
}
}
This is my City entity:
<?php
namespace App\Entity\Geo;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
/**
* #ORM\Entity(repositoryClass="App\Repository\Geo\CityRepository")
*/
class City
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=255)
* #Assert\NotBlank()
*/
private $name;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Geo\Province", inversedBy="cities")
* #ORM\JoinColumn(nullable=false)
*/
private $province;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Geo\Country", inversedBy="cities")
* #ORM\JoinColumn(nullable=false)
*/
private $country;
/**
* #ORM\OneToMany(targetEntity="App\Entity\Geo\Person", mappedBy="city")
* #ORM\JoinColumn(nullable=false)
*/
private $persons;
/**
* #return mixed
*/
public function getId()
{
return $this->id;
}
/**
* #return mixed
*/
public function getName()
{
return $this->name;
}
/**
* #param mixed $name
*/
public function setName($name): void
{
$this->name = $name;
}
/**
* #return mixed
*/
public function getProvince()
{
return $this->province;
}
/**
* #param mixed $province
*/
public function setProvince($province): void
{
$this->province = $province;
}
/**
* #return mixed
*/
public function getCountry()
{
return $this->country;
}
/**
* #param mixed $country
*/
public function setCountry($country): void
{
$this->country = $country;
}
/**
* #return mixed
*/
public function getPersons()
{
return $this->persons;
}
/**
* #param mixed $persons
*/
public function setPersons($persons): void
{
$this->persons = $persons;
}
}
This is my form to add a Person (PersonType.php)
<?php
namespace App\Form\Geo;
use App\Entity\Geo\Person;
use App\Entity\Geo\Country;
use App\Entity\Geo\Province;
use App\Entity\Geo\City;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
class PersonType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name', TextType::class, ['label' => "Name"])
->add('country', EntityType::class, [
'class' => Country::class,
'choice_label' => function(Country $country) {
return $country->getName();
},
'placeholder' => 'Choose a Country'
])
;
$formModifier = function (FormInterface $form, Country $country = null) {
$provinces = null === $country ? [] : $country->getProvinces();
$form->add('province', EntityType::class, [
'class' => Province::class,
'placeholder' => 'Choose a Province',
'choices' => $provinces,
]);
};
$builder->addEventListener(
FormEvents::PRE_SET_DATA,
function (FormEvent $event) use ($formModifier) {
$data = $event->getData();
$formModifier($event->getForm(), $data->getCountry());
}
);
$builder->get('country')->addEventListener(
FormEvents::POST_SUBMIT,
function (FormEvent $event) use ($formModifier) {
$country = $event->getForm()->getData();
$formModifier($event->getForm()->getParent(), $country);
}
);
$builder->add( 'save', SubmitType::class);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' =>Person::class
]);
}
}
This is my twig template (person-add.html.twig)
{% extends 'base.html.twig' %}
{% block title %}Add Person{% endblock %}
{% block body %}
{{ form_start(form) }}
{{ form_row(form.name) }}
{{ form_row(form.country) }}
{{ form_row(form.province) }}
{{ form_end(form) }}
<script>
$(document).ready(function() {
var $country = $('#person_country');
// When sport gets selected ...
$country.change(function () {
// ... retrieve the corresponding form.
var $form = $(this).closest('form');
// Simulate form data, but only include the selected sport value.
var data = {};
data[$country.attr('name')] = $country.val();
// Submit data via AJAX to the form's action path.
$.ajax({
url: $form.attr('action'),
type: $form.attr('method'),
data: data,
success: function (html) {
// Replace current position field ...
$('#person_province').replaceWith(
// ... with the returned one from the AJAX response.
$(html).find('#person_province')
);
// Position field now displays the appropriate positions.
}
});
})
});
</script>
{% endblock %}
Thanks to this post I have managed to change my PersonType.php form file like this:
<?php
namespace App\Form\Geo;
use App\Entity\Geo\Person;
use App\Entity\Geo\Country;
use App\Entity\Geo\Province;
use App\Entity\Geo\City;
use App\Repository\Geo\CityRepository;
use App\Repository\Geo\ProvinceRepository;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
class PersonType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name', TextType::class)
//
->add('country', EntityType::class, [
'class' => Country::class,
'label' => 'Country',
'required' => true,
'choice_label' => function(Country $country) {
return $country->getName();
},
'invalid_message' => 'You must select a Country',
'placeholder' => 'Select Country',
]);
//**************** Start Province Form
$addProvinceForm = function (FormInterface $form, $country_id) {
// it would be easier to use a Park entity here,
// but it's not trivial to get it in the PRE_SUBMIT events
$form->add('province', EntityType::class, [
'class' => Province::class,
'label' => 'Province',
'required' => true,
'invalid_message' => 'Choose a Province',
'placeholder' => null === $country_id ? 'Choose a Country first' : 'Select Province',
'query_builder' => function (ProvinceRepository $repository) use ($country_id) {
return $repository->createQueryBuilder('p')
->innerJoin('p.country', 'c')
->where('c.id = :country')
->setParameter('country', $country_id)
;
}
]);
};
$builder->addEventListener(
FormEvents::PRE_SET_DATA,
function (FormEvent $event) use ($addProvinceForm) {
$country = $event->getData()->getCountry();
$country_id = $country ? $country->getId() : null;
$addProvinceForm($event->getForm(), $country_id);
}
);
$builder->addEventListener(
FormEvents::PRE_SUBMIT,
function (FormEvent $event) use ($addProvinceForm) {
$data = $event->getData();
$country_id = array_key_exists('country', $data) ? $data['country'] : null;
$addProvinceForm($event->getForm(), $country_id);
}
);
//**************** End Province Form
//**************** Start City Form
$addCityForm = function (FormInterface $form, $province_id) {
$form->add('city', EntityType::class, [
'class' => City::class,
'label' => 'City',
'required' => true,
'invalid_message' => 'You must choose a City',
'placeholder' => null === $province_id ? 'Choose a Province first' : 'Choose a City',
'query_builder' => function (CityRepository $repository) use ($province_id) {
return $repository->createQueryBuilder('ci')
->innerJoin('ci.province', 'pr')
->where('pr.id = :province')
->setParameter('province', $province_id)
;
}
]);
};
$builder->addEventListener(
FormEvents::PRE_SET_DATA,
function (FormEvent $event) use ($addCityForm) {
$province = $event->getData()->getProvince();
$province_id = $province ? $province->getId() : null;
$addCityForm($event->getForm(), $province_id);
}
);
$builder->addEventListener(
FormEvents::PRE_SUBMIT,
function (FormEvent $event) use ($addCityForm) {
$data = $event->getData();
$province_id = array_key_exists('province', $data) ? $data['province'] : null;
$addCityForm($event->getForm(), $province_id);
}
);
//**************** End City Form
$builder->add( 'save', SubmitType::class);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' =>Person::class
]);
}
}
The Province dropdown works as expected when you first select a Country.
The problem is the City dropdown: nothing changes after you select a Province.
If everything is ok with the query executed inside the PersonType.php file, I think I am doing something wrong with the javascript. Here's my code:
<script>
$(document).ready(function() {
var $country = $('#person_country');
var $province = $('#person_province');
// When country gets selected ...
$country.change(function () {
// ... retrieve the corresponding form.
var $form = $(this).closest('form');
// Simulate form data, but only include the selected country value.
var data = {};
data[$country.attr('name')] = $country.val();
// Submit data via AJAX to the form's action path.
$.ajax({
url: $form.attr('action'),
type: $form.attr('method'),
data: data,
success: function (html) {
// Replace current province field ...
$('#person_province').replaceWith(
// ... with the returned one from the AJAX response.
$(html).find('#person_province')
);
}
});
});
// When province gets selected ...
$province.change( function () {
// ... retrieve the corresponding form.
var $form = $(this).closest('form');
// Simulate form data, but only include the selected province value.
var data = {};
data[$province.attr('name')] = $province.val();
// Submit data via AJAX to the form's action path.
$.ajax({
url: $form.attr('action'),
type: $form.attr('method'),
data: data,
success: function (html) {
// Replace current city field ...
$('#person_city').replaceWith(
// ... with the returned one from the AJAX response.
$(html).find('#person_city')
);
}
});
});
});
</script>

Related

Symfony Form EntityType

I'am building a simple product page. Some products could have some extra options (productOptions). These options could be selected due to selectboxes. But the problem is as follows:
I've the following Entities:
Product
#src/appbundle/entity
<?php
/**
* Product
*/
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
/**
* #ORM\Entity(repositoryClass="AppBundle\Repository\ProductRepository")
* #ORM\Table(name="product")
*/
class Product {
/**
* #var string id
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
* #ORM\Column(type="integer")
*/
protected $id;
/**
* #var string title
* #ORM\Column(type="string")
*/
protected $title;
/**
* #var ArrayCollection|ProductOption[]
* #ORM\OneToMany(targetEntity="AppBundle\Entity\ProductOption", mappedBy="product", fetch="EAGER")
*/
protected $productOptions;
protected $tempProductOptions;
/**
* #return string
*/
public function getId() {
return $this->id;
}
/**
* #return string
*/
public function getTitle() {
return $this->title;
}
/**
* #param string $title
*/
public function setTitle($title) {
$this->title = $title;
}
public function __construct() {
$this->productOptions = new ArrayCollection();
}
public function addProductOption(ProductOption $productOption){
if($this->productOptions->contains($productOption)){
return;
}
$productOption->setProduct($this);
$this->productOptions->add($productOption);
}
public function removeProductOption(ProductOption $productOption){
if(!$this->productOptions->contains($productOption)){
return;
}
$productOption->setProduct(null);
$this->productOptions->removeElement($productOption);
}
/**
* #return ProductOption[]|ArrayCollection
*/
public function getProductOptions() {
return $this->productOptions->toArray();
}
}
ProductOption
#src/appbundle/entity
<?php
/**
* ProductOption
*/
namespace AppBundle\Entity;
use AppBundle\Repository\GenusRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Criteria;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
use Gedmo\Mapping\Annotation as Gedmo;
/**
* #ORM\Entity(repositoryClass="AppBundle\Repository\ProductOptionRepository")
* #ORM\Table(name="product_option")
*/
class ProductOption {
/**
* #var int id
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
* #ORM\Column(type="integer")
*/
protected $id;
/**
* #var string title
* #ORM\Column(type="string")
*/
protected $title;
/**
* #var float price
* #ORM\Column(type="float", scale=2)
*/
protected $price;
/**
* ProductOption connected to specific product
* #ORM\ManyToOne(targetEntity="Product", inversedBy="productOptions")
* #ORM\JoinColumn(nullable=false)
*/
protected $product;
/**
* #return int
*/
public function getId() {
return $this->id;
}
/**
* #param int $id
*/
public function setId($id) {
$this->id = $id;
}
/**
* #return string
*/
public function getTitle() {
return $this->title;
}
/**
* #param string $title
*/
public function setTitle($title) {
$this->title = $title;
}
/**
* #return float
*/
public function getPrice() {
return $this->price;
}
/**
* #param float $price
*/
public function setPrice($price) {
$this->price = $price;
}
/**
* #return Product
*/
public function getProduct() {
return $this->product;
}
/**
* #param Product $product
*/
public function setProduct($product) {
$this->product = $product;
}
public function __toString() {
return (string)$this->getTitle() . '(€'.$this->getPrice().')';
}
}
This is my ProductType (formbuilder)
#src/appbundle/form
<?php
namespace AppBundle\Form;
use AppBundle\Entity\Product;
use AppBundle\Entity\ProductOption;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;
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;
class ProductType extends AbstractType {
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder->add('title', TextType::class, [
'label' => 'titel'
]);
$builder->add('tempProductOptions', EntityType::class, [
'class' => ProductOption::class,
'multiple' => true,
'expanded' => true,
'mapped' => false,
'choice_attr' => function($productOption, $key, $index) {
/** #var ProductOption $productOption */
return [
'data-price' => $productOption->getPrice(),
];
},
]);
$builder->add('save', SubmitType::class, array(
'label' => 'Opslaan'
));
}
public function configureOptions(OptionsResolver $resolver) {
$resolver->setDefaults(array(
'data_class' => Product::class,
));
}
public function getBlockPrefix() {
return 'product';
}
}
Twig file
its a very basic example
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<h1>test product page</h1>
{{ dump(form) }}
{{ form_start(form) }}
{{ form_row(form.titel) }}
{{ form_row(form.productOptions) }}
{{ form_row(form.save) }}
{{ form_end(form) }}
</body>
</html>
For exmaple product 1 has two options:
option 1 (50 euro)
option 2 (60 euro)
In the database option 1 and 2 are connected to product 1
table product_option
ID title price product_id
1 option 1 50 1
2 option 2 60 1
But in the view the options are already checked (because they connected through db). Is it possible to uncheck the checkboxes.
Short version. Each product could have extra options. These options are stored in db. When creating the form on product page the user could choose for these options, but they are already checked.
Also added the following to my formbuilder
public function finishView(FormView $view, FormInterface $form, array $options) {
foreach ($view->children['productOptions']->children as $productOption) {
$productOption->vars['checked'] = false;
}
}
but the checkboxes are still being checked
I've found an solution:
public function buildForm(FormBuilderInterface $builder, array $options) {
// arraycollection with productOptions
$productOptions = $options['data']->getProductOptions();
$builder->add('id', HiddenType::class, ['mapped' => false]);
$builder->add('productOptions', ChoiceType::class, [
'choices' => $productOptions,
'expanded' => true,
'multiple' => true,
'mapped' => false,
'choice_value' => function (ProductOption $productOption) {
return $productOption->getId();
},
'choice_label' => function(ProductOption $productOption, $key, $index) {
return $productOption->__toString();
},
'choice_attr' => function(ProductOption $productOption, $key, $index) {
return [
'data-prijs' => $productOption->getPrijs(),
];
},
]);
// rest here ...

Symfony Entity Field Type where clause in Query Builder not working on One-To-Many self referencing association

I have an Entity called "InterestGroup" that has a self referencing association (adjacency list) in the form of properties "children" (one to many) and "parent" (many to one).
In setting up a form type for InterestGroup, I'm attempting to provide a select list for the property parent, that has choices of all 'top level' interest groups (those whose 'parent' property is null). When I add the the where clause and null parameter to the query_builder for the EntityType field, it always returns nothing, even when I have several top level (parent is null) interest groups persisted. If I remove the where clause it will return all InterestGroups in the table. I'm having a difficult time understanding why the where clause is not working.
This is the field in question:
->add('parent',EntityType::class,
array(
'placeholder' => 'Top Level (No Parent)',
'required' => false,
'class' => 'Common\ContentBundle\Entity\InterestGroup',
'query_builder' => function (EntityRepository $er) {
return $er->createQueryBuilder('ig')
->where('ig.parent = :n')
->setParameter('n',null)
->orderBy('ig.title', 'ASC');
},
'choice_label' => 'title'
)
)
The above will return an empty select menu. By removing the where clause and the setparameter, I get all the InterestGroup Entities including all those that have null parents.
Following is the Entity Class for InterestGroup
<?php
namespace Common\ContentBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
use Symfony\Component\Validator\Constraints as Assert;
use Gedmo\Mapping\Annotation as Gedmo;
/**
* InterestGroup
*
* #ORM\Table(name="interest_group")
* #ORM\Entity(repositoryClass="Common\ContentBundle\Repository\InterestGroupRepository")
*/
class InterestGroup
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="title", type="string", length=255, unique=true)
* #Assert\NotBlank(message="This is a required field.")
*/
private $title;
/**
* #ORM\OneToMany(targetEntity="InterestGroup", mappedBy="parent")
*/
private $children;
/**
* #ORM\ManyToOne(targetEntity="InterestGroup", inversedBy="children")
* #ORM\JoinColumn(name="parent_id", referencedColumnName="id")
*/
private $parent;
/**
* #Gedmo\Slug(fields={"title"})
* #ORM\Column(length=128, unique=true)
*/
private $slug;
// ...
/**
* #ORM\ManyToMany(targetEntity="Product", mappedBy="interestGroups")
*/
private $products;
/**
* InterestGroup constructor.
*/
public function __construct()
{
$this->children = new ArrayCollection();
$this->products = new ArrayCollection();
}
/**
* Get id
*
* #return int
*/
public function getId()
{
return $this->id;
}
/**
* #return string
*/
public function getTitle()
{
return $this->title;
}
/**
* #param string $title
*/
public function setTitle($title)
{
$this->title = $title;
}
/**
* #return mixed
*/
public function getSlug()
{
return $this->slug;
}
/**
* #return mixed
*/
public function getChildren()
{
return $this->children;
}
/**
* #param mixed $children
*/
public function setChildren($children)
{
$this->children = $children;
}
/**
* #return mixed
*/
public function getParent()
{
return $this->parent;
}
/**
* #param mixed $parent
*/
public function setParent($parent)
{
$this->parent = $parent;
}
/**
* #return mixed
*/
public function getProducts()
{
return $this->products;
}
}
And the Form Type Class:
<?php
namespace Common\ContentBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Doctrine\ORM\EntityRepository;
class InterestGroupType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('title',TextType::class)
->add('parent',EntityType::class,
array(
'placeholder' => 'Top Level (No Parent)',
'required' => false,
'class' => 'Common\ContentBundle\Entity\InterestGroup',
'query_builder' => function (EntityRepository $er) {
return $er->createQueryBuilder('ig')
->where('ig.parent = :n')
->setParameter('n',null)
->orderBy('ig.title', 'ASC');
},
'choice_label' => 'title'
)
)
;
}
/**
* #param OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Common\ContentBundle\Entity\InterestGroup'
));
}
}
Thanks in advance!
Check you code here:
return $er->createQueryBuilder('ig')
->where('ig.parent = :n') // <---
->setParameter('n',null) // <---
->orderBy('ig.title', 'ASC');
It is the same to:
... WHERE ig.parent = NULL ...
So, this query always returns null dataset.
Right code:
return $er->createQueryBuilder('ig')
->where('ig.parent IS NULL')
->orderBy('ig.title', 'ASC');
Use IS NULL for checking null values.
This problem is related to what is "=null" and " IS NULL"

form whith createForm doesn't work and i can't get assert message

I already have to ask a question about a similar assert problem with FosUserBundle configuation.
But now I'm trying to run a simple form, but even with a esiest form => nothing is happening.
when i click on submit :
1) isValid() stay to false
2) No Assert message appears when input name is blank/empty
UserTmp.php (entity)
<?php
namespace BISSAP\UserBundle\Entity;
use FOS\UserBundle\Model\User as BaseUser;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
/**
* BISSAP\UserBundle\Entity\User
*
* #ORM\Table()
* #ORM\Entity(repositoryClass="BISSAP\UserBundle\Entity\UserRepository")
*/
class Usertmp
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\Column(name="name", type="string", length=255)
*
* #Assert\NotBlank(message="Please enter your name.", groups={"Registration", "Profile"})
* #Assert\Length(
* min=3,
* max=255,
* minMessage="The name is too short.",
* maxMessage="The name is too long.",
* groups={"Registration", "Profile"}
* )
*/
private $name;
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set name
*
* #param string $name
* #return Usertmp
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Get name
*
* #return string
*/
public function getName()
{
return $this->name;
}
}
UserType.php
<?php
namespace BISSAP\ForumBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class UserType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name', 'text', array('required' => false))
->add('Envoyer', 'submit', array(
'attr' => array(
'class' => 'btn right-flt'
)));
}
/**
* #param OptionsResolverInterface $resolver
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'BISSAP\UserBundle\Entity\Usertmp'
));
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array( 'data_class' => $this->class, 'intention' => 'Registration', ));
}
/**
* #return string
*/
public function getName()
{
return 'bissap_forumbundle_user';
}
}
TController.php
<?php
namespace BISSAP\ForumBundle\Controller;
use Doctrine\Common\DataFixtures\FixtureInterface;
use Doctrine\Common\Persistence\ObjectManager;
use BISSAP\ForumBundle\Form\UserType;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use BISSAP\UserBundle\Entity\Usertmp;
class TController extends Controller
{
public function indexAction()
{
$entity = new Usertmp();
$form = $this->createForm(new UserType(), $entity);
if ($form->isValid())
{
return $this->redirectToRoute('bissap_forum_index');
}
return $this->render('BISSAPForumBundle:T:index.html.twig', array('form'=> $form->createView(), 'errors_tmp' => $this->getErrorMessages($form)));
}
private function getErrorMessages(\Symfony\Component\Form\Form $form)
{
$errors = array();
foreach ($form->getErrors(true, false) as $error) {
// My personnal need was to get translatable messages
// $errors[] = $this->trans($error->current()->getMessage());
$errors[] = $error->current()->getMessage();
}
return $errors;
}
}
?>
index.html.twig
----> {{form( form )}}
{% for error in errors_tmp %}
<div>error : {{ error }}</div>
{% endfor %}
So, form didn't work cause : $form->handleRequest($request); missed in TController.php
In your UserType.php, try:
$resolver->setDefaults(array( 'data_class' => $this->class, 'intention' => 'Registration', 'validation_groups' => array('registration'),));
You can also set the validation group in your TController.php instead of hard-coding it on your UserType:
$form = $this->createForm(new UserType(), $entity, array('validation_groups' => 'registration'));
Source: Validation Groups
Also in your UserType.php you are saying that the name is not required, but at the same time you want to assert if it is not blank:
->add('name', 'text', array('required' => false))
Try removing that option too.

Symfony2 (2.7) Form Entity Data Transformer

I'm trying to customize a selection list's text while using the entity's ID. This is because I want the list options to be specific to the authenticated user. The database text values are Full Name, By City and State, and Anonymous, but I want it to actually display the user's full name (John Smith), User in Denver, CO, and Anonymous. I'm attempting to use a view data transformer to achieve this, but with no luck. I'd rather not use Javascript to achieve this if possible.
Here's my main form type:
<?php
namespace Members\MessagesBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Security\Core\SecurityContext;
class MessageType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('viewability', 'viewability_entity', array(
'class' => 'MessagesBundle:Viewability',
'property' => 'name',
'required' => true,
))
->add('body', new MessageBodyType())
;
}
/**
* #param OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Members\MessagesBundle\Entity\Message',
));
}
/**
* #return string
*/
public function getName()
{
return 'members_messages_message';
}
}
Here's my custom form type for Viewability (the entity which I would like to transform):
<?php
namespace Members\MessagesBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Security\Core\SecurityContext;
use Members\MessagesBundle\Form\DataTransformer\MessageNameTransformer;
class ViewabilityType extends AbstractType
{
private $context;
/**
* #param SecurityContext $context
*/
public function __construct(SecurityContext $context)
{
$this->context = $context;
}
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$transformer = new MessageNameTransformer($this->context);
$builder->addViewTransformer($transformer);
}
/**
* #param OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'invalid_message' => 'The selected issue does not exist',
));
}
/**
* #return string
*/
public function getParent()
{
return 'entity';
}
/**
* #return string
*/
public function getName()
{
return 'viewability_entity';
}
}
Here's my service which defines the Viewability Type:
members.messages.form.type.viewability_entity:
class: Members\MessagesBundle\Form\ViewabilityType
tags:
- { name: form.type, alias: viewability_entity }
arguments: [#security.context]
Here's my Viewability Entity:
<?php
namespace Members\MessagesBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity()
*/
class Viewability
{
/**
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\Column(type="string", length=255)
*/
private $name;
public function __construct()
{
}
/**
* #return mixed
*/
public function getName()
{
return $this->name;
}
/**
* #param mixed $name
*/
public function setName($name)
{
$this->name = $name;
}
/**
* #return mixed
*/
public function getId()
{
return $this->id;
}
/**
* #param mixed $id
*/
public function setId($id)
{
$this->id = $id;
}
}
Finally, here's my data transformer:
<?php
namespace Members\MessagesBundle\Form\DataTransformer;
use Symfony\Component\Form\DataTransformerInterface;
use Members\MessagesBundle\Entity\Viewability;
use Symfony\Component\Security\Core\SecurityContext;
class MessageNameTransformer implements DataTransformerInterface
{
private $user;
/**
* #param SecurityContext $context
*/
public function __construct(SecurityContext $context)
{
$this->user = $context->getToken()->getUser();
}
/**
* #param Viewability|null $viewability
* #return string
*/
public function transform($viewability)
{
if (null === $viewability) {
return '';
}
if($viewability === 'Full Name')
return sprintf('%s %s', $this->user->getInfo()->getFirstName(), $this->user->getInfo()->getLastName());
if($viewability === 2)
return sprintf('Lawyer in %s, %s', $this->user->getInfo()->getAddress()->getCity(), $this->user->getInfo()->getAddress()->getState());
if($viewability === 3)
return 'Anonymous';
}
/**
* #param Viewability $viewability
* #return Viewability
*/
public function reverseTransform($viewability)
{
return $viewability;
}
}
The data passed into transform() always seems to be null or "" (empty string).
Thanks for any help.
So I ended up taking a different approach to solving this. Originally I was trying to transform data coming from an entity. Fortunately this entity didn't really need to be a database entity after all and a simple choice type sufficed. This doesn't solve the specific issue of transforming an entity list, but it allows me to customize the drop down list values.
The viewability entity was removed and the relationship in the Message entity was changed to an integer field.
My main type is now as follows:
class MessageType extends AbstractType
{
private $user;
public function __construct($user)
{
$this->user = $user;
}
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('body', new MessageBodyType())
->add('viewability', 'choice', array(
'choices' => array(
1 => $user->getFirstName(),
2 => $user->getAddress()->getCity(),
3 => 'Anonymous',
),
'multiple' => false,
'label' => 'Send Message As',
'data' => 0,
))
;
}
/**
* #param OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Members\MessagesBundle\Entity\Message',
));
}
/**
* #return string
*/
public function getName()
{
return 'members_messages_message';
}
}

Symfony 2.3.4 Implemented form not validated

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'),
));