yii2 Validation not working - validation

I need to validate a value from another table both of them field type decimal
if amount(model 1) moreover available(model2 DATA selected)
model 1
rule
public function rules()
{
return [
[['amount'], 'checkAvailable','skipOnEmpty' => false],
];
}
custom validation function
public function checkAvailable($attribute, $params)
{
$caid = $this->capex_budget_id;
$available = Capexbudget::find()
->select(['available'])
->where('id = :caid', [':caid' => $caid])
->all(); // select amount from Capexbudget where id = $caid
if ($this->amount > $available){
$this->addError('amount', 'not enough');
}
}
i can got a data available from Capexbudget where ID that i select
here are the query logs
but validation not working it not compare value between $this->amount and $available
what i missing please.

First of all, you're selecting all records matching your where conditions here:
$available = Capexbudget::find()
->select(['available'])
->where('id = :caid', [':caid' => $caid])
->all(); // HERE
You should change function all() to one(), to get one record.
Second thing is, if you're using all() or one(), that methods returns object, not value.
To make it work, you should change if statement to:
if ($this->amount > $available->available){ //here, compare to attribute `available`

Related

Array on where clause laravel

I have an array of values I want to pass into my where clause when querying a model
How do I achieve this?
I have $categorypro variable getting below array in this format
Array
(
[0] => 4
[1] => 8
[1] => 5
)
Currently I am doing this in controller but not getting proper data some category
Below is my controller code :
function filter_each_video(Request $request){
if(request()->ajax()){
if($request->has('categorypro') && ($request->get('categorypro')!= null)){
$categorypro = $request->get('categorypro');
$item = Item::whereIn('category_id', $categorypro)->get();
}
}
}
If any one have better idea then let me know . Or something wrong in this then give some idea
Generally you would use a 'whereIn' clause:
MyModel::whereIn('attribute', ['value', 'value', 'value'])
->get();
First, you already have the $request in your method, so you don't need to call request(). Always make sure you're validating any data you're using from a request. Second, you can get the query first, then filter it if necessary depending on the post data. Only put your whereIn filter inside the if block.
public function filter_each_video(Request $request)
{
$request->validate([
'categorypro' => 'nullable|array',
'categorypro.*' => 'int'
]);
$query = Item::query();
if ($request->ajax() && $request->categorypro) {
$query->whereIn('category_id',$request->categorypro);
}
$categories = $request->categorypro
? Category::whereIn('id',$request->categorypro)->get()
: null;
$items = $query->get();
return view('myview',compact('items'));
}

Filter an array list result from query builder in model in Laravel

I have a query function in my model to get list user and it return an array data to my repository class:
Model.php:
public function getUser()
{
return $this->select("{$this->table}.'*'")
->get();
}
Here is an example for the data:
[
[
"user_id":1,
"fullname":"amdv",
"is_active":0
],
[
"user_id":2,
"fullname":"abc",
"is_active":1
],
[
"user_id":3,
"fullname":"zyz",
"is_active":1
]
]
Now I want to check if is_active = 1, it continue check that user with other condition in other query
In my model, I have another query function with param is user id
Model.php:
public function checkUserAvailable($userId)
{
return $this->select("{$this->table}.'*'")
->join('other_table', 'join-condition')
->where('user_id', $userId)
->get();
}
But this function return data too, I don't know how to check this.
Thank you very much!
$data = [
[
"user_id":1,
"fullname":"amdv",
"is_active":1
],
[
"user_id":2,
"fullname":"abc",
"is_active":1
],
[
"user_id":3,
"fullname":"zyz",
"is_active":1
]
];
$filtered = collect($data)->filter(function($user) {
return User::someMethodYouMentioned($user['user_id']);
});
From what I understand is that you want to check the data from the array returned by the first function and if is_active = 1 then you want to check if the user is available via the second function in your model
//Get the user_id of records where is_active === 1
$available = collect($data)
->reject(fn($user) => $user['is_active'] !== 1)
->pluck('user_id')
->map(fn($id) => User::checkUserAvailable($id));
For this to work the checkUserAvailable needs to be a static function.
However looking at both the functions - question arises why do you need to run multiple database queries to check for user available via the second function.
You can modify the query in the second function to check for is_active
public static function checkUserAvailable()
{
return static::query()
->join('other_table', 'join-condition')
->where('is_active', 1)
->get();
}
Or if you still want to first get a list of all users as array and then check for availability, you could define the checkUserAvailable as a scope on the model
public function scopeCheckUserAvailable($query, $id)
{
$ids = Arr::wrap($id);
return $query->join('other_table', 'join-condition')
->whereIn('user_id', $ids);
}
Then you can get the data from the getUser() function and perform the second check like
//Get the user_id of records where is_active === 1
$checkIds = collect($data)
->reject(fn($user) => $user['is_active'] !== 1)
->pluck('user_id')
->all();
$availableUsers = User::checkAvailableUsers($checkIds)->get();
If you want to filter out the ones that are not active you can use array_filter with a callback:
$filtered = array_filter($array, fn ($i) => $i['is_active']);
If you want to use those values to call a function and then get those results you can use array_map:
$res = array_map([$this, 'checkUserAvailable'], array_column($filtered, 'user_id'));
You can also do this with the Collection methods if you would like:
collect($array)->where('is_active', 1)
->pluck('user_id')
->map([$this, 'checkUserAvailable'])
Though, I am not sure what else you need to be checking besides that is_active is true.

Laravel firstOrCreate without Eloquent

Eloquent has a firstOrCreate method which gets a model based on a condition, or creates it if it doesn't exist.
Is there any equivalent method in Laravel's query builder (i.e. NOT in Eloquent)? For example:
$row = DB::table('users')->where('user_id', 5)->firstOrCreate('name' => 'Peter', 'last_name' => 'Pan');
That would try to get a row from users with 'user_id'==5. If it doesn't exist, it would insert a row with that id number, plus the other mentioned fields.
EDIT: I'm not trying to apply my question with users. I used users as an example to make as clear as possible what I'm looking for.
updateOrInsert function with empty values give me the result like firstOrCreate
Nope, Laravel firstOrCreate is function, that says next:
public function firstOrCreate(array $attributes, array $values = [])
{
if (! is_null($instance = $this->where($attributes)->first())) {
return $instance;
}
return tap($this->newModelInstance($attributes + $values), function ($instance) {
$instance->save();
});
}
But you can add it with query micro:
DB::query()->macro('firstOrCreate', function (array $attributes, array $values = [])
{
if ($record = $this->first()) {
// return model instance
}
// create model instance
});
So than you will be able to call it same way you do with Eloquent.
$record= DB::table('records')->where('alias', $alias)->firstOrFail();
Yeah of course! Just use normal SQL and ->selectRaw( your conditions ) and look for if there is a entry where your specifications are.
https://laravel.com/docs/5.7/queries#raw-expressions

Yii2 Activerecord get fields from junction table and order by according to them

I have items and units table that have many to many relationship. In other words, the item has many units and the unit has many items. I managed the relation through a junction table item_units. The junction table has some extra field more than item_id and unit_id, i.e it has price, and weight (it is an integer to manage the order of units for each item for display purposes).
I managed the relations in the models as follows:
//In Items model
/**
* #return \yii\db\ActiveQuery
*/
public function getItemUnits()
{
return $this->hasMany(ItemUnits::className(), ['item_id' => 'id'])->orderBy(['item_units.weight' => SORT_DESC]);
}
public function getUnits()
{
return $this->hasMany(Units::className(), ['id'=> 'unit_id'])->select(['id','title'])->via('itemUnits');
}
//
//In Units model
public function getItemUnits()
{
return $this->hasMany(ItemUnits::className(), ['unit_id' => 'id'])->orderBy(['price' => SORT_DESC]);
}
/**
* #return \yii\db\ActiveQuery
*/
public function getItems()
{
return $this->hasMany(Items::className(), ['id' => 'item_id'])->via('itemUnits');
}
//
//In ItemUnits model
public function getItem()
{
return $this->hasOne(Items::className(), ['id' => 'item_id']);
}
/**
* #return \yii\db\ActiveQuery
*/
public function getUnit()
{
return $this->hasOne(Units::className(), ['id' => 'unit_id']);
}
In the controller I'm able to get the data of all related units to an item by something like the following:
$item = Items::findOne($id);
return Json::encode($item->units);
The following is a demo of the JSON object obtained:
[{"id":"4","title":"قرص"},{"id":"5","title":"شريط 10"},{"id":"6","title":"علبة 2 شريط"}]
However, I could not able to order the results according to the weight field in item_units table and also I could not able to include the price field there in the demo result above -JSON Object-.
I only able to get data in item_units as a separate result like the following:
return Json::encode($item->itemUnits);
Update
According to the two answers (#Александр Шалаев & #Onedev.Link) , I have overridden the fields method in Units model as follows:
public function fields() {
parent::fields();
return [
'price' => function($model){
return $model->id; //Here I could not able to get the corresponding price field value from item_units -junction table-
},
'id',
'title',
];
}
However, I could not able to get the price field value from the junction table, temporary, I set it to current model id to prevent error generation. Also, I still has no any mean to set order by using weight field in that junction table.
Update 2
In other words, how could Yii2 Activerecords perform the following SQL query:
SELECT units.id UnitID, units.title Unit, iu.weight, iu.price
FROM units
Left JOIN item_units AS iu
ON iu.item_id = 1 AND iu.unit_id = units.id
WHERE
units.id = iu.unit_id
ORDER BY iu.weight;
Finally I have found a solution. It depends on findBySql method. I'm going to use the above SQL query regarded in Update 2 -just I have removed some selected fields to be suitable for my current task-.
public function actionUnitsJson($id){
$sql = 'SELECT units.id, units.title
FROM units
Left JOIN item_units AS iu
ON iu.item_id = :id AND iu.unit_id = units.id
WHERE
units.id = iu.unit_id
ORDER BY iu.weight DESC;';
$units = \common\models\Units::findBySql($sql,[':id' => $id])->asArray()->all();
return Json::encode($units);
}
You need fields or extraFields in your ActiveRecord model with asArray.
Example:
/**
* #return array
*/
public function fields()
{
return [
'itemUnit', //will get getItemUnit method
];
}
or
/**
* #return array
*/
public function extraFields()
{
return [
'itemUnits', //it is relation name
];
}
Usage:
$model->toArray(); //will contains fields and extra fields relations
... sort array & return
By default, yii\base\Model::fields() returns all model attributes as fields, while yii\db\ActiveRecord::fields() only returns the attributes which have been populated from DB.
You can override fields() to add, remove, rename or redefine fields. The return value of fields() should be an array. The array keys are the field names, and the array values are the corresponding field definitions which can be either property/attribute names or anonymous functions returning the corresponding field values.

Laravel 4: Unique Validation for Multiple Columns

I know this question has been asked earlier but i did not get relevant answer.
I want to know that how can i write a rule to check uniqueness of two columns. I have tried to write a rule like:
public $rules = array(
"event_id"=>"required",
"label"=>"required|unique:tblSection,label,event_id,$this->event_id",
"description"=>"required"
);
In my example i need to put validation so that one label could be unique for a single event id but may be used for other event id as well. For Example i want to achieve:
id event_id label description
1 1 demo testing
2 2 demo testing
In the rule defined above, somehow i need to pass current selected event_id so that it could check whether the label does not exist in the database table for selected event_id but i am getting syntax error like:
{"error":{"type":"Symfony\\Component\\Debug\\Exception\\FatalErrorException","message":"syntax error, unexpected '\"'","file":"\/var\/www\/tamvote\/app\/modules\/sections\/models\/Sections.php","line":39}}
Note: I don't want to use any package but simply checking if laravel 4 capable enough to allow to write such rules.
The answer from Mohamed Bouallegue is correct.
In your controller for the store method you do:
Model::$rules['label'] = 'required|unique:table_name,label,NULL,event_id,event_id,' .$data['event_id'];
where $data is your POST data.
And for the update method you do:
$model = Model::find($id);
Model::$rules['label'] = 'required|unique:table_name,label,NULL,event_id,event_id,'.$data['event_id'].',id,id'.$model->id;
where $data is your PUT/PATCH data, $model is the record you are editing and id is the table primary key.
I didn't try this before but I think if you get the event_Id before validating then you can do it like this:
'label' => 'unique:table_name,label,NULL,event_id,event_id,'.$eventId
//you should get the $eventId first
If you want to declare your validation rules statically you can do this as well. It's not the most efficient since it checks the database for each value.
protected $rules = [
'user_id' => 'unique_multiple:memberships,user_id,group_id',
'group_id' => 'unique_multiple:memberships,user_id,group_id',
]
/**
* Validates that two or more fields are unique
*/
Validator::extend('unique_multiple', function ($attribute, $value, $parameters, $validator)
{
//if this is for an update then don't validate
//todo: this might be an issue if we allow people to "update" one of the columns..but currently these are getting set on create only
if (isset($validator->getData()['id'])) return true;
// Get table name from first parameter
$table = array_shift($parameters);
// Build the query
$query = DB::table($table);
// Add the field conditions
foreach ($parameters as $i => $field){
$query->where($field, $validator->getData()[$field]);
}
// Validation result will be false if any rows match the combination
return ($query->count() == 0);
});
Like Sabrina Leggett mentioned, you need to create your own custom validator.
Validator::extend('uniqueEventLabel', function ($attribute, $value, $parameters, $validator) {
$count = DB::table('table_name')->where('event_id', $value)
->where('label', $parameters[0])
->count();
return $count === 0;
}, 'Your error message if validation fails.');
You can call your validator by adding the following line to your rules:
'event_id' => "uniqueEventLabel:".request("label")
If you need more fields, you could add a new where clause to the sql statement.
(Source: edcs from this answer)
As you I was looking for hours to do that but nothing worked, I test everything ... suddenly the randomness of the doc I came across this:
'email' => Rule::unique('users')->where(function ($query) {
return $query->where('account_id', 1);
})
https://laravel.com/docs/5.5/validation#rule-unique
and it works perfectly and moreover it is very flexible :)

Resources