Laravel relationship conflicts in union - php

I have following model:
1- User model
/**
* Define user and functional area relationship
*/
public function functionalAreas()
{
return $this->belongsToMany('App\FunctionalArea', 'user_functional_areas', 'user_id', 'functional_area_id')->withPivot('id', 'is_primary')->withTimestamps();
}
and Business model:
/**
* Define business and user functional area relationship
*/
public function functionalAreas()
{
return $this->belongsToMany('App\FunctionalArea', 'business_functional_areas', 'business_id', 'functional_area_id')->withTimestamps();
}
Now I should take all businesses and users and show them in a single list, for this I'm using from union, following is my query:
public function usersAndOrganizations()
{
$users = $this->users();
$organizations = $this->organizations();
$invitees = $users->union($organizations)->paginate(10);
return response()->json($invitees);
}
private function users()
{
$users = User::byState($approved = true, 'is_approved')
->search()->select([
'id',
DB::raw("CONCAT(first_name, ' ', last_name) AS name"),
'about',
'address',
'slug',
'average_reviews',
DB::raw("'freelancer' AS type")
]);
$users = $users->with([
"functionalAreas" => function ($q) {
$q->select([
'functional_areas.id',
DB::raw("functional_areas.name_en AS name"),
]);
}
]);
return $users;
}
private function organizations()
{
$businesses = Business::where('owner_id', '!=', auth()->user()->id)->verified()
->active()->search()
->select([
'id',
'name',
'about',
'address',
'slug',
'average_reviews',
DB::raw("'business' AS type")
]);
$businesses = $businesses
->with([
"functionalAreas" => function ($q) {
$q->select([
'functional_areas.id',
DB::raw("functional_areas.name_en AS name"),
]);
}
]);
return $businesses;
}
But above query not return the business functional area, its output query use from user relationship instead of business, that with section generate twice the following query:
select
`functional_areas`.`id`,
functional_areas.name_en AS name,
`user_functional_areas`.`user_id` as `pivot_user_id`,
`user_functional_areas`.`functional_area_id` as `pivot_functional_area_id`,
`user_functional_areas`.`id` as `pivot_id`,
`user_functional_areas`.`is_primary` as `pivot_is_primary`,
`user_functional_areas`.`created_at` as `pivot_created_at`,
`user_functional_areas`.`updated_at` as `pivot_updated_at`
from `functional_areas`
inner join `user_functional_areas`
on `functional_areas`.`id` = `user_functional_areas`.`functional_area_id`
where `user_functional_areas`.`user_id` in (2, 6, 7)
But in fact 6, and 7 is business id not user only 2 is user id, one of this queries should use business_functional_areas instead of user_functional_areas.
One more thing found is, all items are inside App\User model in result, its like businesses are also as user object.

The only way for now is to use from map.
public function usersAndOrganizations()
{
$users = $this->users();
$organizations = $this->organizations();
$invitees = $users->union($organizations)->paginate(10);
$invitees = $this->getRelatedData($invitees);
return response()->json($invitees);
}
private function getRelatedData($invitees)
{
$invitees->map(function($object) use($functionalAreaName) {
if($object->type == 'business') {
$relationName = 'businesses';
$relationKey = 'business_id';
$attachableType = Business::MORPHABLE_TYPE;
}
if($object->type == 'freelancer') {
$relationName = 'users';
$relationKey = 'user_id';
$attachableType = User::MORPHABLE_TYPE;
}
$functionalAreas = FunctionalArea::whereHas($relationName, function($q) use ($object, $relationKey){
$q->where($relationKey, $object->id);
})->get([$functionalAreaName.' As name', 'id']);
$object->functional_areas = $functionalAreas->toArray();
});
return $invitees;
}
And remove with from your functions, and call this after you get the paginated result.

In simple words, for now you would not be able to achieve it using Eloquent Eager Loading with Unions. This is not supported yet in Laravel. One of such scenario for which they closed as a Non-Fix issue is Union with Eloquent fail....
Reason: While calling UNION function only the first model(user) is considered main model and model type of result set of other model(Business) passed as argument will be converted to the main one(USER) only and the main model relationship is called on all records(not the desired one).
Due to the above issue only relationship of user model is called on each record of result set. So even for business_id = 1, functional_area of user_id =1 are being fetched.
You can debug more about it from below file & function.
File:
<your_laravel_project>\vendor\laravel\framework\src\Illuminate\Database\Query\Builder.php
Function: get
Alternate Solution
You can fetch the both result set as it is and then merge them after data fetch using php.
public function usersAndOrganizations()
{
$users = $this->users()->get();
$organizations = $this->organizations()->get();
$invitees = $users->toBase()->merge($organizations->toBase())->toArray();
dd($invitees);
}

You can not concat incompatible queries with union.
See Unions.
Your users() method return eloquent builder for User entity.
And organizations() return builder for Business entity.
Thus, it is incorrect to select users and organizations in one query.
The correct query is like that:
SELECT City FROM Customers
UNION
SELECT City FROM Suppliers
ORDER BY City;

Related

laravel orderby nested related model

I have model with name "Date" which has relationship (one-to-one) with "Tour" and "Tour" Model has relationship (many-to-many) with "Type" Model.
I want to order my Date Records Based on "Type" name. Unfortunately I don't have any clue to do it with eloquent.
Date model:
public function Tour()
{
return $this->belongsTo('\App\Tour');
}
Tour model:
public function Date()
{
return $this->hasOne('\App\Date','tour_id');
}
public function Types()
{
return $this->belongsToMany('\App\Type');
}
Type model:
public function Tours()
{
return $this->belongsToMany('\App\Tour');
}
and my controller for output:
public function tourList()
{
$dates = new Date();
$dates = $dates->orderBy('id','asc')
->paginate(6)
->appends([
'sort_price' => request('sort_price'),
'minmax' => request('minmax'),
'type' => request('type')
]);
return view('primary.Tour.list', compact(['dates']));
}
$dateRes = Date::join('type_tour as TT', 'TT.tour_id', '=', 'date.tour_id')
->join('type as T','TT.type_id','=','T.id')
->orderBy('T.name')
->get();
See if it is work for you.
if you want to order the result based on nested relation column, you must use a chain of joins:
$result= Date::join('tours','tours.id','=','dates.tour_id')
->leftJoin('type_tour','type_tour.tour_id','=','tours.id')
->leftJoin('types','types.id','type_tour.type_id')
->orderBy('types.name')->get();
make sure the relation middle table between tours & types table is type_tour
it might be tour_type or something else ...
please note that if you want to order by multiple columns you could add 'orderBy' clause as much as you want:
->orderBy('Types.id', 'DESC')->orderby('Types.name', 'ASC') //... ext
check my answer here:
https://stackoverflow.com/a/61194625/10573560

How OrderBy afterwards HasMany and BelongsTo Relationships

I created a relationship between the "Review, Games and Info" tables, unfortunately, though, the main table is Games, and he orders me all for Games, While I would like to order the ID of "review" table.
Models: Review
public function games()
{
return $this->hasMany('App\Models\Giochi', 'id_gioco', 'id');
}
Models: Giochi
public function review()
{
return $this->belongsTo('App\Models\Review', 'id', 'id_gioco');
}
public function infogiochi()
{
return $this->belongsTo('App\Models\InfoGiochi', 'id', 'id_gioco');
}
Models: InfoGiochi
public function games()
{
return $this->hasMany('App\Models\Giochi', 'id', 'id_gioco');
}
Controller:
$review = Giochi::with('Review','InfoGiochi')->orderBy('id','DESC')->get();
Here is a screenshot of my json:
How do I order content for review table IDs?
You have 2 options. You use a join and order in the sql statement or you order it after retrieving the results in the collection.
Using Join
Giochi::select('giocos.*')
->with('Review','InfoGiochi')
->leftJoin('reviews', 'reviews.id', '=', 'giocos.id_gioco')
->orderBy('reviews.id','DESC')
->get();
Sorting Collection
Giochi::with('Review','InfoGiochi')
->get()
->sortByDesc(function($giochi) {
return $giochi->review->id;
});
This would be the shortest version to sort on the collection:
Giochi::with('review')
->get()
->sortByDesc('review.id');
You can modify your relationship query when you fire it:
Giochi::with([
'Review' => function ($query) { return $query->orderBy('id','DESC'); },
'InfoGiochi'
])->orderBy('id','DESC');
You can try with a raw query or you can use ->orderBy() directly on review function.

Laravel Datatables Sorting Appended Fields

My Model Items is related to Rooms, which is related to Buildings, which is related to Locations.
Short: Items belongsTo Rooms belongsTo Buildings belongsTo Locations
At the index function of the ItemController I want to show a table of Items. I use Laravel Datatables. It works for 'simple' tables, but I ran into the problem of sorting/searching custom/appended fields, because they are not in the table of course.
Basically I want to join the Room, Building and Location Name for each Item in the displaying table.
This is my Items Model:
protected $appends = [
'building_title',
'location_title',
'room_title',
];
public function getLocationTitleAttribute() {
return $this->room->building->location->title;
}
public function getBuildingTitleAttribute() {
return $this->room->building->title;
}
public function getRoomTitleAttribute() {
return $this->room->title;
}
This is my Controller:
public function anyData()
{
return Datatables::of(Item::query())->make(true);
}
What is the best approach to enable sorting/filtering for appended fields?
Thank you for reading.
This is my solution (no appended fields were used):
public function anyData()
{
$items = Item::join('rooms', 'items.room_id', '=', 'rooms.id')
->join('buildings', 'rooms.building_id', '=', 'buildings.id')
->join('locations', 'buildings.location_id', '=', 'locations.id')
->select([
'items.id',
'items.title',
'items.label_number',
'items.fibu_number',
'rooms.title as room_title',
'buildings.title as building_title',
'locations.title as location_title'
]);
return Datatables::of($items)->make(true);
}

Laravel / Eloquent : hasManyThrough WHERE

In the documentation of Eloquent it is said that I can pass the keys of a desired relationship to hasManyThrough.
Lets say I have Models named Country, User, Post. A Country model might have many Posts through a Users model. That said I simply could call:
$this->hasManyThrough('Post', 'User', 'country_id', 'user_id');
This is fine so far! But how can I get these posts only for the user with the id of 3 ?
Can anybody help here?
So here it goes:
models: Country has many User has many Post
This allows us to use hasManyThrough like in your question:
// Country model
public function posts()
{
return $this->hasManyThrough('Post', 'User', 'country_id', 'user_id');
}
You want to get posts of a given user for this relation, so:
$country = Country::first();
$country->load(['posts' => function ($q) {
$q->where('user_id', '=', 3);
}]);
// or
$country->load(['posts' => function ($q) {
$q->has('user', function ($q) {
$q->where('users.id', '=', 3);
});
})
$country->posts; // collection of posts related to user with id 3
BUT it will be easier, more readable and more eloquent if you use this instead:
(since it has nothing to do with country when you are looking for the posts of user with id 3)
// User model
public function posts()
{
return $this->hasMany('Post');
}
// then
$user = User::find(3);
// lazy load
$user->load('posts');
// or use dynamic property
$user->posts; // it will load the posts automatically
// or eager load
$user = User::with('posts')->find(3);
$user->posts; // collection of posts for given user
To sum up: hasManyThrough is a way to get nested relation directly, ie. all the posts for given country, but rather not to search for specific through model.
$user_id = 3;
$country = Country::find($country_id);
$country->posts()->where('users.id', '=', $user_id)->get();
$this->hasManyThrough('Post', 'User', 'country_id', 'user_id')->where(column,x);
What happen here is you get the collection in return you can put any condition you want at the end.

Creating a query in Laravel by using `with` and `where`

I'm wondering it would be possible to add a where condition to a with.
Such as:
Comment::with('Users')->where('allowed', 'Y')->get();
I was trying to find a more simple way to make queries avoiding the whereHas method which looks quite verbose:
$users = Comment::whereHas('users', function($q)
{
$q->where('allowed', 'Y');
})->get();
The raw query I want internally to generate should be like so:
select * from comments, users
where users.id = comments.user_id and
users.allowed = 'Y'
I'm used to work with CakePHP in which this queries look very simple:
$this->Comments->find('all', array('Users.allowed' => 'Y'));
The relationships I have defined are:
//Comments.php
public function Users()
{
return $this->belongsTo('Users');
}
//Users.php
public function Comments(){
return $this->hasMany('Comments');
}
You may try this
$users = User::with(array('comments' => function($q)
{
$q->where('attachment', 1);
}))->get();
Update : Alternatively you may use a where clause in your relationship in your User model
// Relation for comments with attachment value 1
// and if hasMany relation is used
public function commentsWithAttachment()
{
return $this->hasMany('Comment')->where('attachment', 1);
}
// Relation for all comments
// and if hasMany relation is used
public function comments()
{
return $this->hasMany('Comment');
}
So, you can just use
// Comments with attachment value 1
User::with('commentsWithAttachment')->get();
// All comments
User::with('comments')->get();
Update : I think you want all comments with users where attachment is 1, if this what you want then it should be Comment not User
Comment::with('user')->where('attachment', 1)->get();
In this case your relation should be
public function user()
{
return $this->belongsTo('User'); // if model name is User
}
Because one comment belongs to only one user.

Categories