Let's say I have three databases, player, credit, and photo, linked with foreign keys:
player
id | name | address
credit
id | player_id | credit_status
photo
id | player_id
Say I want to get all players who has credit_status $status, I'd do it like this:
$status = 'bar';
Player::with('photo','credit')->whereHas('credit', function ($q) use ($status) {
$q->where('credit_status', $status)->with('credit_status');
})->paginate(15);
This will lists all players who has credit_status $credit, but it still lists all credits by that player regardless of the status.
The output is something like:
{
id: 1
name: Andrew
address: home
photo: {
id: 2
photo: image1
}
credit: {
[
{
id: 6
credit_status: foo,
id: 2
credit_status: bar
}
]
}
},
{
id: 2
name: Mark
address: home
photo: {
id: 5
photo: image4
}
credit: {
[
{
id: 10
credit_status: foo,
id: 6
credit_status: bar,
id: 8
credit_status: bar
}
]
}
}
I want to filter the credit in with('credit') also.
My desired output:
{
id: 1
name: Andrew
address: home
photo: {
id: 2
photo: image1
}
credit: {
[
{
id: 2
credit_status: bar
}
]
}
},
{
id: 2
name: Mark
address: home
photo: {
id: 5
photo: image4
}
credit: {
[
{
id: 6
credit_status: bar,
id: 8
credit_status: bar
}
]
}
}
You can just do the same filtering on the with (constraining eager loading):
$creditFilter = function ($q) use ($status) {
$q->where('credit_status', $status);
};
Player::with(['photo', 'credit' => $creditFilter])
->whereHas('credit', $creditFilter)
->paginate(15);
You can save that closure and pass it to the with and whereHas so you don't have to type the same identical closure twice.
Laravel 5.6 Docs - Eloquent - Relationships - Eager Loading - Constraining Eager Loads
If you want to filter credits too then you have to also use the condition in the credit. Actually whereHas() and with() work independently they are not dependent on each other.
$status = 'bar';
Player::with(['photo','credit' => function($query) use ($status){
$query->where('credit_status', $status)->with('credit_status');
}])->whereHas('credit', function ($q) use ($status) {
$q->where('credit_status', $status)->with('credit_status');
})->paginate(15);
You only need to make conditional with the 'with' function; not with the 'whereHas' function.
$creditFilter = function ($q) use ($status) {
$q->where('credit_status', $status);
};
Player::with(['photo', 'credit'])
->with(['credit' => $creditFilter])
->paginate(15);
Related
I am trying to get parent and child data and show it in Select2.
Model
// JobTypes model
public function jobs()
{
// one type of job has many jobs
return $this->hasMany('App\Jobs', 'id'); // id refer to jobs.id
}
// Jobs model
public function job_types()
{
// one job only belongs to one type of job
return $this->belongsTo('App\jobTypes');
}
Controller
$keyword = $this->request->get('q');
$categories = \App\JobTypes::with(['jobs' => function ($query) use ($keyword) {
$query
->whereHas('job_types')
->orWhereDoesntHave('job_types')
->where('name', 'LIKE', "%$keyword%");
}])->get();
return $categories;
View
$('#goals').select2({
maximumSelectionLength: 2,
ajax: {
url: '{{ url('showjobs') }}',
processResults: function(data) {
return {
results: data.map(function(item) {
return {
id: item.id,
text: item.name,
//childern
children: data.map(function(child){
return {
id: child.id,
text: child.name
}
})
// end childern
}
})
}
}
} });
But the result is not expected.
So, I want all of child data above coming from 'jobs' table. I've tried many times to solve this problem but still stuck here.
I have 3 models (User, Post, and Comment).
User model:
public function posts() // A user can have many posts
{
return $this->hasMany('App\Post', 'from_id');
}
Post model:
protected $with = [ // I'm using with() here
'from',
'for',
'comments',
];
public function from() // Post owner
{
return $this->belongsTo(User::class, 'from_id');
}
public function for() // To whom this post is addressed
{
return $this->belongsTo(User::class, 'for_id');
}
public function comments() // All comments for this post
{
return $this->morphMany(Comment::class, 'commentable');
}
Post migration:
$table->id();
$table->string('uuid');
$table->foreign('from_id')->references('id')->on('users');
$table->unsignedBigInteger('from_id');
$table->foreign('for_id')->references('id')->on('users');
$table->unsignedBigInteger('for_id')->nullable();
$table->text('body');
$table->timestamps();
$table->softDeletes();
Comment model:
protected $with = [ // I'm also using with() here
'from',
'children',
];
public function from() // Comment owner
{
return $this->belongsTo(User::class, 'from_id', 'id');
}
public function commentable()
{
return $this->morphTo();
}
public function children() // child comments for this comment (?) Not sure how it's working or not
{
return $this->hasMany(Comment::class, 'parent_id');
}
Comment migration:
$table->id();
$table->string('uuid');
$table->unsignedBigInteger('commentable_id');
$table->string('commentable_type');
$table->foreign('from_id')->references('id')->on('users');
$table->unsignedBigInteger('from_id');
$table->foreign('parent_id')->references('id')->on('comments');
$table->unsignedBigInteger('parent_id')->nullable();
$table->text('body');
$table->timestamps();
$table->softDeletes();
Note that I have two dummy users in my Users table.
Then let's say we have two records inside our Posts table:
|------------------------------------------------------|
| id | uuid | from_id | for_id | body | ... |
|------------------------------------------------------|
| 1 | .... | 1 | null | ... | ... |
| 2 | .... | 1 | null | ... | ... |
So let's create a comment for id #1:
php artisan tinker
>> $post = App\Post::first();
>> $post->comments()->create(['from_id' => 2, 'body' => 'Test comment']);
>> App\Comment {#3744
from_id: 2,
body: "Test comment",
commentable_id: 1,
commentable_type: "App\Post",
uuid: "68bc8dbd-9769-44d7-8139-3e4e14d3df4f",
updated_at: "2020-09-01 15:00:38",
created_at: "2020-09-01 15:00:38",
id: 1,
}
Now let's see the first posts with comments (I'm using protected $with in my Post model):
php artisan tinker
>> $post = App\Post::first();
>> App\Post {#4030
id: 1,
uuid: "e9503551-99ac-495f-902e-b505408ab9ef",
from_id: 1,
for_id: null,
body: "Vero vel officia qui et. Veritatis laudantium itaque nisi sint repellendus laborum. Nihil at aliquam alias in.",
created_at: "2020-09-01 14:59:11",
updated_at: "2020-09-01 14:59:11",
deleted_at: null,
comments: Illuminate\Database\Eloquent\Collection {#4039
all: [
App\Comment {#4049
id: 1,
uuid: "68bc8dbd-9769-44d7-8139-3e4e14d3df4f",
commentable_id: 1,
commentable_type: "App\Post",
from_id: 2,
parent_id: null,
body: "Test comment",
created_at: "2020-09-01 15:00:38",
updated_at: "2020-09-01 15:00:38",
deleted_at: null,
from: App\User {#4061
id: 2,
name: "Prof. Runte Jr.
email: "abigail#example.test",
...
},
children: Illuminate\Database\Eloquent\Collection {#4040
all: [],
},
},
],
},
}
We can see the first post now has a comment, alright.
Now I want to create a child comment for the first comment inside the post before:
php artisan tinker
// get the first post, and then get the first comment of it
>> $post = App\Post::first()->comments()->first();
>> App\Comment {#4060
id: 1,
uuid: "68bc8dbd-9769-44d7-8139-3e4e14d3df4f",
commentable_id: 1,
commentable_type: "App\Post",
from_id: 2,
parent_id: null,
body: "Test comment",
created_at: "2020-09-01 15:00:38",
updated_at: "2020-09-01 15:00:38",
deleted_at: null,
from: App\User {#4073
id: 2,
name: "Prof. Runte Jr.
email: "abigail#example.test",
...
},
children: Illuminate\Database\Eloquent\Collection {#4030
all: [],
},
}
// Now we want to create child comment
// Get the first post
>> $post = App\Post::first()
// Get the first comment from it
->comments()->first()
// Get children relationship (IDK if this the right words to put it) and create a child comment
->children()->create(['from_id' => 1, 'body' => 'Testing child comment']);
This is what tinker returns:
Illuminate/Database/QueryException with message 'SQLSTATE[HY000]: General error: 1364 Field 'commentable_id' doesn't have a default value (SQL: insert into comments (from_id, body, parent_id, uuid, updated_at, created_at) values (1, Testing child comment, 1, aa3d624f-3984-438c-adb0-26086459de33, 2020-09-01 15:38:36, 2020-09-01 15:38:36))'
So my question is:
How to make a child comment inside polymorphic relationship (morphMany())? I already set the column for parent_id which belongs to comment id.
As comments() return collection of comments. So, You should use this one like:
$post = App\Post::first();
foreach($post->comments() as $comment){
$comment->children()->create([
'from_id' => 1,
'body' => 'Testing child comment'
]);
}
You can also use save() method as:
$post = App\Post::first();
$comment = new App\Comment([
'from_id' => 1,
'body' => 'Testing child comment'
]);
$child = $post->comments()->first()->save($comment);
And You have to change in your App\Comment method children() with this :
public function children()
{
return $this->morphMany(Comment::class, 'commentable');
}
Hello wonderful people of StackOverflow, I hope you all have a good day ( ˘⌣˘)♡(˘⌣˘ )
I'm new to Laravel and currently learning about eloquent relationships (hasMany)
I'm sorry this post was too long to read, but I wanted to clarify every step I did until I ran into problems, and also sorry if my english is bad (⁄ ⁄•⁄ω⁄•⁄ ⁄)
So here we go
In my User model
public function posts() {
// A user can have many posts
return $this->hasMany('App\Post');
}
In my Post model
public function owner()
{
// A post belongs to a user
return $this->belongsTo('App\User', 'user_id');
}
In my Post Table, I have 3 simple records
|----------------------------------------------------------|
| id | user_id | body | ... | ... |
| 1 | 1 | My first post | ... | ... |
| 2 | 1 | Second post | ... | ... |
| 3 | 1 | Another post | ... | ... |
And then let's say, we want to see the user with id = 1 with all posts they created, so I use the code below:
// AccountController
public function profile($id)
{
$user = App\User::with('posts')->findOrFail($id);
return $user;
}
// Will return data:
{
"id": 1,
"name": "Prof. Angela Runte Jr.",
"email": "mborer#example.org",
...
"posts": [
{
"id": 1,
"user_id": 1,
"body": "My first post",
...
},
{
"id": 1,
"user_id": 1,
"body": "Second post",
...
},
{
"id": 1,
"user_id": 1,
"body": "Another post",
...
}
}
And in Blade view, I can simply get data like below:
// in profile.blade.php
$user->name
$user->...
#foreach($user->posts as $post)
// Show each post data
#endforeach
It works perfectly the way I want, Thanks to Taylor Otwell for creating an amazing framework (´• ω •`) ♡
And then I had an idea in my mind, let's make a feature where a user can post to other users, why not? Let's do it! (✧ω✧)
So in my Post migration, I change the table schema:
From:
// Before
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
$table->unsignBigInteger('user_id');
$table->text('body');
$table->timestamps();
To:
// After
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
$table->unsignedBigInteger('user_id');
// New column: nullable()
$table->foreign('for_id')->references('id')->on('users')->onDelete('cascade');
$table->unsignedBigInteger('for_id')->nullable();
$table->text('body');
$table->timestamps();
As you can see, I put nullable() in column 'for_id'
In words, if for_id is empty then this post belongs only to the user who made the post
Otherwise the post belongs to the user who made the post, as well as the intended user (for_id).
Then after that, I run
// I only use UserSeeder (wanna keep it simple tho)
php artisan migrate:fresh --seed
And now we have 2 users with id = 1 and 2 (Yay!)
|----------------------------------------------------------------------------------------|
| id | name | email | ... | ... |
| 1 | Camden Kulas | lemke.fabian#example.net | ... | ... |
| 2 | Mrs. Roberta Stroman | ybartoletti#example.com | ... | ... |
Let's use tinker for creating dummy data
php artisan tinker
>> $user = App\User::first();
=> App\User {#3910
id: 1,
name: "Camden Kulas",
email: "lemke.fabian#example.net",
...
}
>> $user->posts()->create(['body' => 'New first post']);
=> App\Post {#4120
body: "New first post",
user_id: 1,
updated_at: "2020-08-30 03:42:43",
created_at: "2020-08-30 03:42:43",
id: 1,
}
// we create one for Mrs. Roberta Stroman (user with id #2)
>> $user->posts()->create(['for_id' => 2, 'body' => 'Hello there']);
=> App\Post {#3912
for_id: 2,
body: "Hello there",
user_id: 1,
updated_at: "2020-08-30 03:44:18",
created_at: "2020-08-30 03:44:18",
id: 2,
}
So now we have 2 record in our posts table
|--------------------------------------------------------------------|
| id | user_id | for_id | body | ... |
| 1 | 1 | NULL | New first post | ... |
| 2 | 1 | 2 | Hello there | ... |
We're still in user id #1
>> $user->posts
=> Illuminate\Database\Eloquent\Collection {#4120
all: [
App\Post {#4125
id: 1,
user_id: 1,
for_id: null,
body: "New first post",
...
},
App\Post {#4128
id: 2,
user_id: 1,
for_id: 2,
body: "Hello there",
...
},
],
}
( ̄ω ̄) Ok 'till here, all works fine, let's try with user id #2
In my mind, I want to get user id #2, and with all posts made by id # 2, and all posts from others for id # 2
>> $user = App\User::find(2);
=> App\User {#4121
id: 2
name: "Mrs. Roberta Stroman",
email: "ybartoletti#example.com",
...
}
>> $user->posts
=> Illuminate\Database\Eloquent\Collection {#4067
all: [],
}
Σ(°ロ°) The posts is empty, so why the hell it's empty?
ఠ ͟ಠ Then I try to use with()
>> $user = App\User::with('posts')->find(2);
=> App\User {#4120
id: 2,
name: "Mrs. Roberta Stroman",
email: "ybartoletti#example.com",
...
posts: Illuminate\Database\Eloquent\Collection {#4123
all: [],
},
}
(ꐦ°᷄д°᷅) What?? Why The posts still empty? щ(ºДºщ)
So, after searching and reading for days, still I couldn't figured it out how to solve this (。╯︵╰。)
And then I tried to change in my User model: the posts() function
From:
public function posts()
{
// A post belongs to a user
return $this->hasMany('App\Post');
}
To:
public function posts()
{
// In my logic like:
// Hey I want to fetch all posts that made by this user, **or where all posts that created by others for this user**
// Make sense in human language right?
return $this->hasMany('App\Post', 'user_id')->orWhere('posts.for_id', $this->id);
}
I exited the tinker, then re-enter the tinker again
>> $user = App\User::first();
// return user id #1
>> $user->posts
// works fine, return all posts
(ಠ益ಠ) Now I am facing other problem
>> $user = App\User::with('posts')->first(); // notice that I'm using with()
// tinker return the desired user, and also return ONLY one post
>> App\User {#4123
id: 1,
name: "Camden Kulas",
email: "lemke.fabian#example.net",
....
posts: Illuminate\Database\Eloquent\Collection {#4130
all: [
App\Post {#4128
id: 1,
user_id: 1,
for_id: null,
body: "New first post",
created_at: "2020-08-30 03:42:43",
updated_at: "2020-08-30 03:42:43",
},
// where is the second post?
],
},
}
Then I try to change tinker session with user id #2
>> $user = App\User::find(2);
// return user id #2
>> $user = App\User::with('posts')->find(2);
// return user id #2, but no post (only array all)
So without writing further, the questions I want to ask
[#1] Why user #1 only fetch one post, meanwhile he created 2 posts?
One post without for_id (NULL), and second post with for_id
[#2] How to make user id #2 to fetch all their posts, and also posts that created for them?
Because IMHO posts() function in User model, perfectly make sense for me, but it doesn't work
If there's any unclear explanation, it will edit in time
Thanks in advance
Update
I figured it out that I can simply get all posts by user X and all posts for user X with this way:
$user = App\User::find(X);
$posts = $user->posts()->orWhere('for_id', X)->get();
return [
'user' => $user,
'posts => $posts
];
So now the question comes to my mind:
How to use with() for $posts?
// Like this
$user = App\User::with('allposts')->find(X);
// Return user X and all posts made by X and posted to user X
// So I can simply get data in view like this:
#foreach ($user->allposts as $post)
// Loop all posts by user X and for user X
#endforeach
The GPS data I import can have multiple waypoint places for the same place.
I store this GPS data in the database, but want to treat all duplicates as one.
My Laravel (v6) model Place has id and parent_id (I could change this if necessary). E.g.
id parent_id name
1 null real place
2 1 same as real place
When querying for Place info, I would always like to do this for the real place and the duplicates.
So far I am using a local scope:
public function scopeId($query, $id) {
return $query->where('id', $id)->orWhere('parent_id', $id);
}
This allows me to do e.g. Place::id(1)->with('visits')->get() which gives me e.g.
place 1
- visit A
- visit B
place 2
- visit C
Is there a way to "merge" these Places so that the responses always return just the parent Place?
Like:
place 1
- visit A
- visit B
- visit C
The with('visits') is an example. Would like this functionality to apply to all Place related queries.
Edit: full example
Place::id(1078)->select('id','parent_id')->with('visits:id,place_id')->get()
=> Illuminate\Database\Eloquent\Collection {#3092
all: [
App\Place {#3115
id: 1068,
parent_id: 1078,
visits: Illuminate\Database\Eloquent\Collection {#3109
all: [
App\Timeline {#3087
id: 8022,
place_id: 1068,
},
App\Timeline {#3094
id: 8023,
place_id: 1068,
},
],
},
},
App\Place {#3137
id: 1078,
parent_id: null,
visits: Illuminate\Database\Eloquent\Collection {#3139
all: [
App\Timeline {#3117
id: 8304,
place_id: 1078,
},
App\Timeline {#3084
id: 8401,
place_id: 1078,
},
App\Timeline {#3116
id: 8513,
place_id: 1078,
},
App\Timeline {#3119
id: 9363,
place_id: 1078,
},
],
},
},
],
}
You can override the get() method from Eloquent by creating a new class CustomQueryBuilder that extends the Illuminate\Database\Query\Builder:
Your customized get() method in CustomQueryBuilder:
<?php
namespace App\Override;
class CustomQueryBuilder extends \Illuminate\Database\Query\Builder {
private $needVisitsCombined = false;
public function needVisitsCombined() {
$this->needVisitsCombined = true;
return $this;
}
//#Override
public function get($columns = ['*']) {
//Get the raw query string with the PDO bindings
$originalResult = parent::get($columns);
if($this->needVisitsCombined == false){
return $originalResult;
}
$parentRecord = $originalResult->filter(function ($result, $key) {
return $result->parent_id != null;
});
$originalResult->each(function ($result, $key) {
if($result->parent_id == null) {
$parentRecord->visits()->concat($result->visits())
}
});
return $parentRecord;
}
}
?>
Create a new class CustomModel that extends the Illuminate\Database\Eloquent\Model and there override the newBaseQueryBuilder like this:
<?php
namespace App\Override;
use App\Override\CustomQueryBuilder;
class CustomModel extends Illuminate\Database\Eloquent\Model {
//#Override
protected function newBaseQueryBuilder()
{
$connection = $this->getConnection();
return new CustomQueryBuilder(
$connection, $connection->getQueryGrammar(), $connection>getPostProcessor(), $this
);
}
}
?>
Finally, Now your Place Model ( or Whichever require this custom get() behavior) can extend CustomModel instead.
Now your query will be something like:
Place::id(1)->needVisitsCombined()->with('visits')->get()
Note:
Be aware that all queries made from Models extending your CustomModel will get this new behavior, hence I have added a check with needVisitsCombined() method.
So if you're doing more customization do all the needed checks to don't mess up with Eloquent normal behaviour, something like this
I have to develop an api with Laravel and i'm stuck with this problem:
I need to get all matchs and for each match i need to get the users who belongs to this match (there is 2 users per match)
I've search answer for this problem and i found this https://github.com/topclaudy/compoships but it didn't worked for me.
So far, i have this :
class Match extends Model
{
protected $table = 'matchs';
public $primaryKey = 'id';
public function playerOne() {
return $this->hasMany('App\User', 'id', 'playerOne_id');
}
public function playerTwo() {
return $this->hasMany('App\User', 'id', 'playerTwo_id');
}
}
Controller Method:
public function index()
{
$matchs = Match::with('playerOne', 'playerTwo')->orderBy('created_at', 'asc')->get();
return $matchs;
//return new MatchsCollection($matchs);
}
and my database look like this :
(Matchs table)
id | playerOne_id | playertwo_id | winner_id | status
------------------------------------------------------------
1 | 1 | 3 | 0 | PENDING
2 | 2 | 1 | 0 | PENDING
3 | 3 | 2 | 0 | PENDING
and users table :
id | name | email
-----------------------------------
1 | John | John#email.com
2 | Mark | Mark#email.com
3 | Harry | Harry#email.com
and when i call my api i get this result:
[
{
id: 1,
playerOne_id: 1,
playerTwo_id: 3,
winner_id: 0,
status: "PENDING",
created_at: "2019-01-10 00:00:00",
updated_at: null,
users: [
{
id: 1,
name: "John",
email: "John#email.com",
email_verified_at: null,
created_at: "2019-01-11 10:38:26",
updated_at: "2019-01-11 10:38:26"
}
]
},
{
id: 2,
playerOne_id: 2,
playerTwo_id: 1,
winner_id: 0,
status: "PENDING",
created_at: "2019-01-11 08:26:28",
updated_at: "2019-01-11 08:26:28",
users: [
{
id: 2,
name: "Mark",
email: "Mark#email.com",
email_verified_at: null,
created_at: "2019-01-11 10:40:13",
updated_at: "2019-01-11 10:40:13"
}
]
},
{
id: 3,
playerOne_id: 3,
playerTwo_id: 2,
winner_id: 0,
status: "PENDING",
created_at: "2019-01-11 08:45:22",
updated_at: "2019-01-11 08:45:22",
users: [
{
id: 3,
name: "harry",
email: "harry#email.com",
email_verified_at: null,
created_at: "2019-01-11 10:40:13",
updated_at: "2019-01-11 10:40:13"
}
]
}
]
What i wan't to get is this result (i just show you the first match)
[
{
id: 1,
playerOne_id: 1,
playerTwo_id: 3,
winner_id: 0,
status: "PENDING",
created_at: "2019-01-10 00:00:00",
updated_at: null,
users: [
{
id: 1,
name: "John",
email: "John#email.com",
email_verified_at: null,
created_at: "2019-01-11 10:38:26",
updated_at: "2019-01-11 10:38:26"
},
{
id: 3,
name: "Harry",
email: "Harry#email.com",
email_verified_at: null,
created_at: "2019-01-11 10:38:26",
updated_at: "2019-01-11 10:38:26"
}
]
}
]
is this possible in Laravel ? thanks :)
To be able to return all the players as part of the user array you could either change the relation between Match and User to be a belongsToMany (many-to-many) or simply manipulate the match data before returning it from the controller.
Changing it to a BelongsToMany would be my recommendation since it semantically makes more sense, however, I understand that (depending on how much work you've done so far) it may not be practical.
You would need to:
rename your matchs table to be matches (this will also mean you can remove
protected $table = 'matchs'; from your Match model class ).
Create a table called match_user. If your project is using migrations then you can run:
php artisan make:migration create_match_user_table --create=match_user
In that migration you would need to change the contents of the up() method to be:
public function up()
{
Schema::create('match_user', function (Blueprint $table) {
$table->integer('match_id')->unsigned();
$table->integer('user_id')->unsigned();
$table->timestamps();
$table->primary(['match_id', 'user_id']);
});
}
Run php artisan migrate
In your Match model change the users() relationship to be:
public function users()
{
return $this->belongsToMany(User::class)->withTimestamps();
}
If the data you have in your tables is dummy data you can skip this step. Otherwise, you'll need to move the playerOne_id and playertwo_id information to the new pivot table. You could do that will the following:
Match::all()->each(function ($match) {
$match->users()->attach([$match->playerOne_id, $match->playertwo_id]);
});
It doesn't really matter where you run this as it only has to be run once so after you've run it you can delete it. I would usually run this in a route or in tinker.
Then comes the clean up. Again, if the data you have is just dummy data, then I would just remove the playerOne_id and playertwo_id the original migration, otherwise create a new migration that removes those field Dropping columns docs
Once the above has been done you would just need to do the following in your controller to get the results:
$matches = Match::with('users')->latest()->get();