I'm using the dwightwatson/validating package to create validation rules in the model.
I particularly like the custom rulesets you can create for different routes.
Model
protected $rulesets = [
'set_up_all' => [
'headline' => 'required|max:100',
'description' => 'required'
],
'set_up_property' => [
'pets' => 'required'
],
'set_up_room' => [
'residents_gender' => 'required',
'residents_smoker' => 'required'
],
'set_up_roommate' => [
'personal_gender' => 'required',
'personal_smoker' => 'required'
]
];
Controller
$post = new Post(Input::all());
if($post->isValid('set_up_all', false)) {
return 'It passed validation';
} else {
return 'It failed validation';
}
In the above example, it works well in validating against the set_up_all ruleset. Now I would like to combine several rulesets and validate against all of them together.
According to the documentation, the package offers a way to merge rulesets. I just can't figure out how to integrate the example provided into my current flow.
According to the docs, I need to implement this line:
$mergedRules = $post->mergeRulesets('set_up_all', 'set_up_property_room', 'set_up_property');
This was my attempt, but it didn't work:
if($mergedRules->isValid()) { ...
I get the following error:
Call to a member function isValid() on array
I also tried this, but that didn't work either:
if($post->isValid($mergedRules)) { ...
I get the following error:
array_key_exists(): The first argument should be either a string or an integer
Any suggestions on how I would implement the merging rulesets?
From what I can see - mergeRulesets() returns an array of rules.
So if you do this - it might work:
$post = new Post(Input::all());
$post->setRules($post->mergeRulesets('set_up_all', 'set_up_property_room', 'set_up_property'));
if($post->isValid()) {
///
}
I've released an update version of the package for Laravel 4.2 (0.10.7) which now allows you to pass your rules to the isValid() method to validate against them.
$post->isValid($mergedRules);
The other answers will work, but this syntax is nicer (and won't override the existing rules on the model).
Related
Summary
Context
Sources
2.1. Unit test
2.2. FormRequest's rules method
Behaviors
3.1. Actual behavior
3.2. Expected behavior
Question
Context
In a Unit test, I want to send data to a FormRequest in a REST call. I am testing the behavior of the validation rules I've written in the rules method of the FormRequest.
Sources
Unit test
public function test_detach_user_job_status()
{
$response = $this->put(route('users.update', ['user' => $this->applier['id']], [
'job' => [
]
]));
$response->assertStatus(200);
}
FormRequest's rules method
public function rules()
{
return [
'name' => 'nullable|string',
'job' => 'nullable|array:id,attach_or_detach,message|required_array_keys:id,attach_or_detach',
'job.id' => 'integer|gt:0',
'job.attach_or_detach' => 'boolean',
'job.message' => 'required_if:job.attach_or_detach,true|string',
];
}
Behaviors
Actual behavior
The test succeeds.
Expected behavior
The test fails. Indeed, the array job is provided but no keys id or attach_or_detach or (eventually) message are provided, whereas the validation rules do specify: required_array_keys:id,attach_or_detach.
Also, if no job array is specified at all, then the validator must not reject the request because this array is not provided, nor its keys: it's perfectly normal since the array must be optional (it is nullable to provide this feature).
Question
Why doesn't Laravel make my test fail since my nullable (= optional) array is provided, and that its keys are required?
You didn't put the correct input. you should put the post body to put() method instead of route() method
change this:
$response = $this->put(route('users.update', ['user' => $this->applier['id']], [
'job' => [
]
]));
to:
$response = $this->put(route('users.update', ['user' => $this->applier['id']]),
[
'job' => []
]);
I have this nested relation im abit unsure how i assertJson the response within the phpunit test.
FilmController
public function show(string $id)
{
$film = Film::with([
'account.user:id,account_id,location_id,name',
'account.user.location:id,city'
])->findOrFail($id);
}
FilmControllerTest
public function getFilmTest()
{
$film = factory(Film::class)->create();
$response = $this->json('GET', '/film/' . $film->id)
->assertStatus(200);
$response
->assertExactJson([
'id' => $film->id,
'description' => $film->description,
'account' => $film->account->toArray(),
'account.user' => $film->account->user->toArray(),
'account.user.location' => $film->account->user->location->toArray()
]);
}
Obviously this isnt working because its returning every column for the user im a little unfamiliar with how you test nested relations with the code you need so im unsure with a toArray can anyone help out?
Testing is a place where you throw DRY (don't repeat yourself) out and replace it with hard coded solutions. Why? simply, you want the test to always produce the same results and not be bound up on model logic, clever methods or similar. Read this amazing article.
Simply hard code the structure you expect to see. If you changed anything in your model to array approach, the test would still pass even thou your name was not in the response. Because you use the same approach for transformation as testing. I have tested a lot of Laravel apps by now and this is the approach i prefers.
$account = $film->account;
$user = $account->user;
$location = $user->location;
$response->assertExactJson([
'description' => $film->description,
'account' => [
'name' => $account->name,
'user' => [
'name' => $user->name,
'location' => [
'city' => $location->city,
],
],
],
]);
Don't test id's the database will handle those and is kinda redundant to test. If you want to check these things i would rather go with assertJsonStructure(), which does not assert the data but checks the JSON keys are properly set. I think it is fair to include both, just always check the JSON structure first as it would likely be the easiest to pass.
$response->assertJsonStructure([
'id',
'description',
'account' => [
'id',
'name',
'user' => [
'id',
'name',
'location' => [
'id',
'city',
],
],
],
]);
I have a small question concerning validation.
there is an api route POST /api/document/{document}/link it accepts an array of document IDs ({"ids": [1, 2, 3]}) to be linked to the Document bound to the route. I validate this array as follows
public function rules()
{
return [
'ids' => 'required|array',
'ids.*' => 'numeric|exists:documents,id'
];
}
The thing is the Document model has a partner attribute and it's not possible to link together documents from different partners. What I want is to check if the documents passed (by their IDs) belong to the same partner as the bound Document. I would like to validate this within the FormRequest. Is it possible?
You can use these for your rules:
'ids' => [
'required',
'array'
],
'ids.*' => [
'required',
'exists:documents,id'
],
'ids.*.partner_id' => [
Rule::in([$document->partner_id])
]
this wil validate your id matches with the numbers in the array, since we only put the id from the route given $document in there it should match or return failed.
So, here is what I ended up with:
public function rules()
{
/** #var Document $document */
$document = $this->route('document');
return [
'ids' => ['required', 'array'],
'ids.*' => ['required', 'numeric', Rule::exists('documents','id')->where('partner_id', $document->partner_id)],
];
}
As it turned out the case is described in Laravel docs here https://laravel.com/docs/5.8/validation#rule-exists. I just needed to customize the query executed to ensure that both the passed id and partner_id exist.
I am having a form where i am having title, body, answers[][answer] and options[][option].
I want atleast one answer must be selected for the given question, for example:
i have ABC question and having 5 options for that question,now atleast one answer must be checked or all for given question.
Efforts
protected $rules = [
'title' => 'required|unique:contents|max:255',
'body' => 'required|min:10',
'type' => 'required',
'belongsto' => 'sometimes|required',
'options.*.option' => 'required|max:100',
'answers.*.answer' => 'required',
];
But this is not working. i want atleast one answer must be selected.
Please help me.
The problem is that on $_POST an array filled with empty strings will be passed if no answer is selected.
$answers[0][0] = ''
$answers[0][1] = ''
$answers[0][2] = ''
Hence the following will not work since array count will be greater than zero due to the empty strings:
$validator = Validator::make($request->all(), [
'answers.*' => 'required'
]);
The easiest way to solve this is to create a custom Validator rule by using Laravel's Validator::extend function.
Validator::extendImplicit('arrayRequireMin', function($attribute, $values, $parameters)
{
$countFilled = count(array_filter($values));
return ($countFilled >= $parameters[0]);
});
And then call it in your Validation request:
$validator = Validator::make($request->all(), [
'answers.*' => 'arrayRequireMin:1'
]);
The magic happens in array_filter() which removes all empty attributes from the array. Now you can set any minimum number of answers required.
Validator::extendImplicit() vs Validator::extend()
For a rule to run even when an attribute is empty, the rule must imply that the attribute is required. To create such an "implicit" extension, use the Validator::extendImplicit() method:
Laravel's validation docs
Try this,
'answer.0' => 'required'
it will help you. I think.
In ZF2, I've overridden the Text element with my own (call it My\Form\Element\Text). Now I want to make it so that when I add a text element to the form, it defaults to my overridden class and not Zend\Form\Element\Text:
$this->add([
'type' => 'text',
'name' => 'to',
]);
I know that I could use 'type' => 'My\Form\Element\Text' instead of just 'type' => 'text', but I'm trying to find out if I can avoid that and just use the custom element by default.
I've tried both of these techniques:
module.config.php
return [
'form_elements' => [
'invokables' => [
'text' => 'My\Form\Element\Text',
],
],
];
Module.php
class Module {
public function getFormElementConfig() {
return [
'invokables' => [
'text' => 'My\Form\Element\Text',
],
];
}
}
Neither of these worked (still getting an instance of Zend\Form\Element\Text). Is there some other way of registering the element so that the Zend\Form\Factory::create() method creates an instance of my custom element instead of the Zend version?
Although your config is correct, there are a couple of gotchas to be aware of when using custom elements, detailed in the docs here
Catch 1
If you are creating your form class by extending Zend\Form\Form, you must not add the custom element in the __construct-or, but rather in the init() method
Catch 2
You must not directly instantiate your form class, but rather get an instance of it through the Zend\Form\FormElementManager