I have next structure pivot table: products_equipment_value(id, product_id, equipment_id, value_id).How to update table fields equipment_id and value_id ?
public function equipments()
{
return $this->belongsToMany('App\Equipment', ' product_equipment_value');
}
public function values()
{
return $this->belongsToMany('App\Value', ' product_equipment_value', 'product_id', 'value_id')
}
Use(not work)
$product->equipments()->detach();
foreach($data['eq'] as $key => $val){
$product->equipments()->attach([
'equipment_id' => $key,
'value_id' =>$val
]);
}
You should use withPivot function on the relations.
public function equipments()
{
return $this->belongsToMany('App\Equipment', 'product_equipment_value')->withPivot('value_id');
}
Then when you attach the models
$product->equipments()
->attach([
$key => ['value_id' =>$val],
]);
Referring to Many to Many Eloquent's relationship (https://laravel.com/docs/5.5/eloquent-relationships#many-to-many) you can sync IDs like this :
$product->equipments()->sync([array_of_equipments_ids]);
Example:
$product->equipments()->sync([3, 8, 9, 24]);
If you defined the Many to Many inverse relationship you can also do this that way from the equipment instance :
$equipment->values()->sync([array_of_values_ids]);
If you have extra columns with your pivot table you can add them like this :
public function equipments(){
return $this->belongsToMany('App\Equipment', 'product_equipment_value')->withPivot('extra_column1');
}
And so :
$product->equipments()->sync([1 => ['extra_column1' => 'Value for this column']])
Note that you can use sync() or attach() methods to construct many-to-many associations. You can take a look here : https://stackoverflow.com/a/23969879/8620746
Related
I have a query where I get values from 3 tables, for first 2 I use leftJoin, and is Ok, but for third one I try to get an array of objects, and I am not sure how.
In relationship table a have multiple rows for each ID from People table. HasMany type.
$q = Person::leftJoin('registers', 'people.register_id', '=', 'registers.id')
->leftJoin('relationships', 'people.id', '=', 'relationships.person_id') //if I comment this it works for first 2 tables
->find($id);
return response()->json($q);
Person
public function relationship()
{
return $this->hasMany(Relationship::class);
}
public function register()
{
return $this->belongsTo(Register::class);
}
Relationship
public function person()
{
return $this->belongsTo(Person::class, 'person_id');
}
Register
public function people(){
return $this->hasOne(Person::class);
}
UPDATE -> this works,but is kind of ugly, I think that should be a better way in Laravel
$q = Person::leftJoin('registers', 'people.register_id', '=', 'registers.id')
->find($id);
$q2 = Person::find($id)->relationship;
return response()->json([
'values' => $q,
'relationship' => $q2,
]);
You can just use with like this:
Person::with(['register', 'relationship'])->find($id);
I'm building a small application where I'm having many to many relationship between two models something like this:
class Contact extends Model
{
public function company()
{
return $this
->belongsToMany('App\Company', 'company_contact', 'company_id', 'contact_id')->withTimestamps();
}
}
Now while retrieving this I want only the latest model through the pivot table or you may say relational table, for this I'm trying to implement:
public function getData()
{
$allData = Contact::all();
foreach($allData as $data)
{
$getCompany = $data->company()->latest()->first();
$data->company = $getCompany;
}
return response()->json(['model' => $allData], 200);
}
But I'm unable to retrieve the latest table it is showing the same old value or the first value.
Guide me how can I achieve this.
You can try this :
latest() is a function defined in Illuminate\Database\Query\Builder Class.
public function latest($column = 'created_at')
{
return $this->orderBy($column, 'desc');
}
So, It will just orderBy with the column you provide in descending order with the default column will be created_at
OR
public function getData()
{
$allData = Contact::all();
foreach($allData as $data)
{
$getCompany = $data->company()->orderBy('created_at', 'desc')->first();
$data->company = $getCompany;
}
return response()->json(['model' => $allData], 200);
}
So basically idea is if you are finding the relational data from many to many relation like $data->company() in this question and try to sort this with latest it will sort the company table with created_at sorting desc in order to get relational latest data you need to sort through pivot tables i.e.
$getCompany = $data->company()->withPivot('created_at')->orderBy('pivot_created_at', 'desc')->first();
This is how I achieved the latest relational table.
Note: You must have pivot table in your relation, in this answer created_at is the pivot field I'm using.
In order to update single record in pivot table I use updateExistingPivot method. However it takes $id as the first argument. For example:
$step->contacts()->updateExistingPivot($id, [
'completed' => true,
'run_at' => \Carbon\Carbon::now()->toDateTimeString()
]);
But how can I update multiple existing rows in pivot table at once?
There's an allRelatedIds() method in the BelongsToMany relation that you can access, which will return a Collection of the related model's ids that appear in the pivot table against the initial model.
Then a foreach will do the job:
$ids = $step->contacts()->allRelatedIds();
foreach ($ids as $id){
$step->contacts()->updateExistingPivot($id, ['completed' => true]);
}
You can update only by using a looping statement as there updateExistingPivot function only accept one dimensional params, See the core function for laravel 5.3.
File: yoursite\vendor\laravel\framework\src\Illuminate\Database\Eloquent\Relations\BelongsToMany.php
Function: updateExistingPivot
public function updateExistingPivot($id, array $attributes, $touch = true)
{
if (in_array($this->updatedAt(), $this->pivotColumns)) {
$attributes = $this->setTimestampsOnAttach($attributes, true);
}
$updated = $this->newPivotStatementForId($id)->update($attributes);
if ($touch) {
$this->touchIfTouching();
}
return $updated;
}
So, You should follow the simple process:
$step = Step::find($stepId);
foreach(yourDataList as $youData){
$step->contacts()->updateExistingPivot($youData->contract_id, [
'completed' => true,
'run_at' => \Carbon\Carbon::now()->toDateTimeString()
]);
}
I have a many to many relation:
Parent(id) <- Link(parent,child,sort) -> Product(sku)
I want to get a sorted list of Products from the Parent model
On the parent model I have a couple of functions:
public function getLinksDown()
{
return $this->hasMany( DBLink::className(), ['parent' => 'id'])
->from(['linksDown' => DBLink::tableName()]);
}
public function getProducts()
{
return $this->hasMany(DBProduct::className(), ['sku' => 'child'])
->from(['products' => DBProduct::tableName()])
->via('linksDown')
->innerJoin(
['linx' => DBLink::tableName()],
'linx.child = products.sku AND linx.parent=:parent',
[':parent' => $this->id]
)
->orderBy(['linx.order' => SORT_ASC]);
}
If I call the relation directly it works fine.
$model = Parent::findOne(1234);
$products = $model->products;
Products is a list of products sorted correctly.
If the relation is called as a part of a "with" it fails, $this->id will be null which means no products are returned.
Parent::find()->with('products')->all();
After much head scratching, I have decided the only solution, is to abandon the Many to Many relation where I need a sorted list of products, and do something like this:
$model = Parent::find()->where(['id' => 1234])->with('linksDown.products')->one();
and reference each product through the linksDown relation, instead of directly from the parent model.
foreach( $model->linksDown as $link ) {
dosomethingwith($link->product);
}
In order to make this work the sort will need to be applied to the linksDown relation:
public function getLinksDown()
{
return $this->hasMany( DBLink::className(), ['parent' => 'id'])
->from(['linksDown' => DBLink::tableName()])
->orderBy([linksDown.order => SORT_ASC]);
}
I have three database table:
product (id, name)
product_has_adv (product,advantage,sort,important)
advantage (id, text)
In ProductModel I defined this:
public function getAdvantages()
{
return $this->hasMany(AdvantageModel::className(), ['id' => 'advantage'])
->viaTable('product_has_advantage', ['product' => 'id']);
}
I get the advantages without any problems.
But now I need to add a where product_has_advantage.important = 1 clausel and also sort the advantages by the sort-columen in the product_has_advantage-table.
How and where I have to realize it?
Using via and viaTable methods with relations will cause two separate queries.
You can specify callable in third parameter like this:
public function getAdvantages()
{
return $this->hasMany(AdvantageModel::className(), ['id' => 'advantage'])
->viaTable('product_has_advantage', ['product' => 'id'], function ($query) {
/* #var $query \yii\db\ActiveQuery */
$query->andWhere(['important' => 1])
->orderBy(['sort' => SORT_DESC]);
});
}
The filter by important will be applied, but the sort won't since it happens in first query. As a result the order of ids in IN statement will be changed.
Depending on your database logic maybe it's better to move important and sort columns to advantage table.
Then just add condition and sort to the existing method chain:
public function getAdvantages()
{
return $this->hasMany(AdvantageModel::className(), ['id' => 'advantage'])
->viaTable('product_has_advantage', ['product' => 'id'])
->andWhere(['important' => 1])
->orderBy(['sort' => SORT_DESC]);
}
Using viaTable methods with relations will cause two separate queries, but if you don't need link() method you can use innerJoin in the following way to sort by product_has_advantage table:
public function getAdvantages()
{
$query = AdvantageModel::find();
$query->multiple = true;
$query->innerJoin('product_has_advantage','product_has_advantage.advantage = advantage.id');
$query->andWhere(['product_has_advantage.product' => $this->id, 'product_has_advantage.important' => 1]);
$query->orderBy(['product_has_advantage.sort' => SORT_DESC]);
return $query;
}
Note than $query->multiple = true allows you to use this method as Yii2 hasMany relation.
Just for reference https://github.com/yiisoft/yii2/issues/10174
It's near impossible to ORDER BY viaTable() columns.
For Yii 2.0.7 it returns set of ID's from viaTable() query,
and final/top query IN() clause ignores the order.
For who comes here after a while and don't like above solutions, I got it working by joining back to the via table after the filter via table.
Example for above code:
public function getAdvantages()
{
return $this->hasMany(AdvantageModel::className(), ['id' => 'advantage'])
->viaTable('product_has_advantage', ['product' => 'id'])
->innerJoin('product_has_advantage','XXX')
->orderBy('product_has_advantage.YYY'=> SORT_ASC);
}
Take care about changing XXX with the right join path and YYY with the right sort column.
First you need to create a model named ProductHasAdv for junction table (product_has_adv) using CRUD.
Then create relation in product model and sort it:
public function getAdvRels()
{
return $this->hasMany(ProductHasAdv::className(), ['product' => 'id'])->
orderBy(['sort' => SORT_ASC]);;
}
Then create second relationship like this:
public function getAdvantages()
{
$adv_ids = [];
foreach ($this->advRels as $adv_rel)
$adv_ids[] = $adv_rel->advantage;
return $this->hasMany(Advantage::className(), ['id' => 'advantage'])->viaTable('product_has_adv', ['product' => 'id'])->orderBy([new Expression('FIELD (id, ' . implode(',', $adv_ids) . ')')]);
}
This will sort final result using order by FIELD technique.
Don't forget to add:
use yii\db\Expression;
line to head.
I`ve managed this some how... but it needs additional work after.
The point is that you have to query many-to-many relation first from source model and after that inside that closure you should query your target model.
$query = Product::find();
$query->joinWith([
'product_has_adv' => function ($query)
{
$query->alias('pha');
$query->orderBy('pha.sort ASC');
$query->joinWith(['advantage ' => function ($query){
$query->select([
'a.id',
'a.text',
]);
$query->alias('a');
}]);
},
]);
Then you just have to prettify the sorted result to your needs.
The result for each row would look like
"product_has_adv": [
{
"product": "875",
"advantage": "true",
"sort": "0",
"important": "1",
"advantage ": {
"id": "875",
"text": "Some text..",
}
},
As explained by #arogachev, the viaTable uses two separate queries, which renders any intermediate orderBy obsolete
You could replace the viaTable with an innerJoin as follows, in a similar solution to #MartinM
public function getAdvantages()
{
return $this->hasMany(AdvantageModel::class, ['pha.product' => 'id'])
->innerJoin('product_has_advantage pha', 'pha.advantage = advantage.id')
->andWhere(['pha.important' => 1])
->orderBy(['pha.sort' => SORT_ASC]);
}
By adjusting the result of hasMany, you are adjusting the query for the target class - AdvantageModel::find(); product_has_advantage can be joined via the advantage identity
The second parameter of hasMany, link, can be viewed as [ query.column => $this->attribute ], which you can now support via the joined product_has_advantage and its product identity
Note, when using viaTable, the link parameter can be viewed as if the intermediate query is complete and we are starting from there; [ query.column => viaTable.column ]
hence ['id', 'advantage'] in your question
public function getAdvantages()
{
return $this
->hasMany(AdvantageModel::className(), ['id' => 'advantage'])
->viaTable('product_has_advantage', ['product' => 'id'])
->andWhere(['important' => 1])
->orderBy(['sort' => SORT_DESC]);
}