How can I prevent CakePHP validating fields not in $fieldList? - php

I use CakePHP2.7.5 and my Model's save function is failing because it is trying to validate fields that are not specified in the $fieldList parameter.
According to the Cookbook, save method takes $fieldList as third parameter to limit the saved fields to those listed in $fieldList.
Model::save(array $data = null, boolean $validate = true, array $fieldList = array())
I call save in the model like this:
$this->save($data, true, ['name', 'place']);
$data is like this:
[ 'ModelName' => [ 'id' => $id, 'name' => 'abcdef', 'place' => 'ghijklmn' ] ]
but it fails because validation errors occur for the required fields that are not presented in $data. Is it supposed to work in this way?
Do I have to put some dummy data in $data for the required fields?

You probably need to set required to false in the validation rule:
public $validate = array(
'description' => array(
'alphaNumeric' => array(
'rule' => 'alphaNumeric',
'required' => false,
'message' => 'You have to enter a valid description'
)
);
}
CakePHP 2.x Validation: 'required'

I found the answer myself. In Cake's Model::save function, the second parameter $validate could be an array, and this problem occurs only when it is an array. (so, my example in the question above was not accurate.)
So, this one should work as intended (validates and saves 'name' and 'place' only),
$this->save($data, true, ['name', 'place']);
but this one doesn't,
$this->save($data, ['validate' => true], ['name', 'place']);
and actually I was doing like this. (because I had to make 'atomic' false and this is how to do it.)
$this->save($data, ['validate' => true, 'atomic' => false], ['name', 'place']);
In my case, this is the solution.
$this->save($data, ['validate' => true, 'atomic' => false, 'fieldList' => ['name', 'place']);

Related

Nested Parameters/Values in POST Requests

I have been thinking about a good way to handle nested/complex values in POST requests to a apigility resource.
For example, an order might contain a collection of order items in a single POST requested that is used to create an order. Both, order and order-item do exist as a resource. However, I would very much like to have only one request that would create order and order item entities. Handling that in the resource is not a problem, but I wonder how you would configure that resource (let´s call it order-place) using the apigiliy UI - or, if at all impossible, using the configuration. Applying validators and filters is one of the key features of apigility, and i´d like to keep using that, even for complex request data.
And before you ask, using an underscore to separate the values scopes, for example order_comment and order_item_comment should not be an option.
Any ideas?:)
Addition: A sample json request payload could look like this:
{
"created_at": "2000-01-01",
"amount" : "5000.00",
"address" : {
"name": "some name",
"street": "some street"
...
},
"items" : [
{"productId":99,"qty":1}
...
]
}
Starting from Wilt's answer, I've found that the following code works as well:
# file path: /module/MyApi/config/module.config.php
// some other stuff
'MyApi\\V1\\Rest\\MyRestService\\Validator' => array(
'address' => array(
0 => array(
'name' => 'name',
'required' => true,
'filters' => array(),
'validators' => array(),
),
1 => array(
'name' => 'street',
'required' => true,
'filters' => array(),
'validators' => array(),
),
'type' => 'Zend\InputFilter\InputFilter'
),
'amount' => array(
'name' => 'amount',
'required' => true,
'filters' => array(),
'validators' => array()
)
The only problem I get is when address is passed as a field (string or numeric) rather then an array or object. In this case Apigility throws an exception:
Zend\InputFilter\Exception\InvalidArgumentException: Zend\InputFilter\BaseInputFilter::setData expects an array or Traversable argument; received string in /var/www/api/vendor/zendframework/zendframework/library/Zend/InputFilter/BaseInputFilter.php on line 175
Adding address as a further simple (required) field avoids the exception, but then Apigility doesn't see any difference whether we pass address as an array of name and street or a dummy string.
If you are using the ContentValidation module then you can configure an input filter for the nested resources by assigning it to a variable. Then you have to add a type key (essential otherwise reusing the filter won't work). Now you are able to use this variable in your input_filter_specs and you can reuse the whole filter inside another filter. So something like this in your config.php:
<?php
namespace Application;
// Your address config as if it was used independently
$addressInputFilter => array(
'name' => array(
'name' => 'name',
'required' => true,
'filters' => array(
//...
)
'validators' => array(
//...
)
),
'street' => array(
'name' => 'street',
'required' => true,
'filters' => array(
//...
)
'validators' => array(
//...
)
),
// 'type' key necessary for reusing this input filter in other configs
'type' => 'Zend\InputFilter\InputFilter'
),
'input_filter_specs' => array(
// The key for your address if you also want to be able to use it independently
'Application\InputFilter\Address'=> $addressInputFilter,
// The key and config for your other resource containing a nested address
'Application\InputFilter\ItemContainingAddress'=> array(
'address' => $addressInputFilter,
'amount' => array(
'name' => 'amount',
'required' => true,
'filters' => array(
//...
),
'validators' => array(
//...
)
)
//... your other fields
)
)

Zf2 Callback Validator not passing all form values

Currently setting up a form using ZF2. I'd like to be able to setup validation for the form based on comparing the values of 2 different elements on the form. Where in the past I've used the Callback validator to pass in all of the forms values, it seems in my current scenario I can only get the value of the element being validated.
Here is how I setup my form
public function __construct(Di $di)
{
$this->setDi($di);
parent::__construct();
$inputFilter = $this->getInputFilter();
$this->addElement(
DateSelect::class,
[
'label' => 'Purchase Date'
], [
'name' => 'purchase_date',
'required' => 'true'
]);
$this->addElement(
Select::class,
[
'label' => 'What card type did you use to make the purchase',
'value_options' => [
'' => '',
'credit' => 'Credit',
'debit' => 'Debit'
]
],[
'required' => true,
'name' => 'card_type',
'helpText' => 'card type'
]);
$this->addElement(Submit::class, [],['value' => 'Claim']);
$inputFilter->add([
'name' => 'card_type',
'validators' => array(
array(
'name' => Callback::class,
'options' => array(
'messages' => array(
Callback::INVALID_VALUE => 'Item must have been purchased in the last 6 years in order to claim',
),
'callback' => function($value, $context = []) {
var_dump(func_get_args());
//I need to be able to get both card_type and purchase_date inputs here
die();
},
)
)
)
]);
}
The problem is that currently var_dump(func_get_args()) is currently only returning:
array(2) { [0]=> string(6) "credit" [1]=> array(1) { ["card_type"]=> string(6) "credit" } }
In the past I would have expected this to also pass in the purchase_date value as a part of the second argument of the callback.
Has anyone experienced this problem before? I'm using the callback function as expected in other areas of my application but can't seem to get it working here.
Thanks in advance
For clarification(although I don't believe it will have any effect on validation), the function $this->addElement is my own function, held on a trait and is simply:
public function addElement($class, $options, $attributes)
{
$this->add(
$this->getDi()->newInstance($class, ['options' => $options])->setAttributes($attributes)
);
}
Turns out that the callback will only get passed other values which the input filter is aware of.
The solution to this is:
$filter->add(['name' => 'card_type', 'required' => true]);

Zend_Validate_Db_RecordExists or empty value

I've got a (hopefuly) simple task, and my google fu has failed me. Basically, I've got a form with a select which contains an empty value and then number of ids given content can belong to. What I want to do is - validate if the given ids exist, but only if a value is set. This:
$field = new Zend_Form_Element_Select('field');
$field->addValidator(
new Zend_Validate_Db_RecordExists(
array(
'table' => 'categories',
'field' => 'id'
)
));
takes care of the checking if the given id exists, but I'm not able to find any way to omit this if value is empty. One way to do this would be to move this logic to isValid method, but I'm hoping there's nicer way to accomplish this task.
Try to set this form element as not required:
$field->setRequired(false);
When element is not required and is not filled, validators queue won't be run.
Quick example which works for me:
// Zend_Form form body
$this->addElement('select', 'category', array(
'label' => 'Choose category',
'required' => false,
'multiOptions' => array(
null => 'No category selected',
'1' => 'One',
'2' => 'Two',
),
'validators' => array(
array('Db_NoRecordExists', false, array(
'schema' => 'public',
'table' => 'category',
'field' => 'id',
)),
),
));

How to organize parsing and validation of REST API parameters?

I have a rest api that has many parameters via the query string. I am wondering if someone knows of a design pattern or has a nice way of organizing all the parameters (Objects, functions, array, json). Right now I am parsing and validating all my parameters in the same function, very ugly code.
Ideally I would like some way to handle the parameters similar to a database ORM or even a config file/array/json. However, I have tried to come up with a solution to this without any luck.
Any insight would be appreciated!
Example of my thoughts:
<?php
...
$parameters = [
// ?fields=id,name
'fields' => [
'default' => ['id', 'name'],
'valid' => ['id', 'name', 'date],
'type' => 'csv', // list of values (id & name)
'required' => ['id'],
'replace' => ['title' => 'name'], // if the database & api names don't match
'relation' => null, // related database table
],
// ?list=true
'list' => [
'default' => ['false'],
'valid' => ['true', 'false'],
'type' => 'boolean' // single value (true or false)
'required' => [],
'replace' => [], // if the database & api names don't match
'relation' => 'category', // related database table
],
....
];
Seems to me like you are looking for a validation library. My favorite is Symfony's: https://github.com/symfony/validator. I know Zend Framework 2 also has a validation component. I haven't used it personally, but I expect that to be very good too.
Example from the symfony/validator readme:
<?php
use Symfony\Component\Validator\Validation;
use Symfony\Component\Validator\Constraints as Assert;
$validator = Validation::createValidator();
$constraint = new Assert\Collection(array(
'name' => new Assert\Collection(array(
'first_name' => new Assert\Length(array('min' => 101)),
'last_name' => new Assert\Length(array('min' => 1)),
)),
'email' => new Assert\Email(),
'simple' => new Assert\Length(array('min' => 102)),
'gender' => new Assert\Choice(array(3, 4)),
'file' => new Assert\File(),
'password' => new Assert\Length(array('min' => 60)),
));
$input would be $_GET or something obtained with parse_str etc. It is also possible to define the validation rules in some other format, such as YAML.

Empty values passed to Zend framework 2 validators

How can I pass an empty value through Zend framework 2 ValidatorChain to my custom validator?
It was possible on ZF1 by allowEmpty(false)
On ZF2 with empty element value :
If allowEmpty = false, NotEmptyValidator is added to the top of ValidatorChain with breakOnFailure = true, #see Zend/InputFilter/Input#injectNotEmptyValidator.
If allowEmpty = true, Element is considered as Valid, #see Zend/InputFilter/BaseInputFilter#isValid
if ($input->allowEmpty()) {
$this->validInputs[$name] = $input;
continue;
}
continue_if_empty solved my problem. Thanks to #dson-horácio-junior. This is what I used:
$this->add(array(
'name' => 'field',
'continue_if_empty' => true,
'filters' => array(
array('name' => 'StripTags'),
array('name' => 'StringTrim')
),
'validators' => array(
array(
'name' => 'Application\Form\Validator\Sample'
)
)
));
public function isValid($value, $context = null)
{
if ($value == '' && $context['otherfield'] == '') {
$this->error(self::INVALID_FIELD);
return false;
}
// ...
}
Following works for ZF2 version 2.1.1:
The problem (if I got it correctly) is that in following example, for empty values of 'fieldName', no validation is triggered. This can be quite annoying, though in
$input = new \Zend\InputFilter\Input('fieldName');
$input
->setAllowEmpty(true)
->setRequired(false)
->getValidatorChain()
->attach(new \Zend\Validator\Callback(function ($value) {
echo 'called validator!';
return true; // valid
}));
$inputFilter = new \Zend\InputFilter\InputFilter();
$inputFilter->add($input);
$inputFilter->setData(array('fieldName' => 'value'));
var_dump($inputFilter->isValid()); // true, echoes 'called validator!'
$inputFilter->setData(array('fieldName' => ''));
var_dump($inputFilter->isValid()); // true, no output
$inputFilter->setData(array());
var_dump($inputFilter->isValid()); // true, no output
This is quite annoying when you have particular cases, like checking an URL assigned to a page in your CMS and avoiding collisions (empty URL is still an URL!).
There's a way of handling this for empty strings, which is to basically attach the NotEmpty validator on your own, and avoiding calls to setRequired and setAllowEmpty. This will basically tell Zend\InputFilter\Input#injectNotEmptyValidator not to utomatically attach a NotEmpty validator on its own:
$input = new \Zend\InputFilter\Input('fieldName');
$input
->getValidatorChain()
->attach(new \Zend\Validator\NotEmpty(\Zend\Validator\NotEmpty::NULL))
->attach(new \Zend\Validator\Callback(function ($value) {
echo 'called validator!';
return true; // valid
}));
$inputFilter = new \Zend\InputFilter\InputFilter();
$inputFilter->add($input);
$inputFilter->setData(array('fieldName' => 'value'));
var_dump($inputFilter->isValid()); // true, echoes 'called validator!'
$inputFilter->setData(array('fieldName' => ''));
var_dump($inputFilter->isValid()); // true, echoes 'called validator!'
$inputFilter->setData(array());
var_dump($inputFilter->isValid()); // false (null was passed to the validator)
If you also want to check against null, you will need to extend Zend\InputFilter\Input as following:
class MyInput extends \Zend\InputFilter\Input
{
// disabling auto-injection of the `NotEmpty` validator
protected function injectNotEmptyValidator() {}
}
This triggered validation of my Callback validator when the value was an empty string:
'required' => false,
'allow_empty' => false,
'continue_if_empty' => true,
'validators' => array(
array(
'name' => 'Callback',
'options' => array(
'callback' => function ($value, $context = []) use ($self) {
// ...
}
)
)
)
The allow_empty initially invalidates the empty string and the continue_if_empty allows it to then be evaluated by the validators that follow.
I see often the people making the mistake using allowEmpty in the inputFilter config arrays. The string should be written with underscore separation not with camel case. So allow_empty will work:
'fieldName' => array(
'name' => 'fieldName',
'required' => true,
'allow_empty' => true,
'filters' => array(
//... your filters ...
)
'validators' => array(
//... your validators ...
),
);
meaning a field with key 'fieldName' must be present in the data, but its value is allowed to be empty.
If you like to use a separate form validate class or a array notation for validate, you can do as follows:
$factory = new Zend\InputFilter\Factory();
$inputFilter = new Zend\InputFilter\InputFilter();
$inputFilter->add($factory->createInput(array(
'name' => 'name',
'required' => false,
'allowEmpty' => true,
'filters' => array(
array('name' => 'StripTags'),
array('name' => 'StringTrim'),
),
'validators' => array(
array(
'name' => 'StringLength',
'options' => array(
'encoding' => 'UTF-8',
'min' => '8',
'max' => '100',
),
),
),
)));
You can pass an array with required => false and allowEmpty => true to input filter factory (as I remember you can pass it directly to input filter too - not so sure).

Categories