How to remove code repetition in Yii2 validators method `when`? - validation

You have to read my previous problem to understand what i'm talking here about(Link to my problem). So it was solved.
But i have a lot of fields in my view that are always filled by current user's value. And it's code repetition when i doing next
[['new_email'], 'unique', 'targetAttribute' => 'email', 'targetClass' => '\common\models\User',
'when' => function ($model, $attribute) {
$this->isNewEmail = false;
if($model->$attribute != User::find()->where(['id' => Yii::$app->user->id])->one()->email) {
$this->isNewEmail = true;
}
return $this->isNewEmail;
},
'message' => 'This email can not be taken.'],
And this
[['first_name'], 'string', 'min' => 4, 'max' => 50,
'when' => function ($model, $attribute) {
$this->isNewFirstName = false;
if($model->$attribute != User::find()->where(['id' => Yii::$app->user->id])->one()->first_name) {
$this->isNewFirstName = true;
}
return $this->isNewFirstName;
},
],
And so on.
What can i do to remove this code repetition. Or is there are somewhere exists module or component for Yii2 just like for this situation like mine? Or is there are somewhere exists core validator for my issue? Or i doomed forever to do all of this code repetition?)

You can see that 2 when callable function have same structure. So you can write a simple function like that:
/**
* #param $model ActiveRecord
* #param $attribute string
* #return bool
*/
public function checkUniqueAttribute($model, $attribute)
{
return $model->$attribute != $model::find()->where(['id' => Yii::$app->user->id])->one()->$attribute;
}
And use it in your rules:
[
['new_email'], 'unique',
'targetAttribute' => 'email',
'targetClass' => '\common\models\User',
'message' => 'This email can not be taken.',
'when' => 'checkUniqueAttribute',
],
[
['first_name'], 'unique',
'targetAttribute' => 'first_name',
'targetClass' => '\common\models\User',
'message' => 'This first name can not be taken.',
'when' => 'checkUniqueAttribute',
],
[['first_name'], 'string', 'min' => 4, 'max' => 50,],
Hope it useful.
Goodluck and have fun!

Related

assertJson is not equal to expected to actual in laravel

I'm new in unit tests in laravel and currently facing an error on my test. Please see my code below.
Test
/** #test */
public function users_can_view_homepage_products()
{
$response = $this->get('api/products');
$response->assertStatus(200)
->assertJson([
'id' => 1,
'name' => ucwords($this->faker->words(3, true)),
'slug' => ucfirst($this->faker->slug),
'intro' => $this->faker->sentence,
'price' => number_format($this->faker->randomFloat(2, 100, 99999), 2)
]);
}
Controller
public function index()
{
return [
'id' => 1,
'name' => 'Airpods Pro (2021)',
'slug' => 'airpods-pro-2021',
'intro' => 'New and powerful airpods from apple.',
'price' => 12400
];
}
Error
The test fails because the content of the json is different to that being tested. You probably want to test that the structure is the same, rather than the content:
/** #test */
public function users_can_view_homepage_products()
{
$response = $this->get('api/products');
$response->assertStatus(200)
->assertJsonStructure([
'id',
'name'
'slug'
'intro'
'price'
]);
}

How to make a good validation on laravel?

I have data, they look like this:
{
sender_name : "Real fake sender name",
recipient_name : "Real fake recipient name",
goods: [
{
"no" : 1
"name":"Pen",
"unit": "1",
"qty":"50",
"price":"50",
"amount":"2500",
"vat_percent":"5",
"vat_sum": "125",
"total_sum": "2625"
}
]
}
I need to validate "goods" using extend validator. Here is his code:
Validator::extend('invoiceGoods' , function($attribute, $value, $parameters, $validator) {
$rulesForGoods = [
'no' => 'integer|required',
'name' => 'string|max:64|required',
'unit' => 'required|integer',
'qty' => 'required|string',
'price' => 'required|numeric',
'amount' => 'required|numeric',
'vat_percent' => 'nullable|numeric',
'vat_sum' => 'nullable|numeric',
'total_sum' => 'required|numeric'
];
foreach ($value as $good) {
$validator = Validator::make($good , $rulesForGoods);
if ($validator->fails()) {
return false;
}
}
return true;
});
This is the main code.
$validator = Validator::make($data , [
'goods' => 'invoiceGoods',
'sender_name' => 'string',
'recipient_name' => 'string',
]);
if ($validator->fails()) {
return response()->json([
'success' => false,
'message' => 'Validation error.',
'data' => $validator->errors()
]);
}
If the goods validation error occurs, I get this answer:
But I would like to display errors like this: the wrong unit in the goods with no 1.
I know that the third argument can be passed an array with custom messages, but how to return it from the extended validator if it should return true or false?
https://laravel.com/docs/5.8/validation#custom-error-messages
$messages = [
'Validation.invoice_goods' => 'Errror message!',];
$validator = Validator::make($input, $rules, $messages);

Using isDirty in Laravel

I'm using the isDirty() method in my controller to check if any field is changed. Then I am saving the old data of a field and the new data in a table. The code is working fine; however, how can I optimize this code?
By using the below code, I will have to write each field name again and again. If request->all() has 20 fields, but I want to check six fields if they are modified, how can I pass only 6 fields in the below code, without repeating?
Controller
if ($teacher->isDirty('field1')) {
$new_data = $teacher->field1;
$old_data = $teacher->getOriginal('field1');
DB::table('teacher_logs')->insert(
[
'user_id' => $user->id,
'teacher_id' => $teacher->id,
'old_value' => $old_data,
'new_value' => $new_data,
'column_changed' => "First Name",
]);
}
You can set a list of what fields you want to be checking for then you can loop through the dirty fields and build your insert records.
use Illuminate\Support\Arr;
...
$fields = [
'field1' => 'First Name',
'field2' => '...',
...
];
$dirtied = Arr::only($teacher->getDirty(), array_keys($fields));
$inserts = [];
foreach ($dirtied as $key => $value) {
$inserts[] = [
'user_id' => $user->id,
'teacher_id' => $teacher->id,
'old_value' => $teacher->getOriginal($key),
'new_value' => $value,
'column_changed' => $fields[$key];
];
}
DB::table(...)->insert($inserts);
i tried following code after getting idea by lagbox in comments, and i have found solution to my problem.
$dirty = $teacher->getDirty('field1','field2','field3');
foreach ($dirty as $field => $newdata)
{
$olddata = $teacher->getOriginal($field);
if ($olddata != $newdata)
{
DB::table('teacher_logs')->insert(
['user_id' => $user->id,
'teacher_id' => $teacher->id,
'old_value' => $olddata,
'new_value' => $newdata,
'column_changed' => "changed",
]);
}
}

How to insert all json file data in database?

I have JSON file and have data in it. I want to import all data in the database through DB seeders. I am getting an error Trying to get property name of non-object. I have multiple data how I can insert in the database?
public function run()
{
$json = File::get("public/kmz/WASASubdivisions.geojson");
$data = json_decode($json);
// dd($data);
foreach ($data as $obj){
Regions::create(array(
'name' => $obj[0]->Name,
'description' => $obj[0]->description,
'altitudeMode' => $obj[0]->altitudeMode,
'Town' => $obj[0]->Town,
'AC' => $obj[0]->AC,
'No_of_TW' => $obj[0]->No_of_TW,
'No' => $obj[0]->No,
'DC'=> $obj[0]->DC,
'HH_2017' => $obj[0]->HH_2017,
'FID' => $obj[0]->FID,
'Area_ha' => $obj[0]->Area_ha,
'Field_1' => $obj[0]->Field_1,
'Pop_Dens' => $obj[0]->Pop_Dens,
'Id' => $obj[0]->Id,
'Pop_2017' => $obj[0]->Pop_2017,
'Area_Sq'=> $obj[0]->Area_Sq,
));
}
}
Sample Json Format
31 => {#837
+"type": "Feature"
+"properties": {#838
+"Name": "Gujjar Pura"
+"description": null
+"altitudeMode": "clampToGround"
+"Town": "Shalimar Town"
+"AC": "31"
+"No_of_TW": "11"
+"No": "13"
+"DC": "38"
+"HH_2017": "30478"
+"FID": "31"
+"Area_ha": "648.327"
+"Field_1": "Gujjar Pura"
+"Pop_Dens": "54063.141167"
+"Id": "0"
+"Pop_2017": "196619"
+"Area_Sq": "3.63684"
}
+"geometry": {#839
+"type": "MultiPolygon"
+"coordinates": array:1 [
0 => array:1 [
0 => array:169 [ …169]
]
]
}
}
Let's support I have a model Post and I want to save additional data in JSON format in the posts table. In that case, we can use property $casts in Laravel. Which will cast our field value to whatever we gave.
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Post extends Model
{
protected $table='posts';
protected $fillable = ['user_id', 'title', 'short_description', 'description', 'status', 'json_data'];
/**
* The attributes that should be cast to native types.
*
* #var array
*/
protected $casts = [
'json_data' => 'array',
];
}
Now we want to save data something like this
$data = [
'user_id' => 1,
'title' => 'abc',
'short_description' => 'test',
'description' => 'test',
'status' => true,
'json_data' => [
'additional_info' => '',
'post_image' => '',
...
],
];
$item = new Post;
$item->fill($data)->save();
This will save your json_data array values to JSON in the database. But when you will get data from the database it will convert that to array automatically.
For reference read this
since i am not a big fan of processing the json as a object
So the json_decode will accept the second arg so
$json = File::get("public/kmz/WASASubdivisions.geojson");
$data = json_decode($json,true);
dd($data);
foreach ($data as $obj)
{
Regions::create(array(
'name' => $obj['Name'],
'description' => $obj['description'],
'altitudeMode' => $obj['altitudeMode'],
'Town' => $obj['Town'],
'AC' => $obj['AC'],
'No_of_TW' => $obj['No_of_TW'],
'No' => $obj['No'],
'DC'=> $obj['DC'],
'HH_2017' => $obj['HH_2017'],
'FID' => $obj['FID'],
'Area_ha' => $obj['Area_ha'],
'Field_1' => $obj['Field_1'],
'Pop_Dens' => $obj['Pop_Dens'],
'Id' => $obj['Id'],
'Pop_2017' => $obj['Pop_2017'],
'Area_Sq'=> $obj['Area_Sq'],
));
}
Can You Post the dd() result and fileds sets of the table
public function run()
{
$json = File::get("public/kmz/WASASubdivisions.geojson");
$data = json_decode($json);
dd($data);
foreach ($data as $obj){
Regions::create(array(
'name' => $obj->Name,
'description' => $obj->description,
'altitudeMode' => $obj->altitudeMode,
'Town' => $obj->Town,
'AC' => $obj->AC,
'No_of_TW' => $obj->No_of_TW,
'No' => $obj->No,
'DC'=> $obj->DC,
'HH_2017' => $obj->HH_2017,
'FID' => $obj->FID,
'Area_ha' => $obj->Area_ha,
'Field_1' => $obj->Field_1,
'Pop_Dens' => $obj->Pop_Dens,
'Id' => $obj->Id,
'Pop_2017' => $obj->Pop_2017,
'Area_Sq'=> $obj->Area_Sq,
));
}
}
The attributes are under the properties key, but you're referencing them from the root of the object. e.g. $obj[0]->Name should be $obj[0]->properties->Name, etc.

Laravel more dimesions array validation

There is a request.How I can validate it? I trying with foreach, but i does not work. Thanks.
The request:
'show_data' => [
0 => [
'buyer_search_property_id' => 1,
'date_of_show' => '2019-01-01',
'comment' => 'Nice flat',
],
1 => [
'buyer_search_property_id' => 2,
'date_of_show' => '2019-01-31',
'comment' => 'Too small',
], etc...
],
I tried this, but it does not work (of course... :))
public function rules()
{
$rules = [];
foreach ($this['show_data'] as $key => $item) {
$rules["show_data[$key]['buyer_search_property_id']"] = 'required';
$rules["show_data[$key]['date_of_show']"] = 'required|date';
$rules["show_data[$key]['comment']"] = 'required';
}
return $rules;
}
This is from laravel documentation:
You may also validate each element of an array. For example, to validate that each e-mail in a given array input field is unique, you may do the following:
$validator = Validator::make($request->all(), [
'person.*.email' => 'email|unique:users',
'person.*.first_name' => 'required_with:person.*.last_name',
]);
Read more here
You can validate array in laravel easily like this:
public function rules()
{
return [
"show_data.*.buyer_search_propery_id" => "required",
"show_data.*.date_of_show" => "required|date",
"show_data.*.comment" => "required",
];
}
if you want your method to work, do this:
public function rules()
{
$rules = [];
foreach ($this['show_data'] as $key => $item) {
$rules["show_data.$key.buyer_search_property_id"] = 'required';
$rules["show_data.key.date_of_show"] = 'required|date';
$rules["show_data.$key.comment"] = 'required';
}
return $rules;
}
just remove the inner square brackets and the quotes and add dots to the indices, so change $rules["show_data[$key]['date_of_show']"] to $rules["show_data.$key.date_of_show"]
$data['show_data'] = [
0 => [
'buyer_search_property_id' => 1,
'date_of_show' => '2019-01-01',
'comment' => 'Nice flat',
],
1 => [
'buyer_search_property_id' => 2,
'date_of_show' => '2019-01-31',
'comment' => 'Too small',
],
];
$rules = [
'show_data.*.buyer_search_property_id' => 'required',
'show_data.*.date_of_show' => 'required|date',
'show_data.*.comment' => 'required',
];
$validator = Validator::make($data, $rules);
Note asteriks at rules. With this you don't need a foreach loop. And it looks and feels a lot cleaner.

Resources