How to validate an array of datetimes in Laravel? - php

I have a form that submits an array named prazos, and I want to make sure each item is a valid datetime or null. Following the answers to this question and the Laravel docs, I have this in my Controller:
use Illuminate\Support\Facades\Validator;
// ...
$rules = array([
'prazos' => 'required|array',
'prazos.*' => 'nullable|date'
]);
$validator = Validator::make($request->all(), $rules);
$data = $validator->valid()['prazos'];
foreach($data as $id => $prazo) {
// use $data to update my database
// ...
}
The issue is, the validator is not actually stopping invalid content. If I try to submit "loldasxyz" or other gibberish, I get an error from the database. What am I doing wrong?
Note: previously I had been using validators with the syntax $data = $request->validate($rules), but for some reason it didn't work for the array-type data ($data came back empty). I'm not sure if there is some difference in how those different methods work.
Edit: this is what the parameter bag in $request looks like when I test it (the indices are ids, which is why they start at 1):
#parameters: array:3 [▼
"_token" => "Rf6mAp4lqhpZzQRxaxYsees1M0NfrFKpbGe4Hy28"
"_method" => "PUT"
"prazos" => array:5 [▼
1 => "2021-03-22 21:21"
2 => "2021-03-03 11:27"
3 => "jhbkjhg"
4 => null
5 => "2021-03-02 14:21"
]
]
And this is what the validated $data comes out as:
array:5 [▼
1 => "2021-03-22 21:21"
2 => "2021-03-03 11:27"
3 => "jhbkjhg"
4 => null
5 => "2021-03-02 14:21"
]
I wish it would tell me the third value is invalid.

Heres what youre looking for:
https://laravel.com/docs/8.x/validation#rule-date-equals
The field under validation must be equal to the given date. The dates will be passed into the PHP strtotime function in order to be converted into a valid DateTime instance.

Related

PHP / Laravel - Foreach store into database (\Grammar::parameterize() error)

I have an array, which looks like this:
array:3 [▼
"field" => array:2 [▼
0 => "fromName"
1 => "from"
]
"operator" => array:2 [▼
0 => "="
1 => "="
]
"value" => array:2 [▼
0 => "Oliver"
1 => "oliver#mywebsite.com"
]
]
I am trying to save the above array, into my database table called email_rules:
Below is my code.
StreamEmailRulesController.php:
public function store(Stream $stream)
{
//Validate the request.
//Validate the request.
$attributes = request()->validate([
'field' => 'required|array|min:1',
'field.*' => [
'required', 'string',
Rule::in(['fromName', 'from']),
],
'operator' => 'required|array|min:1',
'operator.*' => [
'required', 'string',
Rule::in(['=', '!=', 'matches']),
],
'value' => 'required|array|min:1',
'value.*' => 'required|string|min:3|max:255',
]);
//Add the document to the database.
$stream->addRules($attributes);
//Return back.
return redirect()->back();
}
Now the $stream->addRules() function is responsible for saving the data to the database.
Stream.php:
/**
* A stream can have many rules.
*/
public function rules()
{
return $this->hasMany(EmailRule::class);
}
/**
* Add Email Rules(s) to the stream
*
* #return Illuminate\Database\Eloquent\Model
*/
public function addRules(array $attributes)
{
return $this->rules()->create($attributes);
}
Now, above does not work. I get below error:
Argument 1 passed to Illuminate\Database\Grammar::parameterize() must be of the type array, int given,
What am I doing wrong?
If you dump $attributes you may be getting an int (bool) as a pass or fail or even json, depending on what's going in, from the validation. This might just be a matter of changing syntax from
$attributes = request()->validate([...
to
$attributes= $this->validate(request(), [...
I believe your issue is that you're trying to save an array as a singular value. IE those attributes need to be iterated over to create a new set of rules for each one, instead. Normally, I'd expect to see the array ready to create individual objects. In this case, though it looks like it is structured to create individual fields (field, operator, value), so looping through those may not do what you wish either -- it provides multiple fields to the create construct, rather than a full set of object params for a new rule(). I think Laravel is hinting that you may wish to change your request/return structure to match the model format.
I think it could be the array structure. Can you modify the array to?:
[
[
"field" => "fromName",
"operator" => "=",
"value" => "Oliver"
],
[
"field" => "from",
"operator" => "=",
"value" => "oliver#mywebsite.com"
],
]
EDIT:
In the Controller add a loop like this:
...
foreach ($attributes as $key => $value) {
foreach ($value as $k => $v) {
$data [$k][$key] = $v;
}
}
//Add the document to the database.
$stream->addRules($data);
The problem was that Laravels create or createMany expect an array with key => pair values, where the key corresponds to the database columns.
This article from Adam Wathan helped me out a lot.
This is what I ended up doing:
$requestData = collect(request()->only('field', 'operator', 'value'));
$rules = $requestData->transpose()->map(function ($ruleData) {
return new EmailRule([
'field' => $ruleData[0],
'operator' => $ruleData[1],
'value' => $ruleData[2],
]);
})->toArray();
//Add the rules to the database.
$stream->addRules($rules);

Laravel Validation – Date Format m/y Not Accepting Specific Value

I've got the following validation rules for basic authentication of a Payment Method (advanced things, like CVD validation, existing card, etc. is handled afterward by Moneris).
$rules = [
"type" => "required|in:visa,mastercard",
"nickname" => "required",
"credit_card_number" => "required|numeric|digits:16",
"expiry" => "required|string|size:5|date_format:m/y|after:today",
"cvd" => "required|numeric|digits:3"
];
The rule expiry is not accepting a specific value, 04/yy, but it is accepting 03/yy and 05/yy; I have no idea why this is happening, but I need it remedied. Has anyone come across this behaviour?
For reference, the result dd($request->input(), $validator->passes(), $validator->errors()); when I pass 04/19 is as follows:
array:6 [▼
"type" => "visa"
"nickname" => "Testing"
"credit_card_number" => "4242424242424242"
"expiry" => "04/19"
"cvd" => "123"
"masked_pan" => "************4242"
]
false
MessageBag {#502 ▼
#messages: array:1 [▼
"expiry" => array:1 [▼
0 => "The expiry does not match the format m/y."
]
]
#format: ":message"
}
When I send 05/19, everything works fine:
array:6 [▼
"type" => "visa"
"nickname" => "Testing"
"credit_card_number" => "4242424242424242"
"expiry" => "05/19"
"cvd" => "123"
"masked_pan" => "************4242"
]
true
MessageBag {#502 ▼
#messages: []
#format: ":message"
}
Looks like it's an issue with how this validation rule works in Laravel 5.4. To fix, I check the date validity of the input prepended with 01/, and if it is valid, merge that into the request, with endOfMonth() to handle after:today validation:
$mergeDate = null;
$rawInput = $request->input("expiry");
try {
$mergeDate = Carbon::createFromFormat("d/m/y", "01/".$request->input("expiry"))->endOfMonth();
} catch(\Exception $ex){}
$request->merge([
"masked_pan" => str_repeat("*", 12).substr($request->input("credit_card_number", ""), -4),
"expiry" => $mergeDate ? $mergeDate->format("d/m/y") : $request->input("expiry")
]);
So now, if I pass 04/22, it will check if 01/04/22 is valid, then convert to end of month 30/04/22, then replace that as the value passed to the validation (which also needs to be updated)
"expiry" => "required|string|size:8|date_format:d/m/y|after:today",
I also have to update and pass $messages to avoid confusion to the user:
$messages = [
"expiry.size" => "The :attribute filed must be 5 characters.",
"expiry.date_format" => "The :attribute field does not match the format m/y"
];
$validator = \Validator::make($request->all(), $rules, $messages);
And finally, replace the value with the raw input if there's an error (so the user doesn't see a value they didn't enter)
if(!$validator->passes()){
$request->merge(["expiry" => $rawInput]);
return back()->withErrors($validator)->withInput();
}
A whole bunch of nonsense, but seems to handle 04/22 and other dates just fine.

Validate array of inputs in form in Laravel 5.7

My form has the same input field multiple times. My form field is as follows:
<input type='text' name='items[]'>
<input type='text' name='items[]'>
<input type='text' name='items[]'>
And request contains ($request['items'):
array:1 [▼
"items" => array:3 [▼
0 => "item one"
1 => "item two"
2 => "item three"
]
]
I want atleast one of the items to be filled. My current validation in the controller is
$validator = Validator::make($request->all(),[
'items.*' => 'required|array|size:1'
]);
It does not work. I tried with combination of size, required, nullable. Nothing works.
In fact, it's enough to use:
$validator = Validator::make($request->all(),[
'items' => 'required|array'
]);
The changes made:
use items instead of items.* - you want to set rule of general items, if you use items.* it means you apply rule to each sent element of array separately
removed size:1 because it would mean you want to have exactly one element sent (and you want at least one). You don't need it at all because you have required rule. You can read documentation for required rule and you can read in there that empty array would case that required rule will fail, so this required rule for array makes that array should have at least 1 element, so you don't need min:1 or size:1 at all
You can check it like this:
$validator = Validator::make($request->all(), [
"items" => "required|array|min:1",
"items.*" => "required|string|distinct|min:1",
]);
In the example above:
"items" must be an array with at least 1 elements.
Values in the "items" array must be distinct (unique) strings, at least 1 characters long.
You can use a custom rule with a closure.
https://laravel.com/docs/5.7/validation#custom-validation-rules
To check if an array has all null values check it with array_filter which returns false if they're all null.
So something like...
$request->validate([
'items' => [
// $attribute = 'items', $value = items array, $fail = error message as string
function($attribute, $value, $fail) {
if (!array_filter($value)) {
$fail($attribute.' is empty.');
}
},
]
]);
This will set the error message: 'items is empty."
Knowing you are using the latest version of Laravel, I really suggest looking into Form Request feature. That way you can decouple validation from your controller keeping it much cleaner.
Anyways as the answer above me suggested, it should be sufficient for you to go with:
'items' => 'required|array'
Just Do it normally as you always do:
$validator = Validator::make($request->all(),[
'items' => 'required'
]);
You should try this:
$validator = $request->validate([
"items" => "required|array|min:3",
"items.*" => "required|string|distinct|min:3",
]);

Call to a member function links() on array in Laravel 5.6

I am using pagination in Laravel 5.6 to build an application. I am using Laravel DB instead of Eloquent. However, when I add a pagination link on view, it shows an error Call to a member function links() on array. This is because I am using an array, the default is an object with a collection. The output I am getting looks like
array:12 [▼
"current_page" => 1
"data" => array:3 [▶]
"first_page_url" => "http://127.0.0.1:8000/posts?page=1"
"from" => 1
"last_page" => 3
"last_page_url" => "http://127.0.0.1:8000/posts?page=3"
"next_page_url" => "http://127.0.0.1:8000/posts?page=2"
"path" => "http://127.0.0.1:8000/posts"
"per_page" => 3
"prev_page_url" => null
"to" => 3
"total" => 9
]
When I use normal pagination i.e. without converting to an array, the output is
LengthAwarePaginator {#264 ▼
#total: 9
#lastPage: 3
#items: Collection {#251 ▶}
#perPage: 3
#currentPage: 1
#path: "http://127.0.0.1:8000/posts"
#query: []
#fragment: null
#pageName: "page"
}
The problem I face here is adding other data to collection
I can't use the default output return by pagination() method. Can someone please suggest how to solve this issue?
Using the below, I am getting the required output.
Step 1: Include LengthAwarePaginator in the file
use Illuminate\Pagination\LengthAwarePaginator;
Step 2: Create an object of LengthAwarePaginator
$paginate = new LengthAwarePaginator(Array, countOfArray, perPage, currentPage, [
'path' => request()->url(),
'query' => request()->query()
]);
I kept currentPage value as null.
Return $paginate to view.
return view('home', ['data' => Array, 'page' => $paginate]);

Laravel Validator removing null fields from array

I'm writing a test case to simulate a creation of a product. When creating the product, I'm passing an array of formats, and for some reasons, when validating with the controller, the thumbnail key is missing from the data.
This is the data I'm sending:
"formats" => array:1 [
0 => array:3 [
"upc" => "584979099857"
"vcode" => "VX43V14FN910479274209"
"thumbnail" => null
]
]
And I send it like the following in my test case:
$response = $this->post(route('products.store'), $product);
For some reasons, when sending the request, the thumbnail => null disappears from the formats attribute.
Therefore, when validating the request, I always get the error that the thumbnail has to be present.
request()->validate([
'formats' => 'bail|required|array',
'formats.*.upc' => 'bail|required|string|max:255',
'formats.*.vcode' => 'bail|required|string|max:255',
'formats.*.thumbnail' => 'bail|present|image'
]);
I've also tried changing the rules of thumbnail to:
bail|required|nullable|image
Or
bail|nullable|required|image
But then I get the required error.
Does anyone know why the thumbnail key is getting removed from the formats array upon sending the request?

Categories