backpack crud filters by pivot table - laravel

PLease, somebody, help me with filters.
I have 3 tables: complaint, frontend_tag, frontendtags_complaints
I heed to filter complaints by frontend_tag
here is my filter code:
$this->crud->addFilter([
'name' => 'frontendtags',
'type' => 'select2',
'label'=> 'FE Tag'
], function() {
return \App\Models\FrontendTags::all()->pluck('name', 'id')->toArray();
}, function($value) {
return $this->crud->query->whereHas('frontendtags', function ($q) use ($value) {
$q->where('tag_id', $value);
});
});
here is my relation code:
public function frontendtags()
{
return $this->belongsToMany('App\Models\FrontendTags', 'frontendtags_complaints', 'complaint_id', 'tag_id');
}
The filter is not working.

Replace
$q->where('tag_id', $value);
With
$q->where('frontend_tag.id', $value);
The where condition is applied on the related table, in this case frontend_tag.

Related

Laravel one of many relationship with argument

coming from this relationship from the docs:
https://laravel.com/docs/9.x/eloquent-relationships#advanced-has-one-of-many-relationships
/**
* Get the current pricing for the product.
*/
public function currentPricing()
{
return $this->hasOne(Price::class)->ofMany([
'published_at' => 'max',
'id' => 'max',
], function ($query) {
$query->where('published_at', '<', now());
});
}
how can I make such an relation with a specific date?
The relation down below will work
/**
* Get pricing for the product of one specific date.
*/
public function priceOfDay(Carbon $date)
{
return $this->hasOne(Price::class)->ofMany([
'published_at' => 'max',
'id' => 'max',
], function ($query) {
$query->where('published_at', '<', $date());
});
}
but how can I use it with Eloquent? How can I pass the date to this:
Product::with('priceOfDay')->get();
update
I now use the one to many relation with a closure
->with(['prices' => function ($query) use ($month) {
$query->where('published_at', '<', $month)
->orderByDesc('published_at')
->orderByDesc('id')
->first();
}])
it works with the little drawback of having a collection instead of an object as relation, but it fills my needs for the moment.
It would be nice if there was something like
->with(['relation', $param])
update 2
since there seems to bo no direct solution here the workarround i came up with:
->first() does not work in the query, you will end up getting all prices, so I finished with an each()
->with(['prices' => function ($query) use ($month) {
$query->where('published_at', '<', $month)
->orderByDesc('published_at')
->orderByDesc('id');
}])
->get()
->each(function ($product) {
$product->price = $product->prices->first()->price;
})

how can we use with method in laravel with where clause?

i need only those products where variants size field is tiny. what i should do ? above quering giving me all products but with empty variants
$result=$categories->with([
'products' => function ($product) {
return $product->with([
'variants' => function ($variants) {
return $variants->where('size','tiny');
}
]);
}
])->get();
Change your code like this.
$result=$categories->with([
'products' => function ($product) {
return $product->whereHas(
'variants', function ($variants) {
return $variants->where('size','tiny');
}
]);
}
])->get();

how to select specific fields from the tables in laravel

This works but it gives all the fields of account table but only few are required.
$data = Loan::select('*')
->with([
'member' => function ($query) {
$query->addSelect('id', 'name');
},
'member.account'
])
->get();
This gives account: null
$data = Loan::select('*')
->with([
'member' => function ($query) {
$query->addSelect('id', 'name');
},
'member.account'=> function ($query) {
$query->addSelect('id', 'account'); // if this line is commented, all the fields are returned but I just need few fields.
}
])
->get();
Member model:
public function account()
{
return $this->hasOne(Account::class);
}
Account model:
public function member()
{
return $this->belongsTo(Member::class);
}
How can I get only the required fields of account table?
You can define the relationship and set it to give you specific columns like this.
on Member model
public function account()
{
return $this->hasOne(Account::class)->select(['id', 'account']);
}
You can try to define required fields in with:
$data = Loan::select('*')
->with([
'member' => function ($query) {
$query->addSelect('id', 'name');
},
'member.account:id,number'
])
->get();
Your are close you just need to select the foreign key column that points to your account model , I guess it would be account_id, So you basically need to select 3 columns from your table which are select(['id', 'name','account_id'])
$data = Loan::with(['member' => function ($query) {
$query->select(['id', 'name','account_id'])
->with(['account'=> function($query){
$query->select(['id','name']);
}]);
}])
->get();
So if you have another nested relation account -> type and you need specific columns from type so you need to select the foreign key column from accounts models as
with(['account'=> function($query){
$query->select(['id','name','type_id'])
->with(['type'=> function($query){
$query->select(['id','name']);
}]);
}]);

How to change result of laravel relationship

I have an one to one relationship in laravel same as following:
public function Category()
{
return $this->belongsTo(Categories::class);
}
and run this eloquent query:
Product::with('category')->first();
this query return:
{
"name": "Green Book",
"category": {
"id": 1,
"name": "Book"
}
}
but I went this data:
{
"name": "Green Book",
"category": "Book"
}
Is it possible to do this without using a loop?
First of all it seems like you have a 1-X relationship which you are mistakenly using as a many to one. Your belongsTo should be hasOne since the your items have one category not the other way around.
You can use the $appends property to append a custom field and make it behave as though it's part of your model:
Rename your relationship and add a mutator and accessor:
public function categoryRelationship()
{
return $this->hasOne(Categories::class);
}
public function getCategoryAttribute() {
return $this->categoryRelationship->name;
}
public function setCategoryAttribute($value) {
$this->categoryRelationship->name = $value;
}
You can also choose to add an event to automatically save your relationship when the model is being saved to ensure it works transparently:
protected static function booted()
{
static::saving(function ($myModel) {
$myModel->categoryRelationship->save();
});
}
}
Finally you add the $appends property to ensure your new attribute is alwasy included in the model as though it's a native one.
protected $appends = [ 'category' ];
// This is so you don't end up also showing the relationship
protected $hidden = [ 'categoryRelationship' ];
You can use leftJoin
return \App\Product::leftJoin('categories', 'products.category_id', '=', 'categories.id')
->select('products.*', 'categories.title as category')->get();
According to Laravel Doc Eager Loading Specific Columns
You may use the following
Product::with('category:id,name')->first();
Or you may do it yourself :
$product = Product::with('category')->first();
$product->category = $product->category->name;
I get same result
so, i tried but didn't get right result.
how to change the result of laravel eloquent relation.
I found one way and used transformer.
we can change the result in transformer.
$result = Event::with(['eventType'])
->where('id', $id)
->where('user_id', auth()->user()->id)
->first();
return $this->trans->transform($result);
this is the result and i used transformer as follow.
public function transform(Event $Event)
{
return [
'id' => $Event->id,
'company_id' => $Event->company_id,
'calendar_id' => $Event->calendar_id,
'case_id' => $Event->case_id,
'user_id' => $Event->user_id,
'title' => $Event->title,
'description' => $Event->description,
'duration' => $Event->duration,
'alert_at' => $Event->alert_at,
'alert_email' => $Event->alert_email,
'email_sent' => $Event->email_sent,
'alert_popup' => $Event->alert_popup,
'popup_triggered' => $Event->popup_triggered,
'created_by' => $Event->created_by,
'completed' => $Event->completed,
'alert_offset' => $Event->alert_offset,
'icon' => $Event->icon,
'color' => $Event->color,
'snoozed' => $Event->snoozed,
'type_id' => $Event->type_id,
'datetime' => $Event->at,
'endtime' => $Event->end_time,
'event_type_name'=>$Event->eventType->name,
];
}
then, we can get right result. Focuse on 'event_type_name'=>$Event->eventType->name,
But i have one problem yet.
now, the result is only 1 row. it is just first().
but if i use get(), i can't use transformer.
of course, i can loop the result using foreach().
but i think it is not right way.
what is better way?
Please answer.

ActiveRecord where and order on via-table

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]);
}

Resources