Laravel Eager loading ,relationship in builder be a value directly - laravel

it's a row with another row, condition on id to info_status
relationship
public function status(){
return $this->hasOne(Row::class,'id','info_status')->select("id" ,"row")
}
result
Row::with('status')->find(5588)
=> App\Models\Row {#3563
id: 5588,
info_status: "2637",
status: App\Models\Row {#3576
id: 2637,
row: 1104,
},
}
how to get status->row->value ,become status->value at preloading sql builder ?
Is it possible ? doc didn't mention it.
//expect
=> App\Models\Row {#3563
id: 5588,
info_status: "2637",
status: 1104,
},
}

Assuming table name as rows, here is my solution:
$row = \Illuminate\Support\Facades\DB::table('rows')
->join('rows as related_rows', 'rows.info_status', 'related_rows.id')
->select('related_rows.row as status', 'rows.id', 'rows.info_status')
->get();

You'll need to make use of an accessor.
The accessor retrieves the row value from the relationship and returns it as an attribute.
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Model;
class Row extends Model {
public $appends = ['status'];
//Relationship status
public function statusRow(){
return $this->hasOne(Row::class,'id','info_status');
}
/**
* Accessor to create status attribute and retrieve row value
* #return \Illuminate\Database\Eloquent\Casts\Attribute
*/
*/
public function getStatusAttribute() : Attribute
{
return Attribute::make(
get: fn () => $this?->statusRow->row;
);
}
}
Source: https://laravel.com/docs/9.x/eloquent-mutators#defining-an-accessor

Related

Laravel - Dynamically add field to model and prevent its save

I need to add a field to a model, and this field is not a real column, for example:
function calculateDiscount(Order $order)
{
// some logic
$order->discount = 20; // It's just an example
return $order
}
The field discount is not a real column to database. I need to add this "dynamic" field to use later.
If I perform a save like:
$order->save();
I get the error:
Column not found: 1054 Unknown column 'discount' in 'field list' (SQL: update `orders` set `discount` = 20, where `id` = 67586)
There is a way to add a "dynamic" field and prevent Laravel save it ?
use appends
Models/Order.php
class Order extends Model {
protected $appends = [
'discount'
];
public function getDiscountAttribute() {
// your logic of how to get discount value
return $discount;
}
}
or you can load the discount attribute only when you need
// controller
$order->append('discount'); // load the value
// model
class Order extends Model {
public function getDiscountAttribute() {}
}
// add the getDiscountAttribute function only

Update all records through model relation ship in laravel

class ModelA {
public function modelB()
{
return $this->hasMany(ModelB::class);
}
}
class ModelB {
public function modelC()
{
return $this->hasMany(ModelC::class);
}
}
class ModelC {
public function modelD()
{
return $this->hasMany(ModelD::class);
}
}
class ModelD {
//has column status_changed_date
}
I have $modelA = ModelA::find($id);
ModelA have multiple ModelB, ModelB have multiple ModelC, ModelC has multiple ModelD.
Now I want update status_changed_date for all matching records.
Is there any better way to do this. I have refered https://laravel.com/docs/5.7/eloquent-relationships#updating-many-to-many-relationships
but couldn't find out a solution.
Please help to solve this.
$modelAs = ModelA::with('modelB.modelC.modelD')->find($id)
before you can use that way :
$modelA->modelB->each->modelC->each->modelC->each->update([
'field' => 'value'
]);
or reduce values to ids, this only made one query :
$ids = [];
$modelA->modelB->each->modelC->each->modelC->each(function ($c) use($ids) {
$ids[] = $c->id;
});
ModelC::whereIn('id', $ids)->update([
'field' => 'value'
});
if each->modelC->each is not working, use method notation
Instead of making all this update, make one common table of common fields and directly update that table.
For more
Laravel Polyformic relationships

Multiple options to questions using eloquent

Im creating a survey app, and basically when a user selects a input type of "select" it appears a option input that can be dynamiclly increase, but that im having some issues in inserting these options in my database, im sugin the sync method to insert these options in my table, but is giving me a error of
SQLSTATE[42S02]: Base table or view not found: 1146 Table 'toufarto.question_id' doesn't exist (SQL: select `option_question_id` from `question_id` where `id` = 11)
Here is my code:
Tables:
questions:
- id;
- input_type;
option_question
- id;
- label_option;
- question_id
My controller:
public function store(Request $request)
{
$this->validate($request, array(
'label_option' => 'max:255',
'input_type' => 'required|integer'
));
$question = new Question();
$question->input_type = $request->input_type;
$question->save();
$question->options()->sync($request->option, false);
Session::flash('success', 'success');
return back();
}
My Question Model:
public function options()
{
return $this->belongsToMany(OptionQuestion::class,'question_id','id');
}
My OptionQuestion Model:
class OptionQuestion extends Model
{
protected $table = "option_question";
}
Note: How i could add the label column to the "sync" method, since i need to insert the label from the option field of the form
As mentioned in the docs of Laravel you need to define your parameters as follows:
$this->belongsToMany(Model,table_name);
or
$this->belongsToMany(Model, table_name, table_1_key, table_2_key);
Looking at your DB schematic however it seems you should make a small adjustment.
questions
- id
- input_type;
options
- id
- label_option
option_question
- id
- question_id
- option_id
Question model
public function options()
{
return $this->belongsToMany(OptionQuestion::class);
}
Options model
public function questions()
{
return $this->belongsToMany(Question::class);
}

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.

Eloquent - Sums of multiple columns in an array

If I get sum of a column using Model::sum('column') , How can I get sums of multiple column returned in an array?
Using sum() for multiple columns is inefficient because it will trigger multiple queries. Add this to your model:
public static function sums($columns){
$instance = new static;
$columns = is_array($columns) ? $columns : func_get_args();
$selects = array_map(function($column){
return DB::raw('SUM('.$column.') AS '.$column);
}, $columns);
return $instance->select($selects)->first();
}
Usage:
Model::sums(array('column', 'foo', 'bar'));
// or
Model::sums('column', 'foo', 'bar');
Can you just do
$total = Model::sum('column1') + Model::sum('column2') + Model::sum('column3');
Edit:
You could just make a function in your Model
class Model extends Eloquent {
public static function getColumn($column_name) {
return Model::sum($column_name);
}
}
Then in your view you just do
$value->getSum('column');

Resources