Laravel array key validation - laravel

I have custom request data:
{
"data": {
"checkThisKeyForExists": [
{
"value": "Array key Validation"
}
]
}
}
And this validation rules:
$rules = [
'data' => ['required','array'],
'data.*' => ['exists:table,id']
];
How I can validate array key using Laravel?

maybe it will helpful for you
$rules = ([
'name' => 'required|string', //your field
'children.*.name' => 'required|string', //your 1st nested field
'children.*.children.*.name => 'required|string' //your 2nd nested field
]);

The right way
This isn't possible in Laravel out of the box, but you can add a new validation rule to validate array keys:
php artisan make:rule KeysIn
The rule should look roughly like the following:
class KeysIn implements Rule
{
public function __construct(protected array $values)
{
}
public function message(): string
{
return ':attribute contains invalid fields';
}
public function passes($attribute, $value): bool
{
// Swap keys with their values in our field list, so we
// get ['foo' => 0, 'bar' => 1] instead of ['foo', 'bar']
$allowedKeys = array_flip($this->values);
// Compare the value's array *keys* with the flipped fields
$unknownKeys = array_diff_key($value, $allowedKeys);
// The validation only passes if there are no unknown keys
return count($unknownKeys) === 0;
}
}
You can use this rule like so:
$rules = [
'data' => ['required','array', new KeysIn(['foo', 'bar'])],
'data.*' => ['exists:table,id']
];
The quick way
If you only need to do this once, you can do it the quick-and-dirty way, too:
$rules = [
'data' => [
'required',
'array',
fn(attribute, $value, $fail) => count(array_diff_key($value, $array_flip([
'foo',
'bar'
]))) > 0 ? $fail("{$attribute} contains invalid fields") : null
],
'data.*' => ['exists:table,id']
];

I think this is what you are looking:
$rules = [
'data.checkThisKeyForExists.value' => ['exists:table,id']
];

Related

Attempt to read property post_id on int

I have a small response from db model, and i colud rebase it and response in route, but i see error.
My Controller:
class PostController extends Controller {
public function getLastRecord() {
$lastRec = Post::latest('created_at')->first();
$res = [];
$res = collect($lastRec)->map(function($record) {
return [
'id' => $record->post_id,
'rec_name' => $record->post_name,
'user' => $record->user_id,
];
})->toArray();
return $res;
}
}
I want that response will be array
['id': 1, 'rec_name': 'test_name', 'user': 1]
instead names from db
['post_id': 1, 'post_name': 'test_name', 'user_id': 1]
Error:
Attempt to read property "post_id" on int
When you collect() a single Record, then call ->map() (or other iterative methods), it loops over your Model's columns, not multiple Post records. You can solve this by wrapping $lastRec in an array, or using ->get() instead of ->first():
$lastRec = Post::latest('created_at')->first();
return collect([$lastRec])->map(function($record) {
return [
'id' => $record->post_id,
'rec_name' => $record->post_name,
'user' => $record->user_id,
];
})->toArray();
// OR
$lastRecs = Post::latest('created_at')->get();
return $lastRecs->map(function ($record) {
return [
'id' => $record->post_id,
'rec_name' => $record->post_name,
'user' => $record->user_id,
];
})->toArray();
Or, since this is a single record (using ->first() only ever returns 1 record), you don't need to collect() or map() at all:
$lastRec = Post::latest('created_at')->first();
return [
'id' => $lastRec->post_id,
'rec_name' => $lastRec->post_name,
'user' => $lastRec->user_id
];

Problems with Laravel data validation

I'm having some problems with validations in my api.
I need to send a json array like this:
[
{
"acktime": "2021-09-25 08:45:07",
"temp": 15.6
},
{
"acktime": "2021-09-25 08:45:07",
"temp": 15.6
}
probably more array....
]
I would like to vaidate one by one array and store only the valid data returning error for unvalid data, I have tried a foreach cylce but it convert the array to object but the validate::make want only array.
I have tried this:
$validator = Validator::make($request->all(), [
'*.acktime' => 'required',
'*.temp' => 'required|numeric'
]);
$validatedData = $validator->validated();
var_dump($validatedData);
return response()->json($validatedData);
But If I send wrong data I get only error without having valid data, so I've tried this way:
foreach($datas as $data){
$arr = (array)$data;
$validator = Validator::make($arr, [
'acktime' => 'required',
'temp' => 'required|numeric'
]);
if ($validator->fails()) {
continue;
} else {
$newrawData = new rawData([
'acktime' => $data->acktime,
'temp' => $data->temp,
'synctime' => now()
]);
$newrawData->save();
}
}
return response('OK', 200); //or error if some data are not ok
}
In this way it work, bot I have no idea about get, a probable, validation error..(for the moment there's a continue for continue the cycle) any suggestion?
There are two ways for approaching this kind of validation:
make a custom rule in laravel validation from below and put your validation code in it and this will work:
https://laravel.com/docs/8.x/validation#custom-validation-rules
easier way:
$data = [ 'data' => $requests->all() ];
$validator = Validator::make($data, [
'data.*.name' => 'required|string',
'data.*.' => 'required|string'
]);

Verify duplicate values (multi column unique) on the array in Laravel5.7

relate as below issue
Verify duplicate values on the array in Laravel5.7
I am add two fields to data base.
// database/migrations/UpdateUsersTable.php
public function up()
{
Schema::table('users', function (Blueprint $table) {
$table->string('staff_no' , 10);
$table->string('staff_code');
$table->unique(['staff_no', 'staff_code']);
});
}
I want to verify if multi column unique in my database or post array value is duplicate or not?
Here is my codes :
this is my Controller
UsersController
public function MassStore(MassStoreUserRequest $request)
{
$inputs = $request->get('users');
//mass store process
User::massStore($inputs);
return redirect()->route('admin.users.index');
}
and this is my POST data (post data($inputs) will send like as below) :
'users' => [
[
'name' => 'Ken Tse',
'email' => 'ken#gamil.com',
'password' => 'ken12ken34ken',
'staff_no' => '20191201CT',
'staff_code' => 'IT-1azz',
],
[
'name' => 'Tom Yeung',
'email' => 'tom#gamil.com',
'password' => 'tom2222gt',
'staff_no' => '20191201CT', // staff_no + staff_code is duplicate, so need trigger error
'staff_code' => 'IT-1azz',
],
]
MassStoreUserRequest
public function rules()
{
return [
'users' => ['required','array'],
'users.*.name' => ['required'],
'users.*.email' => ['required','unique:users','email', 'distinct'],
'users.*.password' => ['required','string','min:8'],
'users.*.staff_no' => ['required','size:10'],
'users.*.staff_code' => ['required','string']
// how to set verify duplicate values(staff_no,staff_code unique) in here?
];
}
You can use distinct validation rule. So your code will look like-
public function rules()
{
return [
'users' => ['required','array'],
'users.*.name' => ['required'],
'users.*.email' => ['required','unique:users','email', 'distinct'],
'users.*.password' => ['required','string','min:8'],
'users.*.staff_no' => ['required','size:10'],
'users.*.staff_code' => ['required','string', 'distinct']
];
}
Change
`'users.*.staff_code' => ['required','string']` line to
'users.*.staff_code' => ['required','string', Rule::exists('staff')->where(function ($query) {
//condition to check if staff_code and staff_no combination is unique
return $query->where('staff_code', $request->('your_key')['staff_code'])->where('staff_no', $request->('your_key')['staff_no']) ? false : true; // You may need to make a loop if you can not specify key
}),]
I solve this problem myself.
https://laravel.com/api/5.7/Illuminate/Foundation/Http/FormRequest.html#method_validationData
main point is overrides method validationData(),make value "staff_no_code" to validation data.
Here is my codes :
MassStoreUserRequest
public function rules()
{
$validate_func = function($attribute, $value, $fail) {
$user = User::where(DB::raw("CONCAT(staff_no,staff_code )", '=', $value))
->first();
if (!empty($user->id)) {
$fail(trans('validation.alreadyExists'));
}
};
return [
'users' => ['required','array'],
'users.*.name' => ['required'],
'users.*.email' => ['required','unique:users','email', 'distinct'],
'users.*.password' => ['required','string','min:8'],
'users.*.staff_no' => ['required','size:10'],
'users.*.staff_code' => ['required','string']
// 'distinct' check when working with arrays, the field under validation must not have any duplicate values.
// $validate_func check DB exist
'users.*.staff_no_code' => ['distinct',$validate_func]
];
}
//make value "staff_no_code" to validation data
protected function validationData()
{
$inputs = $this->input();
$datas = [];
foreach ($inputs as $input ) {
$input['staff_no_code'] = $input['staff_no'] . $input['staff_code'];
$datas[] = $input;
}
return $datas;
}

How can i skip unique field in Yii2?

I have a unique field that I check when editing or adding a new training course. But, for some reason, when I enter a value in a field, it does not show me a hint that the field is already taken.
In addition, I need to do this: when I change the values and did not change this unique field, but left it as it is, then the validor should not swear that the field is already taken.
Thank.
InfCourses Model:
public function rules()
{
return [
[['name', 'short_description', 'price', 'favorite', 'active', 'course_order', 'link'], 'required'],
[['price', 'active'], 'integer'],
[['favorite'], 'string'],
[['name', 'short_description', 'link'], 'string', 'max' => 255],
[['active'], 'exist', 'skipOnError' => true, 'targetClass' => InfStatuses::className(), 'targetAttribute' => ['active' => 'id']],
[['course_order'], 'integer', 'min' => 1],
[
['course_order'], 'unique',
'targetAttribute' => ['course_order'],
'filter' => ['!=', 'id', Yii::$app->request->get('id')],
],
];
}
Validator in InfCoursesController:
public function actionValidate()
{
$model = new InfCourses();
if (Yii::$app->request->isAjax && $model->load(Yii::$app->request->post())) {
Yii::$app->response->format = Response::FORMAT_JSON;
return ActiveForm::validate($model);
}
}
Part of form code:
<?php $form = ActiveForm::begin([
'enableAjaxValidation' => true,
'validationUrl' => 'validate',
'options' => [
'data-pjax' => true,
]
]); ?>
Your validation is simply incorrect. You're using Yii::$app->request->get('id') in your rules, which is probably main source of your problems. Model should not access request or web user component directly - it breaks MVC pattern. Also putting values directly in rules in this way may give you unexpected results. You should check what query is generated by this validator, because it is hard to guess what is happening with such twisted rule.
But it may be easier to fix actionValidate() and distinguish between validating of new record and validating existing record:
public function actionValidate($id = null) {
if (empty($id)) {
$model = new InfCourses();
} else {
$model = $this->findModel($id);
}
if (Yii::$app->request->isAjax && $model->load(Yii::$app->request->post())) {
Yii::$app->response->format = Response::FORMAT_JSON;
return ActiveForm::validate($model);
}
}
Then you can limit your unique rule to:
[['course_order'], 'unique'],
Validator will be smart enough to detect that it is validating existing record and will not report unchanged fields values as duplicates. You just need to provide record ID in this action URL.
Well... I cut code below to each action: create/update.
if (Yii::$app->request->isAjax && $model->load(Yii::$app->request->post())) {
Yii::$app->response->format = Response::FORMAT_JSON;
return ActiveForm::validate($model);
}
Then remove validationUrl from form component. Inside model i make this rule [['course_order'],'unique']... Working fine...

Sort yii2 gridview by column that's not in a model

I have Gridview and one column value I get via http request. Is there a way to sort the table by this column?
myTableModel.php
class myTableModel extends \yii\db\ActiveRecord
{
...,
public function getExternalValue() {
$client = new Client();
return $client->createRequest()->setMethod('get')
->setUrl('http:://...')->setData(['id' => 1])->send()->content;
}
}
myTableModelSearch.php
class myTableModelSearch extends myTableModel
{
public function rules()
{
return [
[[...,'externalValue'], 'string'],
[[..., 'externalValue'], 'safe']
];
}
public $externalValue;
public function searchView($params) {
$query = SomeTable::find();
$dataProvider = new ActiveDataProvider(['query' => $query]);
$dataProvider->setSort(['attributes' => [
'externalValue' => [
'asc' => ['externalValue' => SORT_ASC],
'desc' => ['externalValue' => SORT_DESC]
]
]]);
if (!($this->load($params) && $this->validate()))
return $dataProvider;
return $dataProvider;
}
}
view.php
GridView::widget([
'dataProvider' => $dataProvider,
'filterModel' => $searchModel,
'columns' => [
... ,
[
'attribute' => 'externalValue',
'value' => function($item) {
return $item->externalValue;
},
]
],
]);
I also tried to add value to view simply with $item->getExternalValue() (and without public property set), but it makes no difference - when trying to sort I get database exception error SQLSTATE[42S22]: Column not found: 1054 Unknown column 'externalValue' in 'order clause'. How could I trick gridview, to make it sort my table by externalValue column?
You're using yii\data\ActiveDataProvider which uses an instance of ActiveQuery to find its data.
Try using yii\data\ArrayDataProvider, or extend yii\data\ActiveDataProvider to allow a second source for your data.
Additionally, you have to implement a sort function that can sort using your attribute.
see more here and here

Resources