I'm trying to create a form that change the validation of a field based on the select option from the html form field.
Ex: if user select a option 1 from drop down field "options", I want the field "metric" to validate as sfValidatorInteger. If user select option 2 from field "options", I want the field "metric" to validate as sfValidatorEmail, etc.
So inside the public function configure() { I have the switch statement to capture the value of "options", and create the validator based on that value returned from the "options".
1.) How do I capture the value of "options" ? I've tried:
$this->getObject()->options
$this->getTaintedValues()
The only thing that's currently working for me is but it's not really MVC:
$params = sfcontext::getInstance()->getRequest()->getParameter('options');
2.) Once I've captured that information, how can I assign the value of "metric" to a different field? ("metric" is not a real column in db). So I need to assign the value of "metric" to different field such as "email", "age" ... Currently I'm handling this at the post validator like this, just wondering if I can assign value within the configure():
$this->validatorSchema->setPostValidator(new sfValidatorCallback(array('callback' => array($this, 'checkMetric'))));
public function checkMetric($validator, $values) {
}
Thanks!
You want to use a post validator. Try doing something like this in your form:
public function configure()
{
$choices = array('email', 'integer');
$this->setWidget('option', new sfWidgetFormChoice(array('choices' => $choices))); //option determines how field "dynamic_validation" is validated
$this->setValidator('option', new sfValidatorChoice(array('choices' => array_keys($choices)));
$this->setValidator('dynamic_validation', new sfValidatorPass()); //we're doing validation in the post validator
$this->mergePostValidator(new sfValidatorCallback(array(
'callback' => array($this, 'postValidatorCallback')
)));
}
public function postValidatorCallback($validator, $values, $arguments)
{
if ($values['option'] == 'email')
{
$validator = new sfValidatorEmail();
}
else //we know it's one of email or integer at this point because it was already validated
{
$validator = new sfValidatorInteger();
}
$values['dynamic_validation'] = $validator->clean($values['dynamic_validation']); //clean will throw exception if not valid
return $values;
}
1) In a post validator, values can be accessed by using the $values parameter. Just use $values['options'] and it should be fine... or did you want to access this values from another part of you code? $this->getObject()->widgetSchema['options'] should work too I think, once your form is bound to an object.
2) The configure() method is called at the end of the form constructor, so values are not bound nor accessible yet, unless you are initializing your form with an object from the db (which does not require any validation). But if you want to initialize your form from $_POST, a post validator is definitely the way to go IMHO.
I got the validation error to appear alongside the field by throwing a sfValidatorErrorSchema instead of a sfValidatorError.
$values['dynamic_validation'] = $validator->clean($values['dynamic_validation']);
…becomes…
try
{
$values['dynamic_validation'] = $validator->clean($values['dynamic_validation']);
}
catch(sfValidatorError $e)
{
$this->getErrorSchema()->addError($e, 'dynamic_validation');
throw $this->getErrorSchema();
}
Not sure if this is the best way to get this result, but it seems to be working for me at the moment.
Related
I have fields in a Window, some with validators and all bound to properties.
The validation works as expected.
But -
I do not want to proceed when any field is invalid. What would be the best way to determine if any validation went wrong?
There are several ways of dealing with validation in Vaadin, all supported by Vaadin (no need for custom boolean afterValidationFlag).
One possible way (preffered by me) shown below:
public class CustomWindow extends Window {
DateField customBeanFirstPropertyName = new DateField("Caption1");
ComboBox customBeanSecondPropertyName = new ComboBox("Caption2");
TextArea customBeanThirdPropertyName = new TextArea("Caption3");
BeanFieldGroup<CustomBean> binder = new BeanFieldGroup<>(CustomBean.class);
public CustomWindow(CustomBean customBean) {
buildLayout();
binder.buildAndBindMemberFields(this);
binder.setItemDataSource(new BeanItem<>(customBean));
//add validators
customBeanFirstPropertyName.addValidator((Validator) value -> {
if (value == null) throw new Validator.InvalidValueException("nonnull required");
});
customBeanThirdPropertyName.addValidator(
new RegexpValidator(".{3,20}", "length between 3-20 required")
);
/*
or have basic validators on #Entity level with e.g. javax.validation.constraints.Size
example:
#Size(min = 3, max = 20)
#Column(name = "customBeanThirdPropertyName", unique = true)
private String customBeanThirdPropertyName;
*/
}
void commit(Button.ClickEvent event) { //method called by "save" button
try {
binder.commit(); //internally calls valid() method on each field, which could throw exception
CustomBean customBeanAfterValidation = binder.getItemDataSource().getBean(); //custom actions with validated bean from binder
this.close();
} catch (FieldGroup.CommitException e) {
Map<Field<?>, Validator.InvalidValueException> invalidFields = e.getInvalidFields(); //do sth with invalid fields
}
}
}
If you use a FieldGroup instance to bind your fields with the properties, which is the recommended way, you can write:
fieldGroup.isValid();
That checks on all field validations of the fields managed by the field group.
Maintain a flag. Before proceeding, check if the flag is set. In the validation code, set the flag if the validation fails.
i new to zenframework 2. i have correctly set up zendframework 2,doctrine and zfcUser.All work correctly.
my issue now is now regarding how to prepoulated a form if a member is already logged in.
this is where i extend zfcUser to obtain the Id of a loggged in member:
public function setid( $id)
{
$this->id = $id;
}
public function getId()
{
if (!$this->id) {
$this->setid($this->zfcUserAuthentication()->getAuthService()->getIdentity()->getId());
}
return $this->id;
}
i know want to use that Id to obtain the values from the database and then populate the form with those values.
this is my form:
public function aboutYouAction()
{
$id = $this->getId() ;
$form = new CreateAboutYouForm($this->getEntityManager());
$aboutYou = new AboutYou();
$form->setInputFilter($aboutYou->getInputFilter());
$form->bind($aboutYou);
if ($this->request->isPost())
{
$form->setData($this->request->getPost());
if ($form->isValid())
{
$post = $this->request->getPost();
$this->getEntityManager()->persist($aboutYou);
$this->getEntityManager()->flush();
return $this->redirect()->toRoute('worker', array('action' => 'aboutYou'));
}
}
$messages='';
// return array('form' => $form);
return new ViewModel(array('form' => $form, 'messages' => $messages));
}
To set the values on the form all you need to do is $form->bind($aboutYou)
The bind() method is designed to take the passed entity instance and map it to the forms elements; This process being referred to as form hydration.
Depending on the hydrator attached to the form or fieldset (With doctrine this would normally be the DoctrineModule\Stdlib\Hydrator\DoctrineObject) this should be able to evaluate the AboutYou fields, including any entity references/associations, and set the corresponding form elements values. I'm assuming that one of these fields is user.
In you specific case it seems you are binding a new entity (which therefore will not have any properties set, such as your user)
$aboutYou = new AboutYou(); // Brand new entity
$form->bind($aboutYou); // Binding to the form without any data
What this means is that the form is trying to set the values of the elements but the provided AboutYou class has no data to set (as its new and was not loaded via doctrine) and/or the properties of the AboutYou class to not correctly map to the form's elements.
If you wish to bind the user you will need to fetch the populated instance. This can be done using doctrine ($objectManager->find('AboutYou', $aboutYouId)) or if you need to set the current logged in user call the controller plugin ZfcUser\Controller\Plugin\ZfcUserAuthentication from within the controller and no where else.
You workflow should be similar to this (illustration purposes only)
// Controller
public function aboutYouAction()
{
// Get the id via posted/query/route params
$aboutYouId = $this->params('id', false);
// get the object manager
$objectManager = $this->getServiceLocator()->get('ObjectManager');
// Fetch the populated instance
$aboutYou = $objectManager->find('AboutYou', $aboutYouId);
// here the about you entity should be populated with a user object
// so that if you were to call $aboutYou->getUser() it would return an user object
// Get the form from the service manager (rather than creating it in the controller)
// meaning you should create a factory service for this
$form = $this->getServiceLocator()->get('MyForm');
// Bind the populated object to the form
$form->bind($aboutYou);
//... rest of the action such as handle edits etc
}
I'm using genemu_jqueryselect2_entity for a multiple selection field within a form (located in an Sonata admin class) for a so called Uni (university) entity:
->add('courses', 'genemu_jqueryselect2_entity',array('multiple' => true, 'class' => 'PROJECT\UniBundle\Entity\Course'))
But the selected entries are not filled into my entity. With firebug I was able to detect, that the ids of the courses are passed correctly via POST.
Maybe the field is not correctly mapped to the Uni entity, but I have no idea why.
This is the adding method of my Uni entity, which doesn't even get called:
public function addCourse(\PROJECT\UniBundle\Entity\Course $courses)
{
$this->courses[] = $courses;
return $this;
}
How can I get the field to be mapped with the courses attribute of Uni? How could I debug this?
Any help will be appriciated!
Try writing that method like this:
public function addCourse(\PROJECT\UniBundle\Entity\Course $course)
{
$this->courses[] = $course;
$course->setUniversity($this); // Or similar.
return $this;
}
Otherwise foreign key is not set on a course row in the DB.
Try to create method setCourses
public function setCourses(\Doctrine\Common\Collections\Collection $courses)
{
$this->courses = $courses;
...
I don't know why, but the method addCourse isn't called.
Anyway, Tautrimas Pajarskas's answer was usefull to me so I gave an upvote.
The foreign key relationship was the necessary and missing part of my code.
I implemented it in the university sonata admin like this:
private function addUniToCourses ($university) {
foreach($university->getCourses() as $course) {
if(!$course->getUniversities()->contains($university)) {
$course->addUniversity($university);
}
}
}
public function prePersist($university) {
$this->addUniToCourses($university);
}
public function preUpdate($university) {
$this->addUniToCourses($university);
}
This was the solution to my problem.
I had the same problem a while ago: Symfony2, $form->bind() not calling adder methods of entity
Solution:
For the adder (addCourse()) to be called, you have to disable the by_reference option of the field:
->add('courses', 'genemu_jqueryselect2_entity',
array(
'by_reference' => false, // This line should do the trick
'multiple' => true,
'class' => 'PROJECT\UniBundle\Entity\Course'))
I am trying to reuse the same form for adding and editing employee information. I am using knockout js and on my view I make the knockout model:
var koModel = new EmployeeModel(div);
and if I want to populate the fields from the server I want to do something like this:
var koModel = new EmployeeModel(unserializedModelFromController, div);
I was wondering what is the best way to distinguish if the request is for a new employee or if it is to edit an existing employee.
If you turn your parameters around you can write a single constructor function.
var EmployeeModel = function(div, model) {
if (model) {
// Existing model has been passed, it's an edit request
} else {
// No model has been passed, it's a new request
}
}
This can be called like:
new EmployeeModel(div);
or
new EmployeeModel(div, model);
You can send a parameter with a default value to the view.
If you are editing an employee, you can send the value of id, you're creating not send.
The function that receives a request to store or edit could have a default value.
public void SaveOrEditEmployee(int id=0, ...) //id=0 is a default value
{
if(id==0)
{
//SaveEmployee
}else
{
//EditEmployee
Employee empl = (x => employee.id == id);
...
}
}
Or you can do likewise, receive full model and assess whether the property 'id' already exists in your database
I have a action class for saving some data to a database.In action class iam getting a id through url.I have to save the id in table.
I am getting the id by $request->getParameter('id')
I used this code for saving
$this->form->bind($request->getParameter('question_answers'));
if ($this->form->isValid())
{
$this->form->save();
$this->redirect('#homepage');
}
in model class i used a override save method to save extra field
public function save(Doctrine_Connection $conn = null)
{
if ($this->isNew())
{
$now=date('Y-m-d H:i:s', time());
$this->setPostedAt($now);
}
return parent::save($conn);
so i want to get the id value here .
So how can i pass id value from action class to model class
is any-other way to save the id in action class itself
Thanks in advance
Just use
$this->form->getObject()->setQuestionId($request->getParameter('id'));
$this->form->save();
QuestionId=field name
$request->getParameter('id')= is the default value
Most probably not the best solution but you can save that id value in a session variable and use it later anywhere you need. something like:
$this->getUser()->setAttribute('id', $request->getParameter('id')); // in action class
and
sfContext::getInstance()->getUser()->getAttribute('id'); // in model class
If your model has a field for this ID already (eg in your example posted_id), then you can do one of 2 things:
1: Pass the ID to your form when you create it, as an option:
$this->form = new MyForm(array(), array("posted_id" => $id));
and then you can override your form's doSave() method to set the field:
// ...
$this->getObject()->posted_id = $this->getOption("posted_id");
// ...
or similar
2: Add a widget to your form in the configure() method with a corresponding validator, and pass in the ID when you get the values from the request:
$values = $request->getParameter('question_answers');
$values["posted_id"] = $request->getParameter("id");
$this->form->bind($values);
If you're rendering your form manually, just don't render this new field in your view template.
Either way, saving the model as a result of the form saving it will include this new field I believe. The second one is from memory, but it should technically work... :-)
Best practice I'm using consists of 2 steps:
Pass additional data to forms' options (2nd constructor's parameter or just $form->setOption('name', 'value'));
override protected method doUpdateObject in way like this:
protected function doUpdateObject($values)
{
$this->getObject()->setFoo($this->getOption('foo'));
parent::doUpdateObject($values);
}
Enjoy!
You could use (sfUser)->setFlash and (sfUser)->getFlash instead of setAttribute, it's more secure...