Weird behaviour constraint using simple symfony form - php

I'm trying to use a simple form definition to filter some data, so I create the form with no class attached(expecting to use getData() function) and then work with the array of parameters passed to the form, but the form comes always invalid. Result that the form is trying to validate a parameter that do not belong to the context of form.
I'm getting this validation error on the field "almacen":
This value should not be blank.
With cause:
Symfony\Component\Validator\ConstraintViolation
Object(Symfony\Component\Form\Form).data[almacen].responsable = null
I tried using cascade_validation=false but did't work.
In the controller action I declared:
public function indexAction(Request $request)
{
$informeStock = $this->createForm(new BusquedaInformeStockType());
$informeStock->handleRequest($request);
if ($informeStock->isSubmitted() && $informeStock->isValid()) {
$data = $informStock->getData();
// the action logic...
}
...
}
I have a simple form definition, with an entity form type declared and no data_class asociated to the form.
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('almacen', 'entity', array(
'class' => 'BusetaBodegaBundle:Bodega',
'placeholder' => '---Seleccione---',
'required' => false,
'label' => 'Bodega',
'attr' => array(
'class' => 'form-control',
),
))
...
...
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'csrf_protection' => false,
));
}
And this is the definition of the entity Bodega:
class Bodega
{
...
/**
* #var string
*
* #ORM\Column(name="codigo", type="string", nullable=true)
* #Assert\NotBlank()
*/
private $codigo;
/**
* #var string
*
* #ORM\Column(name="nombre", type="string")
* #Assert\NotBlank()
*/
private $nombre;
/**
* #ORM\ManyToOne(targetEntity="Buseta\BodegaBundle\Entity\Tercero", inversedBy="bodega")
* #Assert\NotBlank()
*/
private $responsable;
...
}
In previous versions of the entity Bodega the parameter "responsable" was left in blank, so there is some rows in the db thas has no "responsable" asociated.
But despite that this should not be happening right? What I'm doing wrong?

You have an entity form field with validation constraints:
/**
* #ORM\ManyToOne(targetEntity="Buseta\BodegaBundle\Entity\Tercero", inversedBy="bodega")
* #Assert\NotBlank()
*/
private $responsable;
This is your problem - Assert not blank
Validates that a value is not blank, defined as not strictly false,
not equal to a blank string and also not equal to null
You have few choices, either add validation group(read this and this) or simple remove that Assert. Also its better to use #Assert\Valid for associations like that.

Related

Catchable Fatal Error: Object of class Proxies\__CG__\AppBundle\Entity\Ticket could not be converted to string

I have an issue on my project using Symfony2. Is a 'worklog' project, with tickets(issues) for a specific project. But when I try to edit a worklog entry I have this error :
Catchable Fatal Error: Object of class
Proxies__CG__\AppBundle\Entity\Ticket could not be converted to
string
This is my db model :
And this is a part of the code from AppBundle/Entity/Worklog/
/**
* #var \Ticket
*
* #ORM\ManyToOne(targetEntity="Ticket")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="ticket_ticket_id", referencedColumnName="ticket_id")
* })
*/
private $ticketTicket;
And from AppBundle/Entity/Ticket/
/**
* #var integer
*
* #ORM\Column(name="ticket_id", type="integer", nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $ticketId;
Do you have any idea why I have this errors ? Any idea to help ?
Worklog Form:
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('worklogDate')
->add('worklogDuration')
->add('worklogDescription')
->add('ticketTicket')
;
}
/**
* #param OptionsResolverInterface $resolver
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\Worklog'
));
}
/**
* #return string
*/
public function getName()
{
return 'appbundle_worklog';
}
The problem is your entity field not expected return.
try add:
public function __toString()
{
return (string) $this->getTicket();
}
Why don't declare your fields fully in your Form class. It would be smth like this:
->add('ticketTicket', 'entity', array(
'class' => 'AppBundle\Entity\Ticket',
'property' => 'propertyName', //needed property's name
'label' => 'choice_field_label'
))
If you need smth more complicated then just findAll for this field, you could use query_builder option:
->add('ticketTicket', 'entity', array(
'class' => 'AppBundle\Entity\Ticket',
'property' => 'propertyName', //needed property's name
'label' => 'choice_field_label',
'query_builder' => function(EntityRepository $er) {
return $er->findAllTicketNames();
//Where findAllTicketNames is the name of method in your
// ticketRepo which returns queryBuilder,
//instead of this you could just write your custom query like
//$qb = $er->createQueryBuilder('t');
//$qb->andWhere(...);
//return $qb;
}
))
p.s.
my answer was copied from my previous answer and some little modifications were added to suit your case :)

Symfony entity field : manyToMany with multiple = false - field not populated correctly

I am using symfony2 with doctrine 2.
I have a many to many relationship between two entities :
/**
* #ORM\ManyToMany(targetEntity="\AppBundle\Entity\Social\PostCategory", inversedBy="posts")
* #ORM\JoinTable(
* name="post_postcategory",
* joinColumns={#ORM\JoinColumn(name="postId", referencedColumnName="id", onDelete="CASCADE")},
* inverseJoinColumns={#ORM\JoinColumn(name="postCategoryId", referencedColumnName="id", onDelete="CASCADE")}
* )
*/
private $postCategories;
Now I want to let the user only select one category. For this I use the option 'multiple' => false in my form.
My form:
->add('postCategories', 'entity', array(
'label'=> 'Catégorie',
'required' => true,
'empty_data' => false,
'empty_value' => 'Sélectionnez une catégorie',
'class' => 'AppBundle\Entity\Social\PostCategory',
'multiple' => false,
'by_reference' => false,
'query_builder' => $queryBuilder,
'position' => array('before' => 'name'),
'attr' => array(
'data-toggle'=>"tooltip",
'data-placement'=>"top",
'title'=>"Choisissez la catégorie dans laquelle publier le feedback",
)))
This first gave me errors when saving and I had to change the setter as following :
/**
* #param \AppBundle\Entity\Social\PostCategory $postCategories
*
* #return Post
*/
public function setPostCategories($postCategories)
{
if (is_array($postCategories) || $postCategories instanceof Collection)
{
/** #var PostCategory $postCategory */
foreach ($postCategories as $postCategory)
{
$this->addPostCategory($postCategory);
}
}
else
{
$this->addPostCategory($postCategories);
}
return $this;
}
/**
* Add postCategory
*
* #param \AppBundle\Entity\Social\PostCategory $postCategory
*
* #return Post
*/
public function addPostCategory(\AppBundle\Entity\Social\PostCategory $postCategory)
{
$postCategory->addPost($this);
$this->postCategories[] = $postCategory;
return $this;
}
/**
* Remove postCategory
*
* #param \AppBundle\Entity\Social\PostCategory $postCategory
*/
public function removePostCategory(\AppBundle\Entity\Social\PostCategory $postCategory)
{
$this->postCategories->removeElement($postCategory);
}
/**
* Get postCategories
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getPostCategories()
{
return $this->postCategories;
}
/**
* Constructor
* #param null $user
*/
public function __construct($user = null)
{
$this->postCategories = new \Doctrine\Common\Collections\ArrayCollection();
}
Now, when editing a post, I also have an issue because it uses a getter which ouputs a collection, not a single entity, and my category field is not filled correctly.
/**
* Get postCategories
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getPostCategories()
{
return $this->postCategories;
}
It's working if I set 'multiple' => true but I don't want this, I want the user to only select one category and I don't want to only constraint this with asserts.
Of course there are cases when I want to let the user select many fields so I want to keep the manyToMany relationship.
What can I do ?
If you want to set the multiple option to false when adding to a ManyToMany collection, you can use a "fake" property on the entity by creating a couple of new getters and setters, and updating your form-building code.
(Interestingly, I saw this problem on my project only after upgrading to Symfony 2.7, which is what forced me to devise this solution.)
Here's an example using your entities. The example assumes you want validation (as that's slightly complicated, so makes this answer hopefully more useful to others!)
Add the following to your Post class:
public function setSingleCategory(PostCategory $category = null)
{
// When binding invalid data, this may be null
// But it'll be caught later by the constraint set up in the form builder
// So that's okay!
if (!$category) {
return;
}
$this->postCategories->add($category);
}
// Which one should it use for pre-filling the form's default data?
// That's defined by this getter. I think you probably just want the first?
public function getSingleCategory()
{
return $this->postCategories->first();
}
And now change this line in your form:
->add('postCategories', 'entity', array(
to be
->add('singleCategory', 'entity', array(
'constraints' => [
new NotNull(),
],
i.e. we've changed the field it references, and also added some inline validation - you can't set up validation via annotations as there is no property called singleCategory on your class, only some methods using that phrase.
You can setup you form type to not to use PostCategory by reference (set by_reference option to false)
This will force symfony forms to use addPostCategory and removePostCategory instead of setPostCategories.
UPD
1) You are mixing working with plain array and ArrayCollection. Choose one strategy. Getter will always output an ArrayCollection, because it should do so. If you want to force it to be plain array add ->toArray() method to getter
2) Also I understand that choice with multiple=false return an entity, while multiple=true return array independend of mapped relation (*toMany, or *toOne). So just try to remove setter from class and use only adder and remover if you want similar behavior on different cases.
/** #var ArrayCollection|PostCategory[] */
private $postCategories;
public function __construct()
{
$this->postCategories = new ArrayCollection();
}
public function addPostCategory(PostCategory $postCategory)
{
if (!$this->postCategories->contains($postCategory) {
$postCategory->addPost($this);
$this->postCategories->add($postCategory);
}
}
public function removePostCategory(PostCategory $postCategory)
{
if ($this->postCategories->contains($postCategory) {
$postCategory->removePost($this);
$this->postCategories->add($postCategory);
}
}
/**
* #return ArrayCollection|PostCategory[]
*/
public function getPostCategories()
{
return $this->postCategories;
}
In my case, the reason was that Doctrine does not have relation One-To-Many, Unidirectional with Join Table. In Documentations example is show haw we can do this caind of relation by ManyToMany (adding flag unique=true on second column).
This way is ok but Form component mixes himself.
Solution is to change geters and seters in entity class... even those generated automatically.
Here is my case (I hope someone will need it). Assumption: classic One-To-Many relation, Unidirectional with Join Table
Entity class:
/**
* #ORM\ManyToMany(targetEntity="B2B\AdminBundle\Entity\DictionaryValues")
* #ORM\JoinTable(
* name="users_responsibility",
* joinColumns={#ORM\JoinColumn(name="user_id", referencedColumnName="id", onDelete="CASCADE")},
* inverseJoinColumns={#ORM\JoinColumn(name="responsibility_id", referencedColumnName="id", unique=true, onDelete="CASCADE")}
* )
*/
private $responsibility;
/**
* Constructor
*/
public function __construct()
{
$this->responsibility = new \Doctrine\Common\Collections\ArrayCollection();
}
/**
* Add responsibility
*
* #param \B2B\AdminBundle\Entity\DictionaryValues $responsibility
*
* #return User
*/
public function setResponsibility(\B2B\AdminBundle\Entity\DictionaryValues $responsibility = null)
{
if(count($this->responsibility) > 0){
foreach($this->responsibility as $item){
$this->removeResponsibility($item);
}
}
$this->responsibility[] = $responsibility;
return $this;
}
/**
* Remove responsibility
*
* #param \B2B\AdminBundle\Entity\DictionaryValues $responsibility
*/
public function removeResponsibility(\B2B\AdminBundle\Entity\DictionaryValues $responsibility)
{
$this->responsibility->removeElement($responsibility);
}
/**
* Get responsibility
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getResponsibility()
{
return $this->responsibility->first();
}
Form:
->add('responsibility', EntityType::class,
array(
'required' => false,
'label' => 'Obszar odpowiedzialności:',
'class' => DictionaryValues::class,
'query_builder' => function (EntityRepository $er) {
return $er->createQueryBuilder('n')
->where('n.parent = 2')
->orderBy('n.id', 'ASC');
},
'choice_label' => 'value',
'placeholder' => 'Wybierz',
'multiple' => false,
'constraints' => array(
new NotBlank()
)
)
)
I know its a pretty old question, but the problem is still valid today.
Using a simple inline data transformer did the trick for me.
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder->add('profileTypes', EntityType::class, [
'multiple' => false,
'expanded' => true,
'class' => ProfileType::class,
]);
// data transformer so profileTypes does work with multiple => false
$builder->get('profileTypes')
->addModelTransformer(new CallbackTransformer(
// return first item from collection
fn ($data) => $data instanceof Collection && $data->count() ? $data->first() : $data,
// convert single ProfileType into collection
fn ($data) => $data && $data instanceof ProfileType ? new ArrayCollection([$data]) : $data
));
}
PS: Array functions are available in PHP 7.4 and above.

Symfony form - how to add some logic while validating

I have a form, used for a course subscription, in which I have 2 entity fields, activite and etudiant. I would like NOT to validate this form IF the trainer i already booked (information that i can find in the DB through the entity activite).
How (where...) can i add some logic instructions to control the validation of this form?
Is anybody has an idea? A lead? It would be so simple WITHOUT Symfony (for me)!...
Thanks
class InscriptionType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('activiteId', 'entity',
array('label'=>'Activité',
'attr'=>array('class'=>'form-control'),
'class'=>'AssoFranceRussie\MainBundle\Entity\Activite',
'property'=>'nomEtNiveauEtJour',
))
->add('etudiantId', 'entity',array('label'=>'Etudiant',
'attr'=>array('class'=>'form-control'),
'class'=>'AssoFranceRussie\MainBundle\Entity\Etudiant',
'property'=>'NomEtPrenom',
))
;
}
You can write your own constraints and validators by extending the Symfony validation classes.
You need to extend Symfony\Component\Validator\Constraint to define the constraint and Symfony\Component\Validator\ConstraintValidator to define the validation code.
There are probably other ways to do this as well, but this gives you complete control of the validation.
You could do what you want in this way:
1- You need a variable to store the EntityManager in your InscriptionType class:
protected $em;
public function __construct($em) {
$this->em = $em;
}
2- Pass the entity manager to the FormType class from your controller as below:
new InscriptionType( $this->getDoctrine()->getManager() );
3- Add the logic what you want in the setDefaultOptions function to specify the validation_groups what you want for the form:
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$p = $this->em->getRepository('YourBundle:YourEntity')->find(1);
if($p){
$resolver->setDefaults(array(
'data_class' => 'YourBundle\Entity\YourEntity',
'validation_groups' => array('myValidation1'),
'translation_domain'=>'custom'
));
}
else{
$resolver->setDefaults(array(
'data_class' => 'YourBundle\Entity\YourEntity',
'validation_groups' => array('myValidation2'),
'translation_domain'=>'custom'
));
}
}
4- In your entity, you need to specify the validation groups for the fields of the entity:
/**
* #var string
*
* #ORM\Column(name="name", type="string", length=255)
* #Assert\NotNull(groups={"myValidation1"})
*/
private $name;
/**
* #var date
*
* #ORM\Column(name="start", type="date")
* #Assert\NotNull(groups={"myValidation1", "myValidation2"})
* #Assert\Date()
*/
private $start;
In this case, the field 'start' would be validated in both cases, but the first only with the myValidation1 is the group to be validated.
In this way, you could control which fields you want to validate.

Symfony2 parameter with embedded forms

EDIT : I find the real problem!
I am trying to give a paramEter to a sub-form from the controller. Without this parameter, the form is working perfectly.
I want to show in the select list only users which are not already present in the relation. I have this query_builder:
'query_builder' => function(UserRepository $er) use($options) {
return $er->getFormateursAvailable($options['categ']);
},
And the method:
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array('data_class' => 'Intranet\FormationBundle\Entity\CategorieFormateur', 'categ' => false));
//$resolver->setDefaults(array('data_class' => 'Intranet\FormationBundle\Entity\CategorieFormateur'));
}
For the collection form, so, I have to put this option in the form:
'options' => $options,
But I don't know if it is true, and I have to define the method:
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array('categ' => false));
}
(The form is working without this method if there is no parameter.)
And calling:
$form = $this->createForm(new GererFormateurCategorieType(), $categorie, array('categ' => $categorie));
And then, I have this error:
Neither the property "user" nor one of the methods "getUser()", "isUser()", "hasUser()", "__get()" exist and have public access in class "Intranet\FormationBundle\Entity\Categorie".
The relation:
Categorie has this property:
/**
* #ORM\OneToMany(targetEntity="Intranet\FormationBundle\Entity\CategorieFormateur", mappedBy="categorie", cascade={"persist", "remove"}, orphanRemoval=true)
**/
private $formateurs;
With addFormateur, removeFormateur and getFormateurs
CategorieFormateur :
/**
* #ORM\Id
* #ORM\ManyToOne(targetEntity="Intranet\FormationBundle\Entity\Categorie", inversedBy="formateurs")
* #ORM\JoinColumn(name="categorie_id", referencedColumnName="id")
*/
private $categorie;
/**
* #ORM\Id
* #ORM\ManyToOne(targetEntity="Intranet\UserBundle\Entity\User")
* #ORM\JoinColumn(name="user_id", referencedColumnName="id")
*/
private $user;
with setters and getters for each properties.
According to the error message, you have to add the setFormateurs() method in your Categorie entity:
public function setFormateurs($formateurs)
{
$this->formateurs = $formateurs;
return $this;
}
I found an alternative method, wich is not the true answer :
In the repository, I use
$request = Request::createFromGlobals();
And I explode the $requestPathInfo() to get the last parameter and I use it in the query...
But the existing users in the relation are not loaded altough they not appers in the list.
I hope somebody knows how to do that properly !

Issue in rendering collection of forms in Symfony 2.0

I have the following User entity:
class User extends BaseUser implements ParticipantInterface
{
/**
* #Exclude()
* #ORM\OneToMany(targetEntity="App\MainBundle\Entity\PreferredContactType", mappedBy="user", cascade={"all"})
*/
protected $preferredContactTypes;
/**
* Add preferredContactType
*
* #param \App\MainBundle\Entity\PreferredContactType $preferredContactType
* #return User
*/
public function addPreferredContactTypes(\App\MainBundle\Entity\PreferredContactType $preferredContactType)
{
$this->preferredContactTypes[] = $preferredContactType;
return $this;
}
/**
* Remove preferredContactType
*
* #param \App\MainBundle\Entity\PreferredContactType $preferredContactType
*/
public function removePreferredContactTypes(\App\MainBundle\Entity\PreferredContactType $preferredContactType)
{
$this->preferredContactTypes->removeElement($preferredContactType);
}
/**
* Get preferredContactType
*
* #return \App\MainBundle\Entity\PreferredContactType
*/
public function getPreferredContactTypes()
{
return $this->preferredContactTypes;
}
}
I wanted to create a form that displays multiple choices of the preferredContactType:
$contactOptions = $em->getRepository('AppMainBundle:ContactType')->findAll();
$settingsForm = $this->createForm(new UserType(), $user, array(
'contact' => $contactOptions,
));
and here's what my UserType looks like:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('preferredContactTypes', 'collection', array('type' => 'choice', 'options' => array('choices' => $options['contact'], 'multiple' => true, 'expanded' => true)))
;
}
but now I am getting an error of:
Expected an array.
How do I solve this?
It fails because you pass to choices parameter instance of ArrayCollection class (that was returned in $em->getRepository('AppMainBundle:ContactType')->findAll();). But choices parameter need to be array (http://symfony.com/doc/2.0/reference/forms/types/choice.html#choices).
You can use toArray() method on your ArrayCollection to retrieve the array but it will not give the expected result because it will be array of instances of ContactType.
The best way to form some choice list from entity is to use entity type instead of choice. See more here: http://symfony.com/doc/current/reference/forms/types/entity.html.
Also you can implement Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface in some class and use it as parameter choice_list in choice form field. For example you can extend Symfony\Component\Form\Extension\Core\ChoiceList\LazyChoiceList and implement loadChoiceList() method.

Categories