Some form fields in my laravel 5.5 application have validation rules that run against a remote API and take quite some time. I would thus only want to run these expensive checks when the field value changes (is different from the value currently stored in the model).
Is there something that implements this already, e.g. as a rule similar to sometimes?
I would image it like this: only_changed|expensive_validation|expensive_validation2. The latter rules would only be executed when the field value has changed.
Assuming you are using a custom Request class, the rules() method expects an associative array to be returned before it applies the validation.
You can build your array of rules dynamically using the request contents before applying the validation like so:
/**
* Get the validation rules that apply to the request.
*
* #return array
*/
public function rules()
{
$validation_array = [
'name' => 'required|string|max:255',
];
if ($this->my_field === $some_condition) {
$validation_array = array_merge($validation_array. [
'my_field' => "required|expensive_validation|expensive_validation2"
]);
}
return $validation_array;
}
Note: I haven't run this yet but the principle should be fine.
Related
I use AuthComponent with CakePHP 3.8 and now I need to do some logic in Model buildRules method but for this I need to get the current user ID.
Is there any way to pass/retrieve it without using hacks such as accessing directly from the session.
I know that it is possible to pass id via validator from controller as described in CakePHP's documentation
https://book.cakephp.org/3/en/core-libraries/validation.html#using-custom-validation-rules
And it works for validation, however, I am unable to access the validator from the inside of build rules.
When I do as described in here, I get an empty object.
https://book.cakephp.org/3/en/orm/validation.html#using-validation-as-application-rules
It seems that I am able to attach new validation rules but unable to retrieve the "Passed" provider to get the User ID.
It seems a trivial thing but a I spent quite a few hours trying to get the id in a proper way.
OK, After working a bit more, I found how to retrieve user_id inside build rules. Might be helpful to someone.
Do this in the controller
$this->ExampleModel->validator('default')->provider('passed', [
'current_user' => $this->Auth->user('id')
]);
And then put this in you buildRules method
public function buildRules(RulesChecker $rules)
{
$user_id = $this->validator('default')->provider('passed')['current_user'];
$rules->add(
function ($entity, $options) use($user_id) {
//return boolean for pass or fail
},
'ruleName',
[
'errorField' => 'some_id',
'message' => 'Some ID field is inconsistent with App logic.'
]
);
return $rules;
}
The proper way to handle this is usually using either events, or saving options.
For example to make the ID available for the application rules of all tables, you could do something like this:
$this->getEventManager()->on('Model.beforeRules', function (
\Cake\Event\Event $event,
\Cake\Datasource\EntityInterface $entity,
\ArrayObject $options
) {
$options['current_user'] = $this->Auth->user('id');
});
It would be available in the $options argument of the rule accordingly, ie as $options['current_user'].
For a specific save operation you can pass it to the options of the save() call:
$this->ExampleModel->save($entity, [
'current_user' => $this->Auth->user('id')
]);
There's also plugins that can help you with it, for example muffin/footprint.
Laravel Form Validation is great, except that it doesn't filter out extraneous keys when array notation is used.
I am type hinting the form request in my controller.
public function saveEdit(Post\EditRequest $request)
{
$valid = $request->validated();
}
If my form has address_1 and address_2, and the user spoofs address_3, the spoofed value will not turn up in $valid.
However, if my form uses array notation, such as venue[address_1], and venue[address_2], then the user can spoof venue[address_3], and it will turn up in $valid.
Has anyone else come across this? How did you deal with it?
It seems like the validated() method on the form request class needs to operate recursively.
Unfortunately, Laravel doesn't include built-in support for validating array keys yet, only array values. To do so, we need to add custom validation rules.
However, with alternative protection like $fillable model attributes and the array_only() helper function, creating these rules is a lot of extra work for a very unlikely edge case. We provide validation feedback for expected user error and sanitize to protect our data from unexpected input.
If the user spoofs a key, as long as we don't save it, or we filter it out, they won't be too concerned if they don't see a pretty validation message—it doesn't make sense to validate a field that shouldn't exist in the first place.
To illustrate how sanitization works, here's a Venue model that declares its fillable attributes:
class Venue extends Model
{
protected $fillable = [ 'address_1', 'address_2' ];
...
}
Now, if a malicious user attempts to spoof another attribute in the array:
<input name="venue[address_1]" value="...">
<input name="venue[address_2]" value="...">
<input name="venue[address_3]" value="spoof!">
...and we use the input array directly to update a model:
public function update(Request $request, $venueId)
{
$venue = Venue::find($venueId);
$venue->update($request->venue);
...
}
...the model will strip out the additional address_3 element from the input array because we never declared it as a fillable field. In other words, the model sanitized the input.
In some cases, we may need to simply sanitize array elements without using Eloquent models. We can use the array_only() function (or Arr:only()) to do this:
$venueAddress = array_only($request->venue, [ 'address_1', 'address_2' ]);
you can use dot notation
public function rules()
{
return [
'venue.address_1' => 'required',
'venue.address_2' => 'required',
'venue.address_3'=>'required',
];
}
and if you are using dynamic array you can do same by
public function rules()
{
return [
'venue.address_.*' => 'required'
];
}
I'm having some difficulties validating a I18N field in CakePHP3.
The translate behavior is setup like this:
$this->addBehavior('Translate', [
'fields' => ['name', 'body', 'slug'],
'validator' => 'default'
]);
Like advertised here: https://book.cakephp.org/3.0/en/orm/behaviors/translate.html#validating-translated-entities
The core validation is working properly. I have a validation rule in validationDefault function that checks if name is not empty and it works fine. Though, I also would like to add some application rules on top of this validation. The "name" field should have a unique value. I don't want to allow multiple entities with the same translated name.
This piece of code apparently doesn't work. CakePHP docs also are quite silent about this matter.
public function buildRules(RulesChecker $rules) {
// prevent duplicate creation
$rules->add($rules->isUnique(['name']));
return $rules;
}
Is this actually possible?
Thanks
What you are doing there is creating a rule for the name field on the main model, this won't affect translations. There is no built-in functionality for that, the behavior only assists with validation rules by making use of the validationTranslated() method in case it exists on your model class, it won't help with application rules.
You'd have to create a custom application rule that checks the translation table, by matching against the field, locale, model and content fields, something along the lines of this:
$rules->add(
function (EntityInterface $entity) {
$behavior = $this->behaviors()->get('Translate');
$association = $this->association($behavior->getConfig('translationTable'));
$result = true;
foreach ($entity->get('_translations') as $locale => $translation) {
$conditions = [
$association->aliasField('field') => 'name',
$association->aliasField('locale') => $locale,
$association->aliasField('content') => $translation->get('name')
];
if ($association->exists($conditions)) {
$translation->setErrors([
'name' => [
'uniqueTranslation' => __d('cake', 'This value is already in use')
]
]);
$result = false;
}
}
return $result;
}
);
Note that this uses the association object rather then the target table, this will ensure that further conditions like the model name are being applied automatically.
Also this requires to set the errors on the entity manually, as they are nested, which isn't supported by the rules checker, currently it can only set errors on the first level entity (see the errorField option).
It should also be noted that it would be possible to modify the rules checker for the translation table association (via the Model.buildRules event), however this would result in the errors being set on new entities that will be put in a separate property (_i18n by default) on the main entity, where the form helper won't find the error, so one would then have to read the error manually, which is a little annoying.
See also
Cookbook > Database Access & ORM > Validation > Applying Application Rules
I am trying to test the store method in the simplest way possible. I don't necessarily need Mockery, I'm fine with actually adding to the database. I couldn't get Mockery to work anyway.
Now I have this:
public function testStore()
{
$data = ['name' => 'TestClub', 'code' => 'TCL'];
Input::replace($data);
$this->route('POST', 'clubs.store', $Input::all());
$this->assertResponseOk();
}
This is my controller method:
public function store() {
$validator = Validator::make($data = Input::all(), Club::$rules);
if ($validator->fails()) {
return Redirect::back()->withErrors($validator)->withInput();
}
Club::create($data);
return redirect(Session::get('backUrl'));
}
I get a return code 500, I have no idea why. Can I somehow see the request it's sending?
I am using Laravel 5.0
First, never use Input::all(). You may not know what's in the form when it's submit, instead use only.
However, you also shouldn't be putting validation logic in the Controller if you can help it. Instead, you should make a new Request and put your validation rules in the rules array. Let's look at that
php artisan make:request StoreUserRequest
Then inside of this Request, you'll add return true to your authorize function (or whatever logic you need to identify if the user should be able to make the request), and then your rules to the rules() function (to the array within the rules function, to be more specific).
Now add your new StoreUserRequest as a dependency to the first argument of the store() function.
public function store(App\Http\Requests\StoreUserRequest $request)
Now use the $request->only() method to get your fields back.
Club::create($request->only(['field1', 'field2', 'blob1', 'text1']);
Furthermore, make sure that any fields you wish to add data to are listed within the protected $fillable array in your Club model.
protected $fillable = ['field1', 'field2', 'blob1', 'text1'];
This should do it.
I'm currently building a form manager in PHP to validate large forms.
I'm wondering what is the best structure for that, because the number of fields will be different each time.
I'm already filtering the field to validate by using a prefix (ex : 'user_name', will be validated, but 'name' no).
My real problem is for the validation : I must check the type of the field (mail, zipcode, phone...)
AND check that the value for this type of field is valid.
I thought that I could use HTML5 Custom data" (ex: data-fieldtype="zipcode"), but i didn't know that the server can't get this attribute...
What's the way to go ?
I could use 2 inputs per field, one for the value and one for the type, but it looks really stupid !
Thanks if you can help.
EDIT :
Your answers are all interesting, i don't know which is best.
I will probably make a mix between your solutions, depending of the kind of form.
Thanks a lot.
Methinks, this shouldn't be played via the Browser without further thought: A malicious user would be able to manipulate a "INT ONLY" field into being freetext, and your application would suddenly have to deal with freetext in a field, that is thought to be validated as an integer (und thus e.g. safe for SQL).
You have two approaches:
Have your form validation structure stored in the DB, and submit a single hidden field, that carries the ID of the validation structure. On receiving the request, your script would request the structure from the DB, unserialize it, and work on it.
If you really need to go through the browser, serialize your validation structure, base64-encode it and use it as a single hidden field. For the reasons stated above, it is mandatory to authenticate this value, either hashing (concatenate with another string only known to the server, hash it, send the hash as a second hidden field, on next request verify the hash is correct) or by encryption (encrypt the serialized data before the browser roundtrip, decrypt afterwards, key known only to the server)
Both would make your prefix-trick unnecessary, increasing readability and maintainability.
If no framework is used, you can use an array of field => options.
$rules = [
'user_name' => 'required',
'user_email' => 'email required',
// ...
];
And then feed them to some validator class, where rules are methods and they're being called inside validate method dynamically:
class Validator {
public function __construct($data) { $this->data = $data; }
private function required($field) {}
private function email($email) {}
// etc
/** #method bool validate(array $rules) */
public function validate($rules) {}
}
Never ever validate on client-side only. Always validate serverside.
Usually you will have a base class for controller, and every other controller extends it.
Aa good approach is to have en every view controller (or the bootsrtap) a method check_params().
It should 1) get a copy or $_REQUEST, check every parameter needed, 2) delete $_REQUEST, 3) write back the checked and validated params.
abstract class controller_base {
public function __construct() { ...; $this->check_param();...}
protected final function check_param() {
foreach ($this->param_list() AS $name => $type) {...}
}
abstract public function param_list();
}
class controller_login extends controller_base {
public function param_list() {
return array('name' => 'string', 'password' => 'string');
}
}
The idea is that this way you
only use params that has been sanitized
you autmaticly delete every param not needed
you have a list in every controller that states the used params.