Attach new key and value by matching collection value - laravel

Hello great people of SO!
First of all, I'm sorry if my english is not very good, but I'm gonna try my best here to describe my problem
I have 3 Models
User
Post
Like
User.php
___________________________________________________________
| id | name | email | password | created_at | updated_at |
| ... | ... | ... | ... | ... | ... |
| ... | ... | ... | ... | ... | ... |
| ... | ... | ... | ... | ... | ... |
User model relationship:
public function posts() {
return $this->hasMany(Post::class, 'user_id', 'id');
}
Post.php
__________________________________________________
| id | user_id | body | created_at | updated_at |
| ... | ... | ... | ... | ... |
| ... | ... | ... | ... | ... |
Post model relationship:
public function user() {
return $this->belongsTo(User::class, 'user_id', 'id');
}
public function likes() {
return $this->morphMany('likeable');
}
Like.php
_________________________________________________________________________
| id | likeable_type | likeable_id | user_id | created_at | updated_at |
| ... | ... | ... | ... | ... | ... |
| ... | ... | ... | ... | ... | ... |
| ... | ... | ... | ... | ... | ... |
Like model relationship:
public function likeable() {
return $this->morphTo('likeable');
}
Everything works fine, for simple C.R.U.D
The problem comes when I use Laravel Debugbar,
I saw so many repetitive queries just to fetch few records:
Ex:
// Let say that I have 5 users
$users = User::all();
foreach ($users as $user) {
$user->load('posts');
}
return $users;
// Result
select * from `posts`.`user_id` 1 ...
select * from `posts`.`user_id` 2 ...
select * from `posts`.`user_id` 3 ...
So I decide to change the method
Ex:
$users = User::all();
$posts = Post::whereIn('user_id', $users->pluck('id')->toArray())->get();
// Result:
select * `posts`.`user_id` in (1, 2, 3, 4, 5)
I no longer see repetitive query, which is good,
After solving this repetitive query, I fetch 'likes' from specific posts
Ex:
$users = User::all();
$posts = Post::whereIn('user_id', $users->pluck('id')->toArray())->get();
$posts_likes = Like::where('likeable_type', 'App\Post') // morphMany
->whereIn('likeable_id', $posts->pluck('id')->toArray())->get();
Now here's the problems, I do not know how to pair posts_likes to to it's post
Ex:
$posts = [
{
'id': 1,
'user_id': 2,
'body': 'Lorem ipsum...',
...
},
{
'id': 2,
'user_id': 2,
'body': 'Sit amet...',
...
},
{
'id': 3,
'user_id': 3,
'body': 'abcde...',
...
},
... etc
];
$posts_likes = [
{
'id': 1,
'likeable_type': 'App\Post',
'likeable_id': 2,
'user_id': 3,
...
},
'id': 2,
'likeable_type': 'App\Post',
'likeable_id': 2,
'user_id': 2,
...
{
'id': 3,
'likeable_type': 'App\Post',
'likeable_id': 1,
'user_id': 5,
...
},
{
'id': 4,
'likeable_type': 'App\Post',
'likeable_id': 3,
'user_id': 1,
...
},
... etc
];
My question:
How to insert likes inside $posts collection by matching exact id? (post id == like likeable_id)
So I can access them in loop, like: $post->likes = [...]
Ex:
$posts = [
{
'id': 1,
'user_id': 2,
'body': 'Lorem ipsum...',
'likes': [
// All likes for post with this id (1)
],
...
},
{
'id': 2,
'user_id': 2,
'body': 'Sit amet...',
'likes': [
// All likes for post with this id (2)
],
...
},
...
];
If there's any unclear explanation, I will edit it a.s.a.p
Thanks in advance

you can eagar load all of them
ref link https://laravel.com/docs/8.x/eloquent-relationships#nested-eager-loading
$users = User::with('posts.likes')->get();
return $users;
this code will work if you set correct relationship
//user model
public function posts()
{
return $this->hasMany(Post::class, 'user_id', 'id');
}
//post model
public function likes()
{
return $this->morphMany('likeable');
}
i suppose to generate json link
[{
"name": "user",
"posts": [{
"name": "postName",
"likes": []
},
{
"name": "postName",
"likes": []
}
]
}]

Related

How to change the structure of eager loaded data efficiently in Laravel

I'm loading a product eagerly with its relationship data in Laravel.
$product = Product::with('attributeValues.attribute')->find($id)->get();
Currently I get the response structure as follows.
[
{
"product_id": 1,
"product_name": "Shirt A",
"attribute_values": [
{
"attribute_value_id": 1,
"attribute_value": "small",
"attribute": {
"attribute_id": 1,
"attribute": "size"
}
},
{
"attribute_value_id": 1,
"attribute_value": "medium",
"attribute": {
"attribute_id": 1,
"attribute": "size"
}
},
...
]
},
...
]
The structure I expected to get is as follows.
[
{
"product_id": 1,
"product_name": "Shirt A",
"attribute": [
{
"attribute_id": 1,
"attribute": "size",
"attribute_values": [
{
"attribute_value_id": 1,
"attribute_value": "small"
},
{
"attribute_value_id": 1,
"attribute_value": "medium"
}
]
},
...
]
},
...
]
The current relationships of the models are as follows
class Product extends Model {
public function attributeValues(){
return $this->belongsToMany(AttributeValue::class, 'product_attributes');
}
}
class AttributeValue extends Model {
public function attribute(){
return $this->belongsTo(Attribute::class);
}
}
class Attribute extends Model { }
Currently I'm succeeded getting this structure correctly by using product and getting it attributes separately using raw queries. I'm trying to achieve a Eloquent way of doing this task.
The tables I have are as follows
products
+----+------+
| id | name |
+----+------+
product_attributes
+----+------------+--------------------+
| id | product_id | attribute_value_id |
+----+------------+--------------------+
attribute_values
+----+--------------+-------+
| id | attribute_id | value |
+----+--------------+-------+
attributes
+----+-----------+
| id | attribute |
+----+-----------+
you can use with inside with using custom eager loading:
$product = Product::with(['attributeValues'=>function($query){
$query->select(['attribute_value_id','attribute_value'])
->with('attribute:attribute_id,attribute');
}])->find($id)->get();
What about using collections methods ? I'm not an expert but i would have done something like this :
$products->map(function($product){
$attribute = $product->attribute_values->groupBy('attribute.attribute')->map(function($groupAttribute,$key){
$attribute_values = $groupAttribute->map(function($attribute) {
return ['attribute_value_id'=>$attribute->attribute_value_id,'attribute_value'=>$attribute->attribute_value];
});
return ['attribute_id' => 1 ,'attribute'=> $key , 'attribute_values' => $attribute_values ];
});
return [
'product_id'=>$product->product_id,
'product_name'=>$product->product_name,
'attribute'=>$attribute
];
});
Just do the following you will get the required result.
$product = Product::with('attribute.attributeValues')->find($id)->get();
The best way is to use JsonResource.
Here you can find a short and easy example
Just define ProductResource, attribyteValueResource, and AttributeResource
Here is the example:
PropertyResource:
use Illuminate\Http\Resources\Json\JsonResource;
class PropertyResource extends JsonResource
{
public function toArray($request): array
{
return [
'product_id' => $this->product_id,
'product_name' => $this->product_name,
'attribute_values' => AttributeResource::collect( $this->whenLoaded('attributeValues') ),
];
}
}
AttributeValueResource:
use Illuminate\Http\Resources\Json\JsonResource;
class AttributeValueResource extends JsonResource
{
public function toArray($request): array
{
return [
'attribute_value_id' => $this->attribute_value_id,
'attribute_value' => $this->attribute_value,
'attribute' => AttributeValueResource::make( $this->whenLoaded('attribute') ),
];
}
}
AttributeResource:
use Illuminate\Http\Resources\Json\JsonResource;
class AttributeResource extends JsonResource
{
public function toArray($request): array
{
return [
'attribute_id' => $this->attribute_id,
'attribute' => $this->attribute,
];
}
}
Then just use them:
$product = ProductResource::make(
Product::load('attributeValues.attribute')->find($id)
);

Laravel eloquent get count on belongsTo id

I have 2 tables subscribers & subscriber_packages. I need to fetch the count of currently active packages for the selected subscriber via given subscriber_package / id.
Tables:
# | subscriber_id | package_id | active
---------------------------------------
1 | 1 | 1 | true
2 | 1 | 2 | true
3 | 1 | 3 | true
4 | 1 | 4 | false
---------------------------------------
App\SubscriberPackage.php
class SubscriberPackage extends Model
{
public function subscriber()
{
return $this->belongsTo(Subscriber::class);
}
public function package()
{
return $this->belongsTo(Package::class);
}
public function _countActivePackages($subscriberPackage)
{
return self::where([
'id' => $subscriberPackage,
'status' => \Common::STATUS_ACTIVE,
])->whereHas('subscriber')->count();
}
}
At your subscriber you need to define correctly a HasManyThrough relationship and just add properly a where cause to check the status even in separate method
public function packages()
{
return $this->hasManyThrough(Package::class, SubscriberPackage::class);
}
public function activePackages()
{
return $this->packages()
->where("subscriber_package.status", "=", "active");
}
After that you can always make a count with it
$subscriber->activePackages()->count();

Eloquent eager loading relationship

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

How use conditional relationship in eloquent laravel

I have a 'conversation_message' table and separate sender role by column 'sender_type' (admin/user). Both of admin & user in a different table. But when I call the model, that showed error Call to a member function addEagerConstraints() on null
Table column and data
| id | id_group | id_reply | id_sender | sender_type | message
| 1 | 1 | null | 3 | admin | Hi, I'm admin
| 2 | 1 | 1 | 3 | admin | I wanna give u promo
| 3 | 1 | 2 | 18 | user | What's promo ?
I've tried if conditional with column value, but it doesn't work.
Conversation_message.php
public function sender(){
switch($this->sender_type){
case "user":
return $this->belongsTo(User::class, 'id_sender', 'id');
case "admin":
return $this->belongsTo(Admin::class, 'id_sender', 'id');
default:
return;
}
}
InboxController.php
public function message_detail_chat($groupId){
$data = [];
$data['messages'] = Conversation_message::with('replies')
->with('sender')
->with('recipients')
->where(['id_group' => $groupId])->get();
}
I expect to use conditional model by column value 'sender_type' but the actual output is wrong.
laravel provide query scope your can read it from here https://laravel.com/docs/5.8/eloquent#local-scopes
function userSender(){
$this->belongsTo(User::class, 'id_sender', 'id');
}
function adminSender(){
return $this->belongsTo(Admin::class, 'id_sender', 'id');
}
public function scopeSender($query)
{
return $query
->when($this->sender_type === 'user',function($q){
return $q->with('userSender');
})
->when($this->sender_type === 'admin',function($q){
return $q->with('adminSender');
});
}
Now you can access your Sender like this
Conversation_message::Sender()->first();
This should give you the right Sender. Hope it helps.

Laravel miltiple level relationship with limit()

I have some problem with Laravel Eloquent and need your help.
I want to display 1 replies of each comment of single post.
Here is my tables
posts (id,title)
id | title
---------------------
1 | My post
---------------------
comments(id,post_id,comment,parent_id)
id | post_id | comment | parent_id
-----------------------------------------
1 | 1 | First comment | null
-----------------------------------------
2 | 1 | Second comment | null
-----------------------------------------
3 | null | 3rd comment | 1
-----------------------------------------
4 | null | 4th comment | 1
-----------------------------------------
5 | null | 5th comment | 2
-----------------------------------------
6 | null | 6th comment | 2
-----------------------------------------
My model (Eloquent)
class Post extends Model
{
public function comments()
{
return $this->hasMany('Comment', 'post_id');
}
}
---------------------
class Comment extends Model
{
public function reply()
{
return $this->hasMany('Comment', 'parent_id');//self relationship
}
}
My query function
public function getPost($postId){
$posts = Post::with(['comment.reply'=>function($q){
$q->limit(1);
}])
->find($postId);
return $posts;
}
And I get result
{[
id=>1,
title=>'My post',
'comments'=>[
0=>[
id=>1,
comment=>'First comment',
parent_id=>null,
post_id=>1,
reply=>[
0=>[........(comment id:3).......]
]
],
1=>[
id=>2,
comment=>'Second comment',
parent_id=>null,
post_id=>1,
reply=>null
]
]
]}
But I want like this
{[
id=>1,
title=>'My post',
'comments'=>[
0=>[
id=>1,
comment=>'First comment',
parent_id=>null,
post_id=>1,
reply=>[
0=>[........(comment id:3,4)........]
]
],
1=>[
id=>2,
comment=>'Second comment',
parent_id=>null,
post_id=>1,
reply=>[
0=>[........(comment id: 5,6).........]
]
]
]
]}
Please kindly help!
Try this:
$posts=Post::where(['id'=>1])->with(['comments'=>function($query)
{
$query->with(['replies'=>function($query)
{
$query->limit(1);
}]);
}])->first();
print_r($posts);

Resources