Building Laravel query adding where statement by relationship's field - php

I am not able to construct simple Laravel query.
I have translation with categories (translation.category_id is foreign key to category.id). Moroever category also has property is_technical.
What I need is:
- get all translations where translation's category.is_technical = 1.
Currently I am haveing this query:
$match = ['lang1_code' => $langfrom, 'lang2_code' => $langto];
$translation = Translation::where($match)->orderByRaw("RAND()")->take(4)->get();
But this query doesn't join category (I have relationship in my db and also in my models). Thus how to join category and set where is_Technical = 1?
I believe this is basic question, but I am new to Laravel and I cannot find answer in documentation.

you need whereHas. See Laravel Document for more info
http://laravel.com/docs/5.1/eloquent-relationships
Here is an example, correct your model name.
//Translation.php
public function category() {
return $this->belongsTo('Category', 'category_id');
}
// query
$translation = Translation::whereHas('category', function($q) {
$q->where('is_technical', '=', 1);
})->where($match)->orderByRaw("RAND()")->take(4)->get();

Related

How to check relation data of a collection before sending it to view

I have a Controller method like this:
public function awaiting()
{
$producers = Producer::where('producer_process',4)->get();
$producers_list = [];
foreach($producers as $producer){
if($producer->brand->brand_rejected == 0){
array_push($producers_list, $producer);
}
}
return view('admin.brands.awaiting', compact('producers_list'));
}
So basically there's One To One relationship between Producer model & Brand model.
In order to get the collection of brands table records that has producer_process of 4 and ALSO the brand_rejected field of related brands table record must be set to 0, I added an array_push and check the condition.
Now this works fine and properly shows the correct data but I wanted to know, what is the shorthand method of doing this with Eloquent relationships?
I mean is there any concise and useful method written in Eloquent relationships that can do this without using array_push or another foreach loop?
You can use whereHas to constrain the result set based on the existence of a relationship. Here we are saying we only want producers that have the field 'produce_process' set to 4 and have a brand with a field of 'brand_rejected' set to 0:
$producers = Producer::where('producer_process', 4)
->whereHas('brand', function ($q) { $q->where('brand_rejected', 0); })
->get();
If you want these producers to have their brand relationship loaded to use you should eager load that. Before the get call you can tell it to load the relationship:
$producers = Producer::where(...)->whereHas(...)->with('brand')->get();
Laravel 5.8 Docs - Eloquent - Relationships - Querying Relationship Existence whereHas
Laravel 5.8 Docs - Eloquent - Relationships - Eager Loading with
You can try this:
public function awaiting()
{
$producers = Producer::where('producer_process',4)
->with('brand', function($q) {
$q->where('brand_rejected', 0);
})->get();
// dd($producers);
dd($producers->pluck(brand));
Sure you can use the method with() also with the where() clause to can apply some conditions to the relationship
Example
$yourQuery->with('brand', function($query){
$query->where('brand_rejected', 0);
});
check this for more info
https://laravel.com/docs/9.x/eloquent-relationships#constraining-eager-loads
I hope it's helpful

Model return empty array when use select in model (Laravel)

I have a hasOne(Many) relation function like this:
return $this->hasOne('App\Models\ProductTranslation','product_id','id')->where('language_id', $language_id['id']);
Also, I tried to use
return $this->hasOne('App\Models\ProductTranslation','product_id','id')->select('product_translations.name')->where('language_id', '1');
In Controller use this
$value=Product::with('translation:name')->find(1);
I try to receive only one specific column from the table, but it returned an empty array. In debug bar I see the query when I use it in PHPMyAdmin it return only one column as I want.
Is this possible?
P.S (use Laravel 5.8.34)
Updating
I choose 'Entity Layers for Translated Fields and Non-Translated Fields' approach for translation in the project and have a database like this Database picture
If you want to get only that language_id type of translation then you might do like this.
$language_id = 1;
$products = Product::with(array('translation'=>function($query) use($language_id){
$query->where('language_id',$language_id);
}))->get();
and if you want to select name then like this
make sure you've to select id,name id is a must.
$language_id = 1;
$products = Product::with(array('translation'=>function($query) use($language_id){
$query->where('language_id',$language_id)->select('id','name');
}))->get();
Remove where conditions from models.
As per your DB structure in Language it's belongsToMany with role
Languages.php model
public function role(){
returh $this->belongsToMany('App\Models\Role','role_translations','language_id','role_id')
}
$language_id = 1;
$products = Product::with(array('translation.role'=>function($query) use($language_id){
$query->where('role_translations.language_id',$language_id)->select('languages.id','languages.name');
}))->get();

Laravel Eloquent Retrieving only certain rows basing on relation field

I have a number of models and I have the relationships set up correctly I believe.
I want to retrieve all of the models from a table if and only if the active column of a foreign key table is true.
I have tried a number of variations but to no avail.
Below is the set up.
The table is question is called device, which points to a table called dep.
The DEVICE model has the following relationship to DEP
public function dep() {
return $this->belongsTo('App\Dep');
}
The DEP model has a column called active, which is set to true or false.
What eloquent command should I use to return all Devices where the active field of the DEP table is true?
At the moment I am having to get all the DEP models and then only get the devices if the active field is set to true but I am sure there must be a more elegant approach.
Thanks.
If you want to limit the results based on the existence of a field in a related model, you want to take a look at the whereHas method.
$devices = Device::whereHas('dep', function($query) {
$query->where('active', '=', true); // Replace true with 1 if you aren't using casts
})->get();
This will get all devices that have a dep with the active field of true. This will not fetch dep along with the Device though. If you want that, then just use whereHas with eager loading as you normally would do like this:
$devices = Device::with('dep')->whereHas('dep', function($query) {
$query->where('active', '=', true);
})->get();
You can simply add a filter to your relationship:
public function dep() {
return $this->belongsTo('App\Dep')->where('active', true);
}
Or, if you prefer to keep the dep relation unaltered, you can consider to add another relation to retrieve only the active models:
public function depActive() {
return $this->belongsTo('App\Dep')->where('active', true);
}
This will return all the DEP models related to a Device, having active = 1.
You can use the relation as follow:
$depActives = $device->depActive;
or
$depActives = $device->depActive()->get();
boh of them will do the same

Laravel get all the models with relations

I can't find how to get all of the elements with relations to another element using Eloquent in Laravel.
I have Substances and Contents and want all the contents related to the substances with name 'subst'.
My relation Substance - Content:
public function relation ()
{
return $this->belongsToMany('Substance', 'contents_substances', 'id_contents', 'id_substances');
}
public function relation ()
{
return $this->belongsToMany('Content', 'contents_substances', 'id_contents', 'id_substances');
}
I know I can get all the contents related to one substance:
$content = Substance::find(1)->relation()->get();
but is possible to get all the contents related to a group of substances?
something like:
$sub = Substance::where('name', '=', 'subst'); // get all the substances with that name
$contents =$sub->relation()->get(); // ???
Any help is appreciated!
The easiest is to start the query from the other angle. In this case from Content. whereHas will then add a constraint based on the substances relation:
$contents = Content::whereHas('substances', function($q){
$q->where('name', 'subst');
})->get();

How to order by pivot table data in Laravel's Eloquent ORM

In my Database, I have:
tops Table
posts Table
tops_has_posts Table.
When I retrieve a top on my tops table I also retrieve the posts in relation with the top.
But what if I want to retrieve these posts in a certain order ?
So I add a range field in my pivot table tops_has_posts and I my trying to order by the result using Eloquent but it doesn't work.
I try this :
$top->articles()->whereHas('articles', function($q) {
$q->orderBy('range', 'ASC');
})->get()->toArray();
And this :
$top->articles()->orderBy('range', 'ASC')->get()->toArray();
Both were desperate attempts.
Thank you in advance.
There are 2 ways - one with specifying the table.field, other using Eloquent alias pivot_field if you use withPivot('field'):
// if you use withPivot
public function articles()
{
return $this->belongsToMany('Article', 'tops_has_posts')->withPivot('range');
}
// then: (with not whereHas)
$top = Top::with(['articles' => function ($q) {
$q->orderBy('pivot_range', 'asc');
}])->first(); // or get() or whatever
This will work, because Eloquent aliases all fields provided in withPivot as pivot_field_name.
Now, generic solution:
$top = Top::with(['articles' => function ($q) {
$q->orderBy('tops_has_posts.range', 'asc');
}])->first(); // or get() or whatever
// or:
$top = Top::first();
$articles = $top->articles()->orderBy('tops_has_posts.range', 'asc')->get();
This will order the related query.
Note: Don't make your life hard with naming things this way. posts are not necessarily articles, I would use either one or the other name, unless there is really need for this.
For Laravel 8.17.2+ you can use ::orderByPivot().
https://github.com/laravel/framework/releases/tag/v8.17.2
In Laravel 5.6+ (not sure about older versions) it's convenient to use this:
public function articles()
{
return $this->belongsToMany('Article', 'tops_has_posts')->withPivot('range')->orderBy('tops_has_posts.range');
}
In this case, whenever you will call articles, they will be sorted automaticaly by range property.
In Laravel 5.4 I have the following relation that works fine in Set model which belongsToMany of Job model:
public function jobs()
{
return $this->belongsToMany(Job::class, 'eqtype_jobs')
->withPivot(['created_at','updated_at','id'])
->orderBy('pivot_created_at','desc');
}
The above relation returns all jobs that the specified Set has been joined ordered by the pivot table's (eqtype_jobs) field created_at DESC.
The SQL printout of $set->jobs()->paginate(20) Looks like the following:
select
`jobs`.*, `eqtype_jobs`.`set_id` as `pivot_set_id`,
`eqtype_jobs`.`job_id` as `pivot_job_id`,
`eqtype_jobs`.`created_at` as `pivot_created_at`,
`eqtype_jobs`.`updated_at` as `pivot_updated_at`,
`eqtype_jobs`.`id` as `pivot_id`
from `jobs`
inner join `eqtype_jobs` on `jobs`.`id` = `eqtype_jobs`.`job_id`
where `eqtype_jobs`.`set_id` = 56
order by `pivot_created_at` desc
limit 20
offset 0
in your blade try this:
$top->articles()->orderBy('pivot_range','asc')->get();
If you print out the SQL query of belongsToMany relationship, you will find that the column names of pivot tables are using the pivot_ prefix as a new alias.
For example, created_at, updated_at in pivot table have got pivot_created_at, pivot_updated_at aliases. So the orderBy method should use these aliases instead.
Here is an example of how you can do that.
class User {
...
public function posts(): BelongsToMany {
return $this->belongsToMany(
Post::class,
'post_user',
'user_id',
'post_id')
->withTimestamps()
->latest('pivot_created_at');
}
...
}
You can use orderBy instead of using latest method if you prefer. In the above example, post_user is pivot table, and you can see that the column name for ordering is now pivot_created_at or pivot_updated_at.
you can use this:
public function keywords() {
return $this->morphToMany(\App\Models\Keyword::class, "keywordable")->withPivot('order');
}
public function getKeywordOrderAttribute() {
return $this->keywords()->first()->pivot->order;
}
and append keyword attribiute to model after geting and use sortby
$courses->get()->append('keyword_order')->sortBy('keyword_order');

Categories