How to Query Multiple Pivot Relationship in Laravel Lighthouse - laravel

Hi, there I'm fairly new to laravel-lighthouse. I've read the documentation but unfortunately, there is no detail about this issue. My question is How can I query more than one pivot table. I have an Item table that belongs to many other Models.
The code is given below.
class Item extends Model
{
public function provisions(): BelongsToMany
{
return $this->belongsToMany(Pwmp::class,
'project_wise_material_provision_items', 'item_id', 'provision_id')
->withPivot('qty', 'unit_cost')
->withTimestamps()
->as('provision_items');
}
public function procurements(): BelongsToMany
{
return $this->belongsToMany(Pwpp::class,
'project_wise_procurement_plan_items', 'item', 'procurement')
->withTimestamps()
->as('procurement_items')
->withPivot('qty', 'unit_cost');
}
public function log_sheets(): BelongsToMany
{
return $this->belongsToMany(Braols::class,
'bid_receiving_and_opening_log_sheet_items', 'item', 'braols')
->withTimestamps()
->as('log_sheet_items')
->withPivot('qty', 'unit_cost', 'tax');
}
public function workshop(): BelongsToMany
{
return $this->belongsToMany(TransformerWorkshop::class,
'transformer_workshop_items', 'item', 'workshop')
->as('workshop_items')
->withTimestamps()
->withPivot('qty', 'flow', 'type');
}
public function sub_office(): BelongsToMany
{
return $this->belongsToMany(MaterialReceiptInSubOffice::class,
'material_receipt_in_sub_office_items', 'item', 'receipt')
->as('sub_office')
->withTimestamps()
->withPivot('id','qty', 'type');
}
Here is my GraphQL schema
type Item{
id: ID!
workshop_items: [TransformerWorkShopItemsPivot]
provision_items: [ProjectWiseMaterialProvisionItem]
sub_office: MaterialReceiptInSubOfficeItemsPivot
}
type MaterialReceiptInSubOfficeItemsPivot{
qty: Int
type: Int
id: ID
}
type ProjectWiseMaterialProvisionItem {
qty: String
unit_cost: String
}
type TransformerWorkShopItemsPivot{
qty: Int
type: Int
flow: Int
}
Now when i query the api
```graphql
material_receipt_in_sub_office_store(id: 4){
id
items{
id
provision_items{
unit_cost
qty
}
sub_office{
type
id
qty
}
}
}
}
```
it returns the following result
{
"data": {
"material_receipt_in_sub_office_store": {
"id": "4",
"items": [
{
"id": "431",
"name": "Drill Machine",
"image": "crane2.jpg",
"category_id": {
"id": "993"
},
"provision_items": null,
"sub_office": {
"type": null,
"id": null,
"qty": null,
"__typename": "MaterialReceiptInSubOfficeItemsPivot"
}
},
I am unable to fetch the multiple pivot table data.
however, if I query pivot it returns the data of only one pivot.

First, it looks like you don't use Laravel model's relationships into your type definition.
By specifying a #belongsToMany / #hasMany directive (see more here) you'll be able to optimize your queries.
type Item{
id: ID!
workshop_items: [TransformerWorkShopItemsPivot] #hasMany(relation: "workshop")
provision_items: [ProjectWiseMaterialProvisionItem] #hasMany(relation: "provisions")
sub_office: MaterialReceiptInSubOfficeItemsPivot #hasMany
}
I'm just not sure about the fact you give table aliases in your relation definition..
Then, by doing the query, you can fetch as much pivot relations as you want. Never got relation problems by doing this.

Related

Eloquent querying relation based on main query

I have 4 tables:
Users with column id
Teams with column id
Roles with column id
Pivot teams_users with columns team_id user_id role_id
Goal: get teams, each team has members. Each member has a role in that team.
My approach:
Fetch teams, then fetch members and roles of that team. Loop the collection, add the role to the correct member, delete the role after.
$teams->each(function($team, $teamKey) use ($teams) {
if(empty($team->members) === false && empty($team->roles) === false) {
$team->members->each(function($member, $memberKey) use ($team) {
$team->roles->each(function($role) use ($member) {
if($member->id === $role->pivot->user_id) {
$member->role = $role;
}
});
});
}
});
My goal:
I want to do it using Eloquent and without looping the collection.
My Team model has two relations:
public function members() {
return $this->belongsToMany(User::class, "teams_users", "team_id", "user_id")->withPivot("role_id")->withTimestamps();
}
public function roles() {
return $this->belongsToMany(Role::class, "teams_users", "team_id", "role_id")->withPivot("user_id")->withTimestamps();
}
expected output should be like this:
{
teams: [
{
id: 1,
title: "Awesome team",
members: [
{
id: 1
name: "Test",
role: {
id: 1,
title: "Admin"
}
}
]
}
]
}
Your help is much appreciated.

Laravel graphql Upsert Directive not working as expected

Please Correct me if I am wrong.
I have two models "Users" and "Profiles"
Scenario 1:
In the "Profiles" model I have defined "user_id" as primary key and foreign key to "users.id"
class Profile extends Model
{
/**
* primary key
*/
protected $primaryKey = 'user_id';
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = [
'user_id', 'country'
];
at schema.graphql
type Mutation {
upsertProfile(user_id: ID, country: String): Profile #upsert
}
type Profile {
user_id: ID!
country: String
user: User #belongsTo
}
Let's suppose there is an id 28 in users table. When I try to run mutation:
mutation {
upsertProfile(user_id: 28, country: "India") {
country
}
}
It works fine and updates the country, but if there is no user_id 28 exists, as per definition it should create one.
As it defines the user_id column is not an auto-increment column
I did one more test
Scenario 2:
I removed 'user_id' as the primary key and added the id column as the primary key and auto-increment.
Let suppose id 1 is there, then after running the mutation:
mutation {
upsertProfile(id: 1, country: "India") {
country
}
}
I got the expected result.
But when I try to run mutation
mutation {
upsertProfile(user_id: 28, country: "India") {
country
}
}
I am getting duplicates of result with new id everytime(auto-increment) whenever I ran this mutation.
My question,
How to use upsert if user_id exists then update the row, else create a row.
As laravel createOrUpdate function prototype contains checking on multiple columns to get a row updated, is there any way to do the same on upsert directive.
BTW, at scenario 1 I debugged query and found that insert query is running but I am getting an exception at the grpahql-playground result
{
"errors": [
{
"debugMessage": "No query results for model [App\\Models\\Profile] 0",
"message": "Internal server error",
"extensions": {
"category": "internal"
},
"locations": [
{
"line": 2,
"column": 3
}
],
"path": [
"upsertProfile"
],
....
]
Make sure the ID attribute is fillable/unguarded.
In my case it was a timestamp issue. I put this line of code in my model and problem was solved
public $timestamps = false;

Multiple Relationship Laravel - Object in Object

I want use Laravel Eloquent to do relationship, but I have a problem accessing a specific filtered object in the relationship.
My objects:
courses:
id - integer
name - string
contents:
id - integer
name - string
course_contents:
id - integer
course_id - integer
content_id - integer
I want get the contents by the course. Until now I can only filter the course_contents to filter contents
My controller:
Course::hasContents()->find($id);
Course Model
public function contents()
{
return $this->hasMany('App\CourseContent');
}
public function scopeHasContents($query)
{
$query->with(['contents' => function($contentQuery) {
$contentQuery->has('content')->with('content');
}]);
}
CourseContents Model:
public function content()
{
return $this->hasOne('App\Content', 'id');
}
My json return ( Course Find ) :
{
"id":1,
"name":"Course Example 1",
"contents":[
{
"id":1,
"course_id":1,
"content_id":1,
"deleted_at":null,
"created_at":"2019-07-16 17:31:05",
"updated_at":"2019-07-16 17:31:05",
"content":{
"id":1,
"name":"Content Example 1",
"deleted_at":null,
"created_at":"2019-07-16 17:31:05",
"updated_at":"2019-07-16 17:31:05"
}
},
{
"id":2,
"course_id":1,
"content_id":2,
"deleted_at":null,
"created_at":"2019-07-16 17:31:05",
"updated_at":"2019-07-16 17:31:05",
"content":{
"id":2,
"name":"Content Example 2",
"deleted_at":null,
"created_at":"2019-07-16 17:31:05",
"updated_at":"2019-07-16 17:31:05"
}
},{ ... }
],
}
What I need:
{
"id":1,
"name":"Course Example 1",
"contents":[
{
"id":1,
"name":"Content Example 1",
"deleted_at":null,
"created_at":"2019-07-16 17:31:05",
"updated_at":"2019-07-16 17:31:05"
},
{
"id":2,
"name":"Content Example 2",
"deleted_at":null,
"created_at":"2019-07-16 17:31:05",
"updated_at":"2019-07-16 17:31:05"
},{ ... }
],
}
First, you need to adjust the relationships a bit. You've many to many relationships so the models should look like:
Course.php
public function contents()
{
return $this->belongsToMany(Content::class, 'course_contents');
}
Content.php
protected $hidden = ['pivot'];
public function courses()
{
return $this->belongsToMany(Course::class, 'course_contents');
}
You can retrieve contents data as given below:
for instance: you want to get all contents for a course 1
Content::whereHas('courses', function($query) {
$query->where('courses.id', 1);
})->get();
// You need to pass course id dynamically but for demonstration, I hard coded it.
This will give you the following result:
array:1 [
0 => array:2 [
"id" => 1
"name" => "Content 1"
]
]
Use the belongsToMany relationship:
In your Course model:
public function contents()
{
return $this->belongsToMany(Contents::class, 'course_contents');
}
Then, use $course->contents;
This function returns all content models of the course.
Hope it helps.
The many-to-many relationship is defined by returning a belongsToMany relationship in the relationship method contents in the Course model. As stated in the Laravel many-to-many documentation.
To retrieve only the Content items in the many to many relationship and not the pivot columns, you should change the relationship instance from App\CourseContent to App\Content.
in content model
public function course()
{
return $this->hasMany('App\Course');
}
in course model
public function content()
{
return $this->hasMany('App\Content');
}
you can do
Course::with('content')->find($id)
or
Content::whereHas('course',function($q)use($id){
$q->where('id',$id)
})->get();

Constraining a nested 3rd level relationship

I'm building an api using eager loading so i can simply return the user model with its deep relations and it automatically be converted as json. Here's the set up.
users
id
..
clients
id
..
user_clients
id
user_id
client_id
..
campaigns
id
..
client_campaigns
id
client_id
campaign_id
..
campaign_activities
id
campaign_id
..
client_campaign_activity_templates
id
campaign_activity_id
client_id *(templates are unique per client)*
..
I've setup the models' relationships.
User
public function clients() {
return $this->belongsToMany('App\Client','user_clients');
}
Client
public function campaigns() {
return $this->belongsToMany('App\Campaign','client_campaigns');
}
Campaign
public function activities() {
return $this->hasMany('App\CampaignActivity');
}
CampaignActivity
public function templates() {
return $this->hasMany('App\ClientCampaignActivityTemplate')
}
I have a simple api endpoint to provide a JSON of a User object including its deep relations using eager loading.
public function getLoggedInUser(Request $request) {
return \App\User::with('clients.campaigns.activities.templates')->find($request->user()->id);
}
Testing this using postman, I can get the user including its deep relations.
{
"user": {
"id": 1,
"name": "user1",
"clients": [
{
"id": 1,
"name": "client1",
"campaigns": [
{
"id": 1,
"name": "campaign1",
"activities": [
{
"id": 1,
"name": "activity1",
"templates": [
{
"id": 1,
"name": "template1 for client1",
"client_id": 1,
"body": "this is a template.",
}, {
"id": 2,
"name": "template1 for client2",
"client_id": 2,
"body": "This is a template for client2"
}
]
}, {
"id": 2,
"name": "activity2",
"templates": []
}, {
"id": 3,
"name": "activity3",
"templates": []
}
]
}
]
}
]
}
}
However, on the user->clients->campaigns->activities->templates level, it will list all the templates for that activity. I know based on the code of the relationships of the models above that it's supposed to behave like that.
So the question is How would you filter the templates to filter for both campaign_activity_id and client_id?
I've been experimenting on how to filter the templates so it will only list templates for that activity AND for that client as well. I have a working solution but it's N+1, I'd prefer eloquent approach if possible. I've been scouring with other questions, answers and comments for a closely similar problem, but I had no luck, hence I'm posting this one and seek for your thoughts. Thank you
I think what you need are eager loading constraints.
public function getLoggedInUser(Request $request) {
return \App\User::with('clients.campaigns.activities.templates',
function($query) use($request) {
$client_ids = Client::whereHas('users', function($q) use($request){
$q->where('id', $request->user()->id);
})->pluck('id');
$query->whereIn('templates.client_id', $client_ids);
})->find($request->user()->id);
}
Not tested but it should only require one additional query.
What I am doing is: define a constraint for your eager loading, namely only show those templates that have a client_id that is in the list (pluck) of Client IDs with a relation to the User.
Try using closures to filter through related models:
$users = App\User::with([
'clients' => function ($query) {
$query->where('id', $id);
},
'clients.campaigns' => function ($query) {
$query->where('id', $id);
}
])->get();
Here's my working solution, but I'm still interested if you guys have a better approach of doing this.
On the CampaignActivity model, I added a public property client_id and modified the relationship code to
CampaignActivity
public $client_id = 0
public function templates() {
return $this->hasMany('App\ClientCampaignActivityTemplate')->where('client_id', $this->client_id);
}
and on my controller, limit the eager loading to activities only (actually, there are more sqls executed using eager loading[9] in this case vs just iterating[7], and also eager loading doesn't make sense anymore because we're iterating lol)
public function getLoggedInUser(Request $request) {
foreach ($user->clients as $client)
foreach( $client->campaigns as $campaign)
foreach ($campaign->activities as $activity) {
$activity->client_id = $client->id;
$activity->templates; //to load the values
}
return $user;
}

How to return a collection of models with selected columns and relation eager loaded in Laravel 5.2

Problem details:
I have three models
a Directorate with id and name fields,
an Employee with id and name fields and
a Telephone with id, tel, employee_id, directorate_id, description and type fields. The employee_id may be nullable, that is there are telephones stored in database with employee_id = null
The models are related as follows:
an employee may have many telephones
a directorate, may have many telephones
class Directorate extends Model
{
public function telephones()
{
return $this->hasMany(Telephone::class);
}
public function employees()
{
return $this->hasMany(Employee::class);
}
}
class Employee extends Model
{
public function telephones()
{
return $this->hasMany(Telephone::class);
}
public function directorate()
{
return $this->belongTo(Directorate::class);
}
}
class Telephone extends Model
{
public function employee()
{
return $this->belongsTo(Employee::class);
}
public function directorate()
{
return $this->belongsTo(Directorate::class);
}
}
Question:
I want to fetch a Collection of all the Telephone models that belong to a specific Directorate, that have employee_id = null and also having their directorate relation eager loaded. In addition, from that resulting collection of Telephone models, I need only some of the models' fields, that is id, tel and description
Tries
What I tried so far was the following:
I created a query scope in the Telephone model:
public function scopeHaveNoEmployeeId($query)
{
return $query->where('telephones.employee_id', '=', null);
}
In my controller
$myTelephones = Telephone::with('directorate')
->haveNoEmployeeId()
->where('directorate_id', $directorateId)
->get(['id', 'tel', 'description']);
However what I am receiving are the requested fields of the filtered models without the relation eager loaded, for instance:
[
{
"id": 79,
"tel": "0648136867",
"directorate": null
},
{
"id": 380,
"tel": "0223796011",
"directorate": null
}
]
I tried also to lazy eager load the relation afterwards but with no luck.
Finally I noticed that if I request all the Telephone models fields, the relation will eager load as I request. For example:
$myTelephones = Telephone::with('directorate')
->haveNoEmployeeId()
->where('directorate_id', $directorateId)
->get();
Then as a result:
[
{
"id": 79,
"tel": "0648136867",
"directorate": {
"id": 23
"name": "Some name"
}
},
{
"id": 380,
"tel": "0223796011",
"directorate": {
"id": 23
"name": "Some name"
}
}
]
Your telephone does not have any relationship with the directorate model.
put this in your Telephone model.
public function directorate()
{
return $this->belongsTo(Directorate::class);
}
Actually, after digging into Laravel's details for a while, I noticed that my initial question was out of context and somewhat silly. I was first eager loaded a relationship and then fatuously I filtered out the relationship by not including it in get() parameters. I had just to do the following:
$myTelephones = Telephone::with('directorate')
->haveNoEmployeeId()
->where('directorate_id', $directorateId)
->get(['id', 'tel', 'description', 'directorate']);

Resources