belongsToMany multiple foreign keys, laravel - laravel

Good morning
I currently have a table that is related to 3 tables.
It has occurred to me to do "belongsToMany", but I do not know how to do it with 3 relationships
Tables:
schools_series (relation)
id
cycle_id
school_id
serie_id
cycle
id
active (true / false)
** more columns **
school
id
** more columns **
serie
id
** more columns **
Principal Model:
School
class School extends Model implements Presentable
{
use PresentableTrait;
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = [
'network_id',
'fantasy_name',
'social_name',
'email',
'sim_web_code',
'state_registration',
'cnpj',
'status',
'start_date',
];
public function series()
{
return $this->belongsToMany(Serie::class, 'schools_series')->where('id_cycle', 'xxxx');
}
I need to be able to take out the "school series" depending on the "cycle_id" that has "active" in cycle
example SQL:
SELECT ss.* FROM schools as s
INNER JOIN schools_series as ss
ON ss.school_id = s.id
INNER JOIN cycle as c
ON ss.cycle_id = c.id AND c.active = 1
WHERE s.id = 115

Solution:
public function series()
{
return $this->belongsToMany(Serie::class, 'schools_series')->withPivot('cycle_id')->join('cycle', 'cycle.id', '=', 'schools_series.cycle_id')->where('cycle.active', '=', 1);
}

Related

hasManyThrough query without laravel_through_key

With a Laravel hasManyThrough relationship how can I prevent extra keys added to the select? Is there a withoutPivot or simiar?
I'm trying to perform a union join and need to the columns to be the same.
SQLSTATE[21000]: Cardinality violation: 1222 The used SELECT statements have a different number of columns
SELECT
count(*) AS aggregate
FROM ((
SELECT
`order_payments`.`id`,
`order_payments`.*,
`orders`.`user_id` AS `laravel_through_key`
FROM
`order_payments`
INNER JOIN `orders` ON `orders`.`id` = `order_payments`.`order_id`
WHERE
`orders`.`user_id` = 1)
UNION (
SELECT
`orders`.`id`
FROM
`orders`
WHERE
`orders`.`user_id` = 1
AND `orders`.`user_id` IS NOT NULL)) AS `temp_table`
I'm unable to use the makeHidden as suggested in another question
$payments = auth()->user()->orderPayments()->select('order_payments.id');
$orders = auth()->user()->orders()->select('orders.id');
$payments->union($orders)->paginate(50);
I guess I could do it without Eloquent manually but just wondering if there was another way?
class User extends Authenticatable implements UserContract {
/**
* Orders for this User
*/
public function orders()
{
return $this->hasMany(Order::class);
}
/**
* Order Payments for this User
*/
public function orderPayments()
{
return $this->hasManyThrough(OrderPayment::class, Order::class);
}
}

Eloquent whereHas Relationship with constraint on particular related Entity

Consider having two models User, and Book the last one has a status column that can obtain different string values active, inactive, deleted, so the user can have multiple books and the book belongs to the user.
how could I get only users that have their last book status = 'inactive'?
The SQL Query for the behavior is given below:
SELECT
*
FROM
`users`
WHERE EXISTS
(
SELECT
*
FROM
`books`
WHERE
`books`.`user_id` = `users`.`id` AND `books`.`status` = 'inactive' AND `books`.`id` =(
SELECT
nested.`id`
FROM
`books` AS nested
WHERE
nested.`user_id` = `users`.`id`
ORDER BY
nested.`created_at` DESC
LIMIT 1
)
)
I'm using Laravel 5.6
Create additional relationship in User model that returns wanted result. Basically you need 1-1 relationship for this.
/**
* #return \Illuminate\Database\Eloquent\Relations\HasOne
*/
public function inactiveBookStillLatestPerUser()
{
return $this->hasOne(Book::class)->where(['status' => 'inactive', 'id' => function (\Illuminate\Database\Query\Builder $nested) {
$nested->from('books as nested')
->selectRaw('max(id)')
->whereRaw('nested.user_id = books.user_id');
}]);
}
Then in somewhere in code (i.e. controller) you call it with
$users = User::has('inactiveBookStillLatestPerUser')->get();
// or if books are needed too
// $users = User::has('inactiveBookStillLatestPerUser')->with(['inactiveBookStillLatestPerUser'])->get();
I used id latest order [max(id)] in subquery to avoid unwanted result if one user made multiple books batch insert at same point of time and when all those books would have same time of insert so latest per created_at wouldn't be most accurate, maybe. But you can do that similarly, instead:
/**
* #return \Illuminate\Database\Eloquent\Relations\HasOne
*/
public function inactiveBookStillLatestPerUser()
{
return $this->hasOne(Book::class)->where(['status' => 'inactive', 'created_at' => function (\Illuminate\Database\Query\Builder $nested) {
$nested->from('books as nested')
->selectRaw('max(created_at)')
->whereRaw('nested.user_id = books.user_id');
}]);
}
Maybe second example is ok, but first example with id would work fine, though.
User::where('your-conditions')
->whereHas('books', function($query) {
$query->where('books.status', '=', 'inactive')
->orderBy('id', 'desc')
->first();
})->get();

How to update product quantity after 'approved' status?

I have these tables:
products:
id - name - quantity - etc
2 watch 4
4 T-shirt 2
orders:
id - user_id - address_id - etc
pivot table between them called order_product
order_id - product_id - quantity
1 2 3
1 4 1
The relation:
/**
* Products table relation
*/
public function products()
{
return $this->belongsToMany('App\Models\Product')->withPivot('quantity');
}
/**
* Orders table relation
*/
public function orders()
{
return $this->belongsToMany('App\Models\Order')->withPivot('quantity');
}
I need when I update the status to approved then update my quantity product!
My shut: ( Here I'm confused because the product can be many! the quantity as well )
public function status_orders(Order $order ,Request $request)
{
if($request->status == 'approved'){
$product_quantity = $order->products->pluck('quantity');
foreach($order->products as $order){
$order_quantity = $order->pivot->quantity;
}
$final_quantity = $product_quantity - $order_quantity;
}
}
you want to update product table quantity after an order is approved. right?? you can do it the following way
public function status_orders(Order $order ,Request $request) {
if($request->status == 'approved') {
foreach($order->products as $product) {
$product->update([
'quantity' => $product->quantity - $product->pivot->quantity
]);
}
}
}

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 5.1 update pivot table column without dropping existing records

I am using Laravel 5.1.
I have two tables namely users and medicines with many-to-many relationship. The related models are as below:
class User extends BaseModel {
use SoftDeletes;
protected $dates = ['deleted_at'];
protected $table = 'users';
public function medicines(){
return $this->belongsToMany('App\Models\Medicine')->withPivot('id', 'time','config' ,'start','end' );
}
}
and
class Medicine extends BaseModel{
use SoftDeletes;
protected $dates = ['deleted_at'];
protected $table = 'medicines';
public function users(){
return $this->belongsToMany('App\Models\User')->withPivot('id', 'time','config' ,'start','end' );
}
}
The many-to-many relational table user_medicine having some additional pivot columns as below:
id(PK) user_id(FK users) medicine_id(FK medicines) time config start end
1 1 41 09:00 {dispense:2} 2015-12-01 2015-12-25
2 1 43 10:00 {dispense:1} 2015-12-10 2015-12-22
3 1 44 17:00 NULL 2015-12-10 2015-12-31
Now, I want to change the time of specific medicine. That I have made codes as below:
public function updateMedicationTime($fields){
$result = array();
try {
$user = User::find($fields['user_id']);
$medicines = $user->medicines()->where('medicines.id',$fields['medicine_id'])->first();
if (!empty($medicines)){
$medicine_times = json_decode($medicines->pivot->time);
foreach ($medicine_times as $key=>$medicine_time ){
if ($medicine_time->time == $fields['oldtime']){
$medicine_time->time = $fields['newtime'];
}
}
// Update the time column of provided medicine id
$user->medicines()->where('medicines.id',$fields['medicine_id'])->sync(array($medicines->id , array("time"=>json_encode($medicine_times))), false);
// I also have tried
// $medicines->sync(array($medicines->id , array("time"=>json_encode($medicine_times))), false); OR
// $medicines->pivot->sync(array($medicines->id , array("time"=>json_encode($medicine_times))), false);
}
}
catch(Exception $e ){
throw $e;
}
return $result;
}
But rather than updating the exiting many-to-many table's time column, it is inserting a new record with medicine id = 1 with provided time and null other columns. Can anyone suggest where am I making a mistake?
There is a method called updateExistingPivot that you can use:
$user = User::find($id);
$user->medicines()->updateExistingPivot($medicine_id, ['time' => $time], false);
The last parameters specifies if the parent table should be "touched", meaning that the timestamps of the updated_at fields are updated.

Resources