How to pass in a parameter to a custom Zend validator? - php

I am trying to write a validator in Zend framework.
The validator queries the database to check if a particular record exists, this query uses a where clause. The value for the where clause is specified by another field on the form, so how do I pass this value into the validator?
This is how I add my validator:
$adgroup_name->addValidator(new Generic_ValidateUniqueAdGroupName() ); break;
Within my validator I have:
// Query which gets an array of existing ad group names in db
$q = Doctrine_Query::create()
->select('a.name')
->from('AdGroup a')
->where('a.name = ?', $value)
->andWhere('a.campaign_id = ?', $campaign_id);
$adgroup_names_result = $q->fetchOne(array(), Doctrine::HYDRATE_ARRAY);
How do I pass in $campaign_id? I've tried the following and it doesn't work:
$adgroup_name->addValidator(new Generic_ValidateUniqueAdGroupName($campaign_id) ); break;

The principle is the same as used in "confirm password" validators. You need the value of another element in the form in the field when the form is submitted. That's why attempting to instantiate the validator with the campaign_id is not working: the value of $campaign_id is not yet set.
When an element calls isValid() on its validators, it passes a second parameter called $context that is filled with an array of submitted form values. So your isValid() method in your validator should look something like this:
public function isValid($value, $context = null)
{
$campaign_id = $context['campaign_id'];
// Now build your query using $value - from the element to which this
// validator is attached - and the $campaign_id provided by the context.
// And, of course, return true/false as required.
}

I think to make new Generic_ValidateUniqueAdGroupName($campaign_id) work you need to define a constructor for Generic_ValidateUniqueAdGroupName class and a variable called e.g. $_campaign_id. A draft is below:
class Generic_ValidateUniqueAdGroupName extends Zend_Validate_Abstract {
protected $_campaign_id;
public function __construct($campaign_id) {
$this->_campaign_id = $campaign_id;
}
}
With this, you would access the id in your query as $this->_campaign_id.

You need to supply the $options argument:
Implementation in Zend_Form_Element:
public function addValidator($validator, $breakChainOnFailure = false, $options = array())
So, you'd do something like this:
$validator = new new Generic_ValidateUniqueAdGroupName();
$adgroup_name
->addValidator($validator, false, array('campaign_id' => 1));

Related

Update values when null

I have a form that is sending the values to the controller in this way:
public function postFormUpdate(ProjectUpdateRequest $request)
{
$inputs = $request->all();
$project = $this->projectRepository->update($inputs['project_id'],$inputs);
//...
}
The project repository is done this way:
public function update($id, Array $inputs)
{
return $this->save($this->getById($id), $inputs);
}
private function save(Project $project, Array $inputs)
{
// Nullable
if (isset($inputs['project_type'])) {$project->project_type = $inputs['project_type'];}
if (isset($inputs['activity_type'])) {$project->activity_type = $inputs['activity_type'];}
...
}
My problem is if the project_type is null from the form field (the project type doesn't need to be entered or can be removed), then isset($inputs['project_type']) will be false and the update will not be triggered.
What I want is if the user had set up a project type and then wants to change it and set it to null, like this, it is not working. I use the isset because sometimes I update only one field and I don't want this to generate an error because it was not part of the inputs and was not set.
What I can do is use:
if (isset($inputs['project_type']) || is_null($inputs['project_type'])) {$project->project_type = $inputs['project_type'];}
But I am looking if there is a more elegant way to do this.
Thanks.
A more elegant way would be to see if anything is set under the key.
so go with array_key_exists
if ( array_key_exists('project_type', $inputs) ){$project->project_type = $inputs['project_type'];}
You may see it in action here

Symfony2 Form Event PreSetData Subscriber

In my Application the user can create Custom Fields for some entities and then set the values for this custom fields for each entity object when i display a form.
The implementation is like this:
1º) I created a Interface for the forms, and the forms that i want implement this Interface.
2º) I created a form extension for all forms:
app_core_form_builder.form_extension:
class: App\Core\Bundle\FormBuilderBundle\Form\FormExtension
arguments: ["#service_container", "#doctrine.orm.entity_manager"]
tags:
- { name: form.type_extension, alias: form }
3º) In this extension if the form implements the interface referenced in the step 1 i add a EventSubscriber:
if($formType instanceof \App\Core\Bundle\FormBuilderBundle\Model\IAllowCustomFieldsdInterface){
$builder->addEventSubscriber(new FormSubscriber($this->container, $this->em));
}
4º) This Form Subscriber subscribes the preSetData FormEvent. In this method i get the Entity associated with the form and i get all custom fields created for it.
Then i add this fields to the form with the help of Symfony2 Form Type.
Everything goes well, and when i display my form the custom fields are rendered correct. Just for the record, when i save the form the values inserted in the custom fields also are store well.
public function preSetData(FormEvent $event) {
$data = $event->getData();
$form = $event->getForm();
// During form creation setData() is called with null as an argument
// by the FormBuilder constructor. You're only concerned with when
// setData is called with an actual Entity object in it (whether new
// or fetched with Doctrine). This if statement lets you skip right
// over the null condition.
if (null === $data) {
return;
}
$formEntity = $form->getConfig()->getType()->getInnerType()->getEntity();
$DbEntity = $this->em->getRepository('AppCoreSchemaBundle:DbEntity')->findOneBy(array('id' => $formEntity));
if ($DbEntity && $DbEntity->getAllowCustomFields()) {
$organization = $this->container->get('app_user.user_manager')->getCurrentOrganization();
if (!$organization) {
throw $this->createNotFoundException('Unable to find Organization entity.');
}
$params = array(
'organization' => $organization,
'entity' => $DbEntity,
);
$entities = $this->em->getRepository('AppCoreSchemaBundle:DbCustomField')->getAll($params);
# RUN BY ALL CUSTOM FIELDS AND ADD APPROPRIATE FIELD TYPES AND VALIDATORS
foreach ($entities as $customField) {
# configurate customfield
FieldConfiguration::configurate($customField, $form);
# THE PROBLEM IS HERE
# IF OBJECT IS NOT NULL THEN MAKE SET DATA FOR APPROPRIATED FIELD
if ($data->getId()) {
$filters = array(
'custom_field' => $customField,
'object' => $data->getId(),
);
$DbCustomFieldValue = $this->em->getRepository('UebCoreSchemaBundle:DbCustomFieldValue')->getFieldValue($filters);
if ($DbCustomFieldValue) {
$form[$customField->getFieldAlias()]->setData($DbCustomFieldValue->getValue());
} else {
$form[$customField->getFieldAlias()]->setData(array());
}
}
}
}
}
The problem is when i try to edit a form. if you look at the part in the code above where says "THE PROBLEM IS HERE" you can understand.
If the object of the form has an ID, then i will get the values stored for the custom fields of that object, and i call $form[field_alias']->setData(value returned from database that is mapped as type Array).
But this dont work, and the Data is not set for the fields. But if in my controller i do the same, the data is set properly.
Does anybody have an idea where the problem can be? Can't i set the data in preSetData Event?
EDITED
The value field from the Entity DbCustomField is mapped in this way:
/**
* #var string
*
* #ORM\Column(name="value", type="array", nullable=true)
*/
protected $value;
`
var_dump($DbCustomFieldValue) -> object(Ueb\Core\Bundle\SchemaBundle\Entity\DbCustomFieldValue)
var_dump(DbCustomFieldValue->getValue())
-> string(11) "bruno valor"
But even if i try something like:
var_dump($customField->getFieldAlias()); = string(21) "testebruno-1383147874"
$form[$customField->getFieldAlias()]->setData('example1'); it doesnt work.
But in my controller if i do the following for the fieldAlias above:
$form['testebruno-1383147874']->setData('example2');
-> it does work
Any idea?
As metalvarez suggested in his/her comment and working as expected, use the postSetData event instead of the preSetData one:
public function postSetData(FormEvent $event) {
// ...
}
The preSetData event method is called before populating the form with default values, then Symfony2 will set the data and it may change from what you set before, thus the use of postSetData instead.
Figure from the doc

Pass findBy parameter to callback method afterFind

I am trying to catch a certain findBy call (with afterFind) where:
if $results is empty (or the value you are trying to find is nonexistent), but the parameter value is found on another table, then it will modify $results to be valid
Some controller action got this:
$this->User->findByUsername("Bingo"); // yes, username Bingo doesnt exist on users table
User model:
function afterFind($results, $primary){
if(empty($results)) {
if(in_array($findbyparameter, array("Bingo", "Bingo1", "Bingo2"))) {
// modify $results
}
}
}
The problem is, how do I get $findbyparameter?
Thanks! All help will be appreciated!
I am not using these convenience methods, but you can pass the variable as Model property like this:
//where you search
$this->User->searchPhrase = "Bingo";
findByUsername($this->User->searchPhrase);
//Model
function afterFind($results, $primary){
if(empty($results)) {
if(in_array($this->searchPhrase, array("Bingo", "Bingo1", "Bingo2"))) {
// modify $results
}
}
}
It's not the prettiest method, but I guess it would work. Try to print_r($this) in afterFind method and see if you can spot somewhere the phrase which you search. I believe it's passed in the condition's array.
Perhaps a custom find type is what you're looking for. Custom find types have two states: before and after.
In the before you would setup your condition, and in the after you would check your data and modify if necessary. In both states you will have access to the query options.
Setting up custom finds is slightly different in 1.x and 2.x (you haven't mentioned which version you're using), so you can look up the specifics in the book.
In short, you would add add your the find type into the $findMethods property of the model and then add the corresponding method name to your model. Say you call your custom find type 'byUsername'
protected function _findByUsername($state, $query, $results = array()) {
if ($state === 'before') {
// add your condition to the query,
return $query;
} elseif ($state === 'after') {
// modify $results if you need to
return $results;
}
}
And you would call it via $this->User->find('byUsername', array('username' => $username));
In $query you would have the key 'username' which you can add to the conditions key of $query. In both states, you would have access to $query['username'].

Filtering form input within Symfony form class

I would like to filter some fields in my form with strtolower() function. Unfortunately I can't find any example of doing that.
How can I write such filter, that will lowercase the input, check the database if element exists and then decide wheter to add the record or not?
1) new project custom validator (we will use it like value filter here):
/lib/validator/MyProjectStringLowerCase.class.php
<?php
class MyProjectStringLowerCase extends sfValidatorBase
{
/**
* #see sfValidatorBase
*/
protected function doClean($value)
{
return strtolower($value);
}
}
2) bound it to field:
$this->setWidget('my_field_name', new sfWidgetFormInputText());
$this->validatorSchema['my_field_name'] = new MyProjectStringLowerCase();
If you have some validator on that field already, you can merge them into combined validators this way:
$this->validatorSchema['my_field_name'] = new sfValidatorAnd(array(
$this->validatorSchema['my_field_name'], // the original field validator
new MyProjectStringLowerCase(),
));
The combined validators order influent how value will flow trough them, so if you want to have value filtrated in second validation, set MyProjectStringLowerCase as the first one.
There are 2 differences between this approach and using post processing (like doSave() for instance):
the value here will be filtered after each send (and will show
filtered in displaying of form errors)
You can reuse it very cleanly and easily in other fields or forms in
your project
In your Form, you can override the doSave() method to do any manual interventions that you need to do that aren't completed by the form validation methods.
For example:
public function doSave($con = null) {
$employee = $this->getObject();
$values = $this->getValues();
// do your filter
$this->values['name'] = strtolower($values['name']);
parent::doSave($con);
}

PHP OOP filter validation on email array

I'm using a validation class by David Powers (from the book PHP Object Oriented Solutions) for filtering input data. Below is a function to filter a single email address. How do I modify this function to filter an array of email addresses? (I have a dynamic form which may contain a variable number of email addresses and some other data relating to it: both arrays are the same length and these filters should not remove values from the arrays, just to report if any of the values are invalid.)
/**
* Checks whether the input conforms to the format of an email address.
*
* It does not check whether the address is genuine. Multiple addresses are
* rejected, guarding against email header injection attacks.
*
* #param string $fieldName Name of submitted value to be checked.
*/
public function isEmail($fieldName)
{
// Check that another validation test has not been applied to the same input
$this->checkDuplicateFilter($fieldName);
// Set the filter options
$this->_filterArgs[$fieldName] = FILTER_VALIDATE_EMAIL;
}
Here's the function for validating integers. What would it require to make this function accept an array of integers? (I commented out the min, max options from the function. They are not mandatory in this array validator. But feel free to help in one way or another...)
public function isInt($fieldName/*, $min = null, $max = null*/)
{
// Check that another validation test has not been applied to the same input
$this->checkDuplicateFilter($fieldName);
// Set the filter options
$this->_filterArgs[$fieldName] = array('filter' => FILTER_VALIDATE_INT);
// Add filter options to test for integers within a specified range
//if (is_int($min)) {
// $this->_filterArgs[$fieldName]['options']['min_range'] = $min;
//}
//if (is_int($max)) {
// $this->_filterArgs[$fieldName]['options']['max_range'] = $max;
//}
}
These were two of the public methods of validation class. Please let me know if I should attach the class constructor or the public function which actually performs the validation. Right now I'm guessing you don't need to see them but my OOP skills are quite poor. Any help is very much appreciated.
UPDATED: added the constructor
/**
* Constructs a validator object for $_POST or $_GET input.
*
* The constructor checks the availability of the PHP filter functions for which it
* acts as a wrapper. If the optional first argument (an array of required fields) is
* supplied, it checks that each one contains at least some input. Any missing items are
* stored as an array in the $missing property.
*
* By default, the constructor sets the input type to "post". To create a validator for
* the $_GET array, set the optional second argument to "get".
*
* #param array $required Optional array containing the names of required fields or URL variables.
* #param string $inputType Type of input; valid options: "post" and "get" (case-insensitive); defaults to "post"
* #return Pos_Validator object
*/
public function __construct($required = array(), $inputType = 'post')
{
if (!function_exists('filter_list')) {
throw new Exception('The Pos_Validator class requires the Filter Functions in >= PHP 5.2 or PECL.');
}
if (!is_null($required) && !is_array($required)) {
throw new Exception('The names of required fields must be an array, even if only one field is required.');
}
$this->_required = $required;
$this->setInputType($inputType);
if ($this->_required) {
$this->checkRequired();
}
$this->_filterArgs = array();
$this->_errors = array();
$this->_booleans = array();
}
UPDATE 2: I realized that after filtering any invalid array values are labeled as bool(false). So the function below works but it doesn't generate validation error same way as the single value filter functions do.
public function isIntArray($fieldName)
{
// Check that another validation test has not been applied to the same input
$this->checkDuplicateFilter($fieldName);
// Set the filter options
$this->_filterArgs[$fieldName]['filter'] = FILTER_VALIDATE_INT;
$this->_filterArgs[$fieldName]['flags'] = FILTER_REQUIRE_ARRAY;
//is is possible to mark the whole array to false or
//somehow break the whole script?
}
The function above strips away invalid values and database cells are filled with blanks if no other precaution are taken. I think I'll check the validated array (with foreach loop) after filtering and if any of the array values is false, I break the script somehow. (There's no point inserting empty values in the database is they suppose to be valid email addresses or integers.) This solution works for now but I bet there is a more elegant and efficient way to do this. Thank you for your help and suggestions. I'm still open for new ideas.
-Jouni
You could just call the isInt function multiple times and have the loop on the outside of the function. What does the constructor look like?
If you really want to do it you way you could change the function to look something like this:
public function isInt( $my_array ) {
foreach( $my_array as $element ) {
$this->checkDuplicateFilter( $element );
$this->_filterArgs[$element] = array('filter' => FILTER_VALIDATE_INT);
}
}
If you like, post the constructor on here and we can show you how to instantiate it and validate your inputs that way instead.
UPDATE (Now constructor has been posted)
What's the name of the class? Let's assume it's called Validator (change to what it is really called)
$validator = new Validator( /* Add your optional arguments here */ );
/* change $inputs to the name of the array containing all your inputs */
foreach( $inputs as $input ) {
// validate each input
if( $validator->isInt( $input ) ) {
// int is valid, do something
} else {
// int isn't valid, do something else
}
}
Then do something similar for isEmail for your e-mail inputs.
you can also use array_map.
From php.net:
<?php
function cube($n)
{
return($n * $n * $n);
}
$a = array(1, 2, 3, 4, 5);
$b = array_map("cube", $a);
print_r($b);
?>
Array
(
[0] => 1
[1] => 8
[2] => 27
[3] => 64
[4] => 125
)
So in your case, you'd use:
(Updated to use object)
array_map(array($this, $this->isEmail), $emailArray);
array_map(array($this, $this->isInt), $integerArray);
Fairly simple I think...
UPDATED, see below.
I think I have a acceptable solution for this so I just answer this little more precisely than in the comments. Here's the whole class but I made some modifications to it.
http://codepad.org/sCC0sruO
First I added a new protected value to the class: protected $_arrayHasFalse;
These two validating functions valide arrays of values. They mark the invalid values as false so a little more work has to be done.
public function isIntArray($fieldName) {
// Check that another validation test has not been applied to the same input
$this->checkDuplicateFilter($fieldName);
// Set the filter options
$this->_filterArgs[$fieldName]['filter'] = FILTER_VALIDATE_INT;
$this->_filterArgs[$fieldName]['flags'] = FILTER_REQUIRE_ARRAY;
}
public function isEmailArray($fieldName){
// Check that another validation test has not been applied to the same input
$this->checkDuplicateFilter($fieldName);
// Set the filter options
$this->_filterArgs[$fieldName]['filter'] = FILTER_VALIDATE_EMAIL;
$this->_filterArgs[$fieldName]['flags'] = FILTER_REQUIRE_ARRAY;
}
This function checks if any of the array values is invalid. It assigns the arrayHasFalse to true if everything is not valid. (This is a minor flaw as it does not remember all the invalid values. But I'm using it in a context where everything should validate or nothing is inserted into database so I am ok with this solution.)
public function checkArrayForFalseValues($array){
foreach($array as $key => $value){
if ($value == false){
$this->_arrayHasFalse = true;
}
}
}
Finally we need a getter function.
public function getArrayFalseRevealer(){
return $this->_arrayHasFalse;
}
So this is the deal in the form process file...
$val = new validator;
$val->isIntArray('AllShouldBeIntegers'); //dynamically expandable form field
$val->isEmailArray('AllShouldBeEmails'); //dynamically expandable form field
//these are in the original class (pretty self explanatory functions I think)
//validated input is stored in $filtered
$filtered = $val->validateInput();
$missing = $val->getMissing();
$errors = $val->getErrors();
//after the validation we go through a bit more work
$val->checkArrayForFalseValues($filtered['AllShouldBeIntegers']);
$val->checkArrayForFalseValues($filtered['AllShouldBeEmails']);
//mastervalue is set to true if anything is invalid
//maybe this function should be named: $doesAnyCheckedArrayHasEvenASingleInvalidValue
$arrayHasFalse = $val->getArrayFalseRevealer();
//proceed if everything is OK
if (!$missing && !$errors && !$arrayHasFalse) {// Everything passed validation.
//do the database magic here...
}
I guess this does the trick. I'll mark this as an accepted answer. Feel free to comment if you have suggestions for improvements.
Thanks everyone.
UPDATE: Improved the array validator. Let's pass the filter as an argument (or is it a parameter?). It is called for example like this: $val->isArrayAcceplable('AllShouldBeEmails', 'email');
public function isArrayAcceplable($fieldName, $filter){
//Check that another validation test has not been applied to the same input
$this->checkDuplicateFilter($fieldName);
//set the desired filter
//add more options for your convenience
switch($filter){
case 'email':
$this->_filterArgs[$fieldName]['filter'] = FILTER_VALIDATE_EMAIL;
break;
case 'int':
$this->_filterArgs[$fieldName]['filter'] = FILTER_VALIDATE_INT;
break;
}
//we are validating an array
$this->_filterArgs[$fieldName]['flags'] = FILTER_REQUIRE_ARRAY;
}
//just remember to check the array for false values. (See above.)
-Jouni

Categories