Validate field based on another field using additional database - php

I have a simple form, which contains two fields, the first field is just a select and the second field contains a value, which needs to be checked with the help of the first field.
I have found a similar question Symfony2 form validation based on two fields and tried to use the Callback validator.
I have read the documentation, but I can't figure out how I can use it with access to the database.
It seems that the only way is to call the validation method statically, but in this case I loose the context of my controller. I need it to access for example the database.
In this relation I am using Silex and want to access services provided by it.
The form isn't mapped to any class, so the creation of an own constraint looks wrong for me, because I don't see any way to pass the other fields to the validator.
Is there any way to achieve it?
Or do I need another approach?

I strongly advise you to map the form to a class and create a custom constraint...
I have written a detailed example on how to:
create your own validation constraint
turn it into a service
inject the object-manager
access the database from the constraint
TLDR:
What you need is a custom validator on class level.
A class-level validator is needed because you need to access the whole
object (not only a single property) if you want validate multiple related values...
... or need to fetch something from database using another property as select-criteria.
Here's the the complete answer with example.
Another option could be creating a form-event listener and passing the object-manager to it before adding it to the form.
Then take care of the validation-process (checking the data against the database + eventually adding errors to the form) inside the listener yourself.

Related

How to make form class for 2 different entities using symfony 4?

Hello I am using Symfony 4,
I want to create a formClass which insert data for 2 different entities like 'vendor' and 'address'. Please explain me how can I create form class to accept data for 2 different entities via same form?
I have created form class which accepts data for only one entity but I am not getting to make formclass which accepts data for 2 different entities?
Basically I am not getting to embed form together as one.
First of all, don't use entity type, make your own DTO(Data Transfer Object). I assume that both of these entities share some fields. So you can create a facade that'll help you with converting DTO to your entity types.
Make your DTO based on the request not on the actual entity, this has a great advantage because your code is less coupled and more flexible for changes in the future, it requires some additional work though. For example, yo'llu need to fill the DTO on your own and convert it back to the entities based on some strategy, in your case, it'd be vendor and access.
There's a nice article explaining how to do it in details you can look at it https://blog.martinhujer.cz/symfony-forms-with-request-objects/
combine the forms (preferred)
Assuming you have two entities Vendor and Address and forms for those entities VendorType and AddressType, you could create a form type VendorAddressType that, as a buildForm function has
$builder->add('vendor', VendorType::class)
->add('address', AddressType::class);
and when creating the VendorAddressType form you provide an array ['vendor' => $vendor, 'address' => $address]. (you can similarly pass through options to the sub-forms). The data_class option for each subform can be the appropriate class, the data_class option for the combined form must be null.
now, in the normal symfony form templates, those forms are rendered differently from truly being in one form, but those templates could be extended, such that instead of form_rows, you call form_widget on form.vendor and form.address instead (see https://symfony.com/doc/current/form/form_themes.html#creating-a-form-theme-in-the-same-template-as-the-form)
combine the forms, literally (not preferred)
Same assumption as before, you could copy the contents of each form type into the new VendorAddress form, and then set the property_path attribute for all fields to point to the respective entities, like:
$builder->add('vendorfieldA', SomeType::class, ['property_path' => 'vendor.fieldA'])
which will look into the provided data array (see before) at the key vendor, and select its property fieldA.
I would advise against this approach, since any changes to the single-entity forms must be repeated for the multiple-entity forms. Instead, I would advise to make static buildForm functions (adding a parameter prefix or something) which can be called from the combined form type via VendorType::staticBuildForm($builder, $prefix) ($prefix = 'vendor.'). (you have to transform that prefix into something you can append/prepend to the form field name as well, or provide that explicitly)

Symfony Validator: validate entity is unique and return the duplicate entity if it isn't?

In my Symfony application, when creating or updating a certain entity, I would like to check whether a duplicate entity already exists, and if it does, display an URL to the existing entity's page together with the error message.
I use Symfony's Validator component with custom constraint classes for my validation, to have reusable validators in different parts of the application - the web controller, console commands, batch processing scripts, etc. I would like $validator->validate($entity) to validate entity uniqueness as well, using a custom UniqueEntityConstraint
However, I cannot find a way to do this - there doesn't appear to be any way to pass an object from a Constraint Validator back to the controller.
Is there a way to do what I need using the Validator component, or am I at a dead end?
Solutions I've explored so far:
DoctrineBridgeBundle's UniqueEntity constraint.
Doesn't return the existing entity or even its ID, just a message that it exists.
Extend ConstraintViolation with a getDuplicateEntity() method.
This would be the optimal solution, but it doesn't work - there appears to be no way to use a custom class without overriding core Symfony code.
Store the duplicate entity on the Constraint itself and retrieve it using $violation->getConstraint()->getDuplicateEntity().
It would be a dirty hack even if it worked, but it doesn't - ConstraintValidators reuse the same Constraint instance, so if you were validating multiple entities in a row, getDuplicateEntity() would only get you the last duplicate from the batch.
Long story short, this is not possible to do with the Validator component. If you want your validation checks to return more than string error messages, you should write a custom service.
I've submitted a feature request to potentially make this possible in the future.

Symfony Form: How to replace form class / retrieve all extra data?

The Symfony 2 Form Component really is something. I guess you know that. Trying to understand what works how is just an seemingly impossible task; and I'm quite experienced at browsing through codebases.. But man, the Form component.. OMG
TL;DR
Below are details, this issue tries to ask
Is it possible to replace the class \Symfony\Component\Form\Form?
Or: How to easily get all extra data from all fields of type form of a form?
Or, a related question: How on earth does it work that if i do $form->add('ss', 'form') - obviously the Core\FormType class gets involved but when i retrieve it later, it is an instance of \Symfony\Component\Form\Form? Where does that happen and can this maybe overriden so it uses a different class there?
Details
The situation
Imagine a Controller that receives a n deep JSON payload. This payload gets decoded and validated through Form. Now, most of this JSON structure is mapped by Models (Doctrine ODM Entities). But some sub-properties are just "hashes" - the client is allowed to post whatever he wants there.
Those "hash" subproperties are fields of type form, are compound flagged and can have extra fields.
The problem
Bottom line our problem is, that all "extra fields" are not returned by $form->getData(). We are unable to specify those fields (and their types) as we don't know what will come - so all this data is part of extraData.
The solution?
So i thought - OK - let's modify the FormFactory so it'll return our Custom extension of Form - one that also returns extraData when getData is called. Easy right.? Noo, not easy..
My idea let me inspect FormFactory, the FormBuilders, the ResolvedFormTypes, whatnot.. and at all important places (like formBuilder->getForm()) - the classes I want to override/replace are instanciated static - no usage of the DIC..(!)
I've read somewhere that the Form component in Symfony 2 was written by the devil himself to let we developer go mad. Luckily I'm not so mad to try to replace the entire Form component by myself ;)
Jokes aside, when you define a form (by creating a <entity>Type class, like UserType) you are defining a new type that can be as simple as a single text input or as complex as full-fledged form (our UserType from before). The "real" form (Symfony\Component\Form\Form) is created from your type definition:
return $this->container->get('form.factory')->create(
new UserType(), new User(), $options
);
Have you evaluated the idea to create a custom form type for your "extraData"? Or maybe it can be mapped as a JSON string that's created by a client-side JavaScript before submit.

Symfony programmatic form validation

In Symfony 2.5.10, I have multiple form types, each with their own validation groups, calling various class constraints. Each type represents a particular step in a workflow. The final step iterates over all other steps, performs the necessary validation and displays any possible violations to the user.
The data behind each form type (data_class) is always the same: a doctrine entity.
Each displayed violation links to the URL/path representing the step in question, with an additional parameter which, when present, should trigger the validation. The idea is that the form should display the errors immediately, without submitting. In other words, I wish to trigger form submission programmatically (server-side, not JS!).
I've tried various different ways:
Somehow put the entity (which I have) into the request and set the method of the latter to POST, so that Form::handleRequest handles everything automatically. Problem: converting the entity object to an array, as would be returned from a real form submission. Maybe a DataTransformer? PropertyAccessor?
As 1. but call Form::submit instead. See http://symfony.com/doc/2.5/cookbook/form/direct_submit.html#calling-form-submit-manually
Validate myself (in the same way I do in the final step), giving me a ConstraintViolationList. Iterate over the list and call Form::addError for each violation. Problem: I need to add the errors to the correct child elements, not just the parent. Maybe I could do this using the propertyPath of each error, but how to go from that to child form?
Any help would be much appreciated. I've trawled stackoverflow and the web in general, but to no avail.
First way looks good. You just should check the ajax form and them just use something like
$entity = $form->getData()
to retrieve data from form (which could be array, entity, or some other stuff you have in you form).
Then just pass this entity to the twig view to form an request answer. Alongside with entity you might want to provide form errors aquired from $form->createView() FormView object.

share annotaion validation rules between form and persistence

one "simple" question: http://mwop.net/blog/2012-07-02-zf2-beta5-forms.html
is it possible to use the zf2 zend form annotation validation rules without using zend form, so i can share the validation rules between a model validator (e.g. using for check if the model is correct before persisting it) and the zend form validation?
if my "name" should be not empty and between 5 and 20 characters, it is the same rule for the form and the model.
i hope i pointed it out clearly
Roman
Well, since all data that the models are getting would be from user input or the database, you shouldn't need to test the models itself, too. THe data inside the database should be correct!
IE: trust your own data but not the users?
But if you still wanna do that, i guess you could build the form with the AnnotationBuilder, then get the InputFilters from the Form (im sure there's a method, maybe on per-element-basis) and then use those inside your models - but as my first paragraph implies, i see this as a quite useless point :)
As for multi usable input filters, best thing would be to write own classes extending Zend\InputFilter\InputFilter on a per model basis. When you build your form then you can attach that class as the filter definition via $form->setInputFilter($myModelInputFilterClass) and you could also call that class inside your models to run your data through those filters. I haven't done this manually but it should work.
The only pitfall i guess might happen if you run into required statements. Checking on a per element basis, i don't know if that will work, too. As the InputFilter checks against all given filters. Though if you import a full CSV-Sheet or something you'd have a populateFromCsv() function or something that then checks all data anyways i guess.

Categories