can't use boolean validation laravel 5 - validation

I'm using Laravel 5.2, and as documentation says:
boolean
The field under validation must be able to be cast as a boolean. Accepted input are true, false, 1, 0, "1", and "0".
So I've created a checkbox (styled like a switch from materialize), to return true when on, and false when off. Here goes the blade:
{!! Form::hidden('eh_capa', 0) !!}
Want to select as a graph cover?
<label>
Off
<input name="cover" type="checkbox" checked>
<span class="lever"></span>
on
</label>
Of course this code goes inside a form tag. I do the validation inside a Request class as said on this part of laravel documentation, here is my rules method:
public function rules()
{
$this['cover'] = $this['cover'] === 'on' ? 1 : 0;
$this['obra_id'] = $this->route('obra');
$this['arquivo'] = $this->hasFile('arquivo') ? $this->file('arquivo') : NULL;
dd($this);
return [
'cover' => 'required|boolean',
'obra_id' => 'exists:obras',
'path' => 'required',
'arquivo' => 'required|file|max:2048|mimes:pdf',
];
}
The dd() function returns my request like this:
StoreGraficoPostRequest {#441 ▼
#container: Application {#3 ▶}
#redirector: Redirector {#448 ▶}
#redirect: null
#redirectRoute: null
#redirectAction: null
#errorBag: "default"
#dontFlash: array:2 [▶]
#json: null
#convertedFiles: array:1 [▶]
#userResolver: Closure {#355 ▶}
#routeResolver: Closure {#354 ▶}
+attributes: ParameterBag {#443 ▶}
+request: ParameterBag {#440 ▼
#parameters: array:5 [▼
"_token" => "bZIpGW6UCcYHlCTZuIZMtmOrpCodWyfcbO1HgQid"
"path" => "hello.pdf"
"cover" => 1
"obra_id" => "29"
"arquivo" => UploadedFile {#438 ▶}
]
}
+query: ParameterBag {#442 ▶}
+server: ServerBag {#446 ▶}
+files: FileBag {#445 ▶}
+cookies: ParameterBag {#444 ▶}
+headers: HeaderBag {#447 ▶}
#content: ""
#languages: null
#charsets: null
#encodings: null
#acceptableContentTypes: null
#pathInfo: null
#requestUri: null
#baseUrl: null
#basePath: null
#method: "POST"
#format: null
#session: Store {#394 ▶}
#locale: null
#defaultLocale: "en"
}
But when I comment the dd function, the validation returns that cover must be true or false. The same happens if I change the value of cover field to true, "1" and "true" when on. I've searched all the web for anything that helps and got nothing... I'm beginning to think that this is a Laravel bug...

Well, I got a way to do it. The trick was just to add this code to my Request class:
protected function getValidatorInstance()
{
$data = $this->all();
$data['eh_capa'] = $data['eh_capa'] === 'on' ? 1 : 0;
$data['obra_id'] = $this->route('obra');
$this->getInputSource()->replace($data);
/* modify data before send to validator */
return parent::getValidatorInstance();
}
and then, my rules method ended only with the return.

You are modifying your input in the wrong place. You should override the all() function in your request class, and modify your input there.
public function rules()
{
return [
'cover' => 'required|boolean',
'obra_id' => 'exists:obras',
'path' => 'required',
'arquivo' => 'required|file|max:2048|mimes:pdf',
];
}
public function all()
{
$input = parent::all();
$input['cover'] = $input['cover'] === 'on' ? 1 : 0;
$input['obra_id'] = ...
$input['arquivo'] = ...
return $input;
}

I ran into the same problem and decided to create a small static class that parses all values that are marked as boolean in a rule.
The advantage is that it will only parse booleans that the rules dictate to be boolean. Any other input values will go unchanged, making it still possible to post a string with value 'true' if you desire.
<?php
namespace App\Helpers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
class ValidationHelper {
/**
* A recursive funciton to loop over any input and parse them to true booleans.
*/
private static function _getObj(&$inputs, $idPath) {
$idPart = array_shift($idPath);
if (count($idPath) > 0) {
if ($idPart == '*') {
for ($i = 0; $i < count($inputs); $i++) {
ValidationHelper::_getObj($inputs[$i], $idPath);
}
}
else {
ValidationHelper::_getObj($inputs[$idPart], $idPath);
}
}
else {
$currentValue = $inputs[$idPart];
if ($currentValue == 1 || strtolower($currentValue) == 'true') {
$inputs[$idPart] = true;
}
else if ($currentValue == 0 || strtolower($currentValue) == 'false') {
$inputs[$idPart] = false;
}
else { // any other value will be left untouched
$inputs[$idPart] = $currentValue;
}
}
}
/**
* Will validate a request against the given rules.
* Will also help fix any booleans that otherwise are parsed as 'true' strings.
* #param Request $request
* #param Array $rules
* #return void
*/
public static function validateRequest(Request $request, $rules) {
// Find any rules demanding booleans:
$booleanIDs = [];
foreach ($rules as $id => $rule) {
if (is_string($rule)) {
$rule = explode('|', $rule);
}
if (in_array('boolean', $rule)) {
$booleanIDs[] = $id;
}
}
if (isset($booleanIDs)) {
// Set all inputs to a bindable array:
$inputs = [];
foreach ($request->all() as $key => $value) {
$inputs[$key] = $value;
}
// Recursively loop through the boolean-ids
foreach ($booleanIDs as $id) {
$idPath = explode('.', $id);
ValidationHelper::_getObj($inputs, $idPath);
}
// Make sure the booleans are not just validated correctly but also stay cast when accessing them through the $request later on.
$request->replace($inputs);
}
else {
$inputs = $request->all();
}
$validator = Validator::make($inputs, $rules);
if ($validator->fails()) {
throw new \Exception('INVALID_ARGUMENTS', $validator->errors()->all());
}
}
}
Rules can be set as array or string (as normal) and it even works with nested values:
ValidationHelper::validateRequest($request, [
['foo'] => ['nullable', 'boolean'],
['bar'] => ['required', 'boolean'],
['item.*.isFoo'] => ['nullable', 'boolean'],
['item.*.isBar'] => 'required|boolean'],
]);

Related

set array_filter to accept null, zero and empty values

How can I set "array_filter" to accept the null values, empty and zero?
I tried the callback function but it didn't work for me.
Here's my code
$student = array_filter($request->student);
$teacher = array_filter($request->teacher);
$ScID = array_map(null, $student, $teacher);
SchoolDescriptions::where('pin_id', $request->session()->get('pin_id'))->delete();
foreach ($ScID as $key=>$array) {
SchoolDescriptions::updateOrCreate([
'student' => $array[0],
'teacher' => $array[1],
'pin_id' => $request->session()->get('pin_id')
]);
}; return back()->withStatus(__('Successfully saved.'));
I was able to find a solution for this. So I just simply made a condition.
To accept null values when submitting teacher and student forms.
if ($request->student == null && $request->teacher == null)
{
//do nothing
}
else {
$student = array_filter($request->student);
$teacher = array_filter($request->teacher);
$ScID = array_map($student, $teacher);
SchoolDescriptions::where('pin_id', $request->session()->get('pin_id'))->delete();
foreach ($ScID as $key=>$array) {
SchoolDescriptions::updateOrCreate([
'student' => $array[0],
'teacher' => $array[1],
'pin_id' => $request->session()->get('pin_id')
]);
};
}

Unable to register extra fileds in a pivot table using sync function with Laravel

I'm unable to register extra fields in a pivot table:
This my scheme:
Buyer Model:
public function qualities(){
//many-to-many relation
return $this->belongsToMany(Quality::class);
}
Quality Model:
public function qualities(){
//many-to-many relation
return $this->belongsToMany(Quality::class);
}
Product Model:
public function buyers(){
//many-to-many relation
return $this->belongsToMany(Buyer::class);
}
Before of send the data to the sync function, I'm combining the data:
public function store(createBuyerRequest $request){
if($request->validated()){
try{
//Register buyer
$buyer = new Buyer;
$buyer->ruc = $request->ruc;
$buyer->companyName = $request->companyName;
$buyer->contact = $request->contact;
$buyer->address = $request->address;
$buyer->phone = $request->phone;
$buyer->email = $request->email;
$buyer->save();
$arrayQualitiesIds = $request->get('qualitiesProductCheckbox');
//Build arrayMap
$extra = array_map(function($qualityId) use($request){
return ['quality_id' => $qualityId, 'product_id' => $request->product];
}, $arrayQualitiesIds);
// Combine the array
$data = array_combine($arrayQualitiesIds, $extra);
$buyer->qualities()->sync($data);
return redirect()->route('admin.buyers.index')
->with('status_success','Comprador registrado correctamente!');
}catch(Exception $e){
return redirect()->route('admin.buyers.index')
->with('cancel','No se pudo registrar el comprador. '.$e->getMessage());
}
}
}
The output of $data = array_combine($arrayQualitiesIds, $extra) of one example was this:
Output:
1 => array:2 [▼
"quality_id" => "1"
"product_id" => "5"
]
2 => array:2 [▼
"quality_id" => "2"
"product_id" => "5"
]
3 => array:2 [▼
"quality_id" => "3"
"product_id" => "5"
]
The combination was successfully done! however was unable of register the data in the pivot table, only was registered the buyer.
This is view from the form to register a buyer:
Basically a buyer is being registered and selection a product of interest and his qualities.. The business wants register a buyer with his desired product.
The code $buyer->qualities()->sync($data); should register the buyer_id automatically using the relation and fill the pivot table with array combination putted in $data.
Any Idea for fix this problem guys I will appreciate, thanks so much.
Fixed
I just modified the relation inside of the Buyer model:
public function qualities(){
return $this->belongsToMany(Buyer::class, 'product_interested','quality_id', 'product_id','buyer_id')->withTimestamps();;
}
I also needed of add the buyer_id inside of the array_map:
$extra = array_map(function($qualityId) use($request, $buyer){
return ['quality_id' => $qualityId,
'product_id' => $request->product,
'buyer_id' => $buyer->id];
}, $arrayQualitiesIds);
//combine arrays
$data = array_combine($arrayQualitiesIds, $extra);
/*
dd($data);
array:1 [▼
4 => array:3 [▼
"quality_id" => "4"
"product_id" => "6"
"buyer_id" => 19
]
]
*/
$buyer->qualities()->sync($data);
I don't know the reason why $buyer->qualities()->sync($data) not inserted automatically the buyer_id
You have to specify the name of the pivot table in your relation methods like this:
Buyer Model:
public function qualities(){
//many-to-many relation
return $this->belongsToMany(Quality::class, 'product_interested');
}

Laravel 8 Class Based Model Factories

I'm trying to figure out why when I try and create a factory of my Player class and dd($this) it comes back as that its not overriding the state of the model. What also doesn't make sense is that it is a collection of two items for the states.
Can anyone give further clarification for any of this?
$player = Player::factory()->injured()->create();
<?php
namespace Database\Factories;
use App\Enums\PlayerStatus;
use App\Models\Player;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Str;
class PlayerFactory extends Factory
{
/**
* The name of the factory's corresponding model.
*
* #var string
*/
protected string $modelClass = Player::class;
/**
* Define the model's default state.
*
* #return array
*/
public function definition(): array
{
return [
'name' => $this->faker->name,
'height' => $this->faker->numberBetween(60, 95),
'weight' => $this->faker->numberBetween(180, 500),
'hometown' => $this->faker->city.', '.$this->faker->state,
'status' => PlayerStatus::__default,
];
}
public function injured(): self
{
$this->state([
'status' => PlayerStatus::INJURED,
]);
dd($this);
$now = now();
$start = $now->copy()->subDays(2);
$this->hasEmployments(1, ['started_at' => $start]);
$this->hasInjuries(1, ['started_at' => $now]);
return $this;
}
}
^ Database\Factories\PlayerFactory^ {#2650
#modelClass: "App\Models\Player"
#model: null
#count: null
#states: Illuminate\Support\Collection^ {#2647
#items: array:2 [
0 => Closure()^ {#2631
class: "Illuminate\Database\Eloquent\Factories\Factory"
this: Database\Factories\PlayerFactory {#2626 …}
use: {
$state: []
}
}
1 => Closure()^ {#2646
class: "Illuminate\Database\Eloquent\Factories\Factory"
this: Database\Factories\PlayerFactory {#2648 …}
use: {
$state: []
}
}
]
}
#has: Illuminate\Support\Collection^ {#2610
#items: []
}
#for: Illuminate\Support\Collection^ {#2640
#items: []
}
#afterMaking: Illuminate\Support\Collection^ {#2455
#items: []
}
#afterCreating: Illuminate\Support\Collection^ {#2453
#items: []
}
#connection: null
#faker: null
}
The method call:
$this->state([
'status' => PlayerStatus::INJURED,
]);
does not modify the current object - it creates a new Factory Instance with the updated 'status'. And this new Factory should be returned by the injured method.
In this case one solution would be:
public function injured(): self
{
$injuredPlayer = $this->state([
'status' => PlayerStatus::INJURED,
]);
$now = now();
$start = $now->copy()->subDays(2);
$injuredPlayer->hasEmployments(1, ['started_at' => $start]);
$injuredPlayer->hasInjuries(1, ['started_at' => $now]);
return $injuredPlayer;
}

Issue with Laravel's Eloquent implicit JSON casting. Associated array casts to indexed array

I try to use casting in my Model like this one:
protected $casts = [
'thumbnails' => 'array', // also tried 'json' and 'object'
];
While saving, the correct data is put:
$thumbnails = ['small' => 'path1.jpeg', 'medium' => 'path2.jpeg', 'large' => 'path3.jpeg'];
$product->images()->create([
'path' => $imagePath,
'thumbnails' => $thumbnails
]);
// Saved content of 'thumbnails' field in DB row:
// {"large": "path1.jpeg", "small": "path2.jpeg", "medium": "path3.jpeg"}
Then I try get access to this field, I have:
$image = Image::find(6);
dd($image->thumbnails);
// array:3 [▼
// 0 => "path1.jpeg"
// 1 => "path2.jpeg"
// 2 => "path3.jpeg"
// ]
Instead of
// array:3 [▼
// 'small' => "path1.jpeg"
// 'medium' => "path2.jpeg"
// 'large' => "path3.jpeg"
// ]
Database field type is json.
My bad, I added some wrong getter in the Model...
/**
* #return array
* #throws \Exception
*/
public function getThumbnailsAttribute() : array
{
return resolve(ImageService::class)->getThumbnailUrls($this);
}

Laravel: How to get model instance after create that has a global scope

I struggle to get the model instance with global scopes.
Normally, if you write:
$offer = Offer::createNew();
with
public static function createNew()
{
return static::create([
'user_id' => auth()->id(),
'reviewed' => false
]);
}
You would get the model instance. But now I've added a global scope and cannot get it to work.
The model is "nearly" empty as you expect because in my case I want only to get Offers that are reviewed. However, if I add:
public static function createNew()
{
return static:::withoutGlobalScopes()->create([
'user_id' => auth()->id(),
'reviewed' => false
]);
}
I get a result from a limited model that only contains these attributes:
#attributes: array:5 [
"user_id" => 1
"reviewed" => false
"updated_at" => "2018-09-24 11:48:27"
"created_at" => "2018-09-24 11:48:27"
"id" => 2
]
But my model has obviously more attributes than that. If I add get(), I'm only getting
Illuminate\Database\Eloquent\Collection {#1635
#items: []
}
So how would you get the model with create when having a global scope?
Edit
My first workaround looks like this:
public static function createNew()
{
$model = static::create([
'user_id' => auth()->id(),
'reviewed' => false
]);
return static::withoutGlobalScopes()->find($model->id);
}
Edit 2
My Globalscope looks like this:
class FoodScope implements Scope
{
public function apply(Builder $builder, Model $model)
{
$builder->where('reviewed', true)
->where('paused', false);
}
}
This behavior is not caused by the global scope.
Use ->fresh() when you want to get the other attributes:
public static function createNew()
{
return static::create([
'user_id' => auth()->id(),
'reviewed' => false
])->fresh();
}

Resources