Push an array into Laravel collection with matching keys - php

I have a collection called User, I also have an array containing two models Post relating to the User model.
The User collection contains a primary key id and each model in my Post collection I have a foreign key user_id.
I am currently executing the following:
foreach ($users as $user) {
foreach ($posts as $post) {
if ($post->user_id == $user->id) {
$user->posts->push($post);
}
}
}
This somewhat works, but not entirely because it pulls in all related posts instead of the recent two posts a user has made.
The array looks like the following:
My User schema looks like; with a hasMany relationship to Post:

You can load the posts associated to a User using with, something like
$user = User::with('posts')->find($id);
But your scenario sounds specifically collecting the latest two Post belonging to a User. To limit your results you can also use scopes.
Something like the following on your Post model would work:
public function scopeLatest($query, $latest = 2)
{
return $query->limit($latest);
}
Then collect these by:
// The user record.
$user = User::find($id);
// Latest 2 posts for this user.
$posts = $user->posts()->latest();
// Latest 5 posts for this user.
$posts = $user->posts()->latest(5);
However, should you with to load the latest 2 posts with the user in a single query - then you could make a new relation:
public function latestPosts()
{
return $this->hasMany(Post::class, 'post_id', 'id')
->orderBy('created_at', 'ASC')
->limit(2);
}
This would work in the following way:
// Load the user with the latest 2 posts.
$user = User::with('latestPosts')->find($userId);
// Access these using; this will be a Collection containing 2 `Post` records.
dd($user->latestPosts);
Basically with Eloquent, when you call $this->latestPosts Eloquent will run latestPosts() and hydrate the related records. Using with this hydration occurs with a single query and the relations are already defined.
The difference between the method latestPosts() and the property $latestPosts is simple.
The method will always return a specific Relation Collection allowing you to chain additional conditions;
So: $user->latestPosts()->get() is the same as $user->latestPosts.

You cannot use query constraints / eager loading to do this. Doing so will only work if you are retrieving the posts for one user. However, if you try to retrieve the posts for multiple users, it will fail because eager loading / query constraints will limit the related results as a whole. To understand, you have to look at the queries Eloquent generates. Lets take a look at an example where you only need one user's posts.
$user = User::with(['posts' => function($query) {
$query->limit(2);
}])->find(1);
In this example, we are getting a user with a primary key of 1. We also also retrieving his/her posts but limiting it so we only retrieve 2 posts. This works, and it will generate 2 queries similar to this:
select * from `users` where `users`.`id` = 1 limit 1
select * from `posts` where `posts`.`user_id` in (1) limit 2
Okay. Now, why doesn't this work if you try to get more than 1 user (or a collection of users)? For example:
$user = User::with(['posts' => function($query) {
$query->limit(2);
}])->get();
In this case, I changed find(1) to get(), and it will generate 2 queries like this:
select * from `users`
select * from `posts` where `posts`.`user_id` in (?, ?, ?, ... ?) limit 2
It's important to take a look at the second query. It's retrieving all the related posts, but at the end, you'll see that it has limit 2. In other words, it's limiting the entire related collection to only 2, which is why query constraints do not work for this.
Achieving this is actually pretty complex, but a fellow member (Jarek Tkaczyk) came up with a solution using MySQL variables, which you can find here: Laravel - Limit each child item efficiently

You can do this a bit simpler with https://laravel.com/docs/5.2/eloquent-relationships#eager-loading constraints.
Example: Users have many Dogs, but only take 2
$user = App\User::with(['dogs' => function ($query) {
$query->limit(2);
}])->find($user_id);
dump($user);
The anonymous constraining function would also have an orderBy in your case

Related

Cant retrieve record from large scale table row

I am using laravel framework.In my database records table have more then 400000 row. Now I want to retrieve record using some condition
logic :
need to match service id
need to match status
but my application cant retrieve data (unable to handle this request).
I am using query using this
foreach ($ven as $ven){
$data = $ven->data;
$record = $data ->records()->where('status','success')->get();
}
My status column already added in index.
Need suggestion
First you need to keep statuses in int form not as strings. It would help you in filtering records easily.
And use chunk for handling large datasets. I suppose $ven is for venue. So, follow the code below.
DB::table('venues')
->select('venues.*', 'records.*')
->join('records', 'venues.id', '=', 'records.venue_id')
->orderBy('id')->chunk(1000, function($venues) {
foreach ($venues as $venue) {
// your logic
}
});
Note: I used query builder instead eloquent. That is because query builder is faster than eloquent.
The way you are trying to access related records in loop could create N + 1 problem
As per official docs When accessing Eloquent relationships as properties, the relationship data is "lazy loaded". This means the relationship data is not actually loaded until you first access the property. However, Eloquent can "eager load" relationships at the time you query the parent model. Eager loading alleviates the N + 1 query problem
You could eager load your related data as
$results = ParentModel::with('records')
->where(...) /* If there are any filters for ParentModel */
->get();
For eager loading only 2 queries will be executed to get your data for main model and related model. Each row in $results will a collection of related records which you can iterate to list details of each related object.
To get filtered rows of related model you could modify with() method as
$results = ParentModel::with(['records' => function ($query) {
$query->where('status','=','success');
}])
->where(...) /* If there are any filters for ParentModel */
->get();
To get filtered rows of ParentModel on basis of related records you could use whereHas
$results = ParentModel::with('records')
->where(...) /* If there are any filters for ParentModel */
->whereHas('records', function ($query) {
$query->where('status','=','success');
})->get();

Calling Laravel relationship with aggregate

If I have a Laravel 5.5 model called User that hasMany Posts and each Post hasMany Hits, is there an aggregate function I can call to get the total number of Hits for a User across all Posts, where the Hit was created in the last week?
It seems like there may be a clever way to do it besides doing something like
$hits = $user->posts()->hits()
and then looping over those hits to check created date.
In this case it seems like raw sql would be better, but I figured there may be an Eloquent way to handle a situation like this.
I think the right solution is just to use a HasManyThrough relationship to grab all the Hit rows, joined through the posts table.
So it'd look like this on the User model (roughly):
return $this->hasManyThrough(
Hit::class,
Post::class
// if you have non-standard key names you can specify them here-- see docs
);
Then when you have your User model you can just call $user->hits to get a collection of all the associated hits through all the user's Posts
You can add the code below to your Post model.
protected static function boot()
{
parent::boot();
static::addGlobalScope('hitCount', function ($builder) {
$builder->withCount('hits');
});
}
It automatically provides a field hits_count whenever you fetch a post.
$post = Post::first();
$hits = $post->hits_count; //Count hits that belongs to this post
You can read the documentation here to customize it to your need.
Set HasManyThrough relation in the User model:
public function hits()
{
return $this->hasManyThrough('App\Models\Hits','App\Models\Posts','user_id','post_id','id');
}
then you can do this:
$reults = $user->hits()->where('hits_table_name.created_at', '>=', Carbon::today()->subWeek())->count();
HasManyThrough Link
Use DB::enableQueryLog(); and DB::getQueryLog(); to see if executed SQL Query is correct;

Trying to get data via related table in Laravel

What I'm trying to do is to show the posts that have been saved by the user in the profile. I will try to explain it as good as possible refering to my code. So:
public function userProfil($id)
I have the profile function which get the data from userprofile table. and inside I have the following code for saved data:
$authed = User::find($id);
$savedarticles = $authed->mysaves;
$allsavings = DB::select("Select * from article where id=$savedarticles->id");
But this code does not work like this anyway. I can do this instead:
$authed = User::find($id);
$savedarticles = $authed->mysaves;
But when I try to get articles from article table with the article_id of mysaves, it does not work such as this:
$allsaved= DB::table('article')->where('id', $savedarticles->article_id);
the error it gives is like:
Property [article_id] does not exist on this collection instance.
although savearticle table has article_id I can output it without the line above and in view I get them as:
#foreach($savedarticles as $savedarticle)
<p>{{$savedarticle}}</p>
#endforeach
it gives me everything that is in the savearticle table and I can get do savedarticle->article_id and get article_id but can't get it in controller.
I am using Laravel 5.4.
The error message Property [article_id] does not exist on this collection instance. means you are trying to get an attribute of a single instance but from a collection.
For example the collection could be like
[$article1, $article2, $article3]
therefore what you tried to do is something similar to
[$article1, $article2, $article3]->article_id
You are trying to get an attribute from a collection instead of a single instance.
As for your query, you can use where in sql statement to search for rows that match any item in an array
$allsaved= DB::table('article')->whereIn('id', $savedarticles->pluck('article_id')->all());
What I have understood is that A USER has many POSTS and a POST belong to an article.
If this is true then you have to do following.
1: In USER model define a relation to get all posts. like below.
public function posts() {
// Foreign key will be a key that is stored in posts table and represent the user MAY BE: user_id
$this->hasMany(Posts::class, 'foreign_key', 'local_key')
}
This will allow you to get all posts belong to a user.
2: In posts, model defines a user relation like below.
public function user() {
$this->belongsTo(User::class, 'foreign_key', 'local_key');
}
This will allow you to get a post User;
3: Now in your controller you will have something like this.
public function show($user_id) {
// find a user with posts as eager loading(to avoid query again)
$user = User::with(['posts'])->where('id', $user_id)->first();
// get all posts that belong to this user
$posts = $user->posts;
}
In controller show($user_id) method you will have a user data as well as user posts data. Now if you want to get a post relations then simply define as below. let say a post belongs to an article as well.
4: In posts, model defines a relation to get an article.
public function article() {
// This will allow you to get a post artcle
$this->belongsTo(Article::class, 'foreign_key', 'local_key');
}
Now you can get the article as well while finding a user. please see below. I am rewriting controller show action to give you a better understanding.
5: Get a user with user_id
public function show($user_id) {
// find a user with posts as eager loading(to avoid query again)
// eager loading for posts & post child, this will give you NOSQL at runtime and all data will come from one query.
$user = User::with(['posts', 'posts.article'])->where('id', $user_id)->first();
// get all posts that belong to this user
$posts = $user->posts;
foreach($posts as $post) {
$article = $post->article; // Child relation of post.
}
}
Hope you will understand the flow, you have to make sure models relation to work it perfectly. If you need further help please let me know.

Laravel Eloquent query unexpected result

I've found some query result really unexpected.
It's Laravel 5.2
We have following entity:
User with method:
public function roles() : BelongsToMany
{
return $this->belongsToMany(Role::class)->withPivot('timestamp');
}
Each User can have many roles, so we have also Role entity (but it doesn't matter much in my question) and pivot table user_role with timestamp field (and ids of course), because we hold information about time, when User achieved specific role.
I want to get all Users with theirs last assigned Role
When I create query (in User context in some repository):
$this->with(['roles' => function($query) {
$query->orderBy('timestamp', 'desc');
}])->all();
the result will contain Users with Roles entities inside itself ordered by timestamp - it's ok. But I want to retrieve only one last role inside each User entity not all ordered.
So...
$this->with(['roles' => function($query) {
$query->orderBy('timestamp', 'desc')->limit(1);
}])->all();
And then I retrieve Users but only User which achieved some Role for the very last time contains it! All the other Users have their roles field containing empty array.
Why ordering was performed on each Users relation separately, but when I added limit it behaved like a global limit for all.
It drives me crazy...
Thanks for advices.
EDIT
I've created lastRoles() method to get all Roles ordered desc. But all, retrieving one is impossible.
public function lastRoles() : BelongsToMany
{
return $this->BelongsToMany(Roles::class)->withPivot('timestamp')->latest('timestamp');
}
And for testing:
$users = (new User())->with('lastRoles')->get();
But now I must iterate over Users and invoke lastRoles() on each one:
foreach ($users as $user) {
var_dump($user->lastRoles()->get()->first()->name);
}
Then I retrieve names of latest Roles assigned to each User.
So... There is no way to do it in one query? This is the only way?
For this to work, you would need a helper function:
public function latestRole()
{
return $this->hasOne(Role::class)->withPivot('timestamp')->orderBy('timestamp', 'DESC');
}
And then:
$this->with('latestRole')->get();
Credits to this awesome article.
When you eager load a relationship with query constraint(s), the query will be run once to load all relationships, not each one individually. This is the expected behavior. Think about it, eager loading exists to turn many queries into one query in order to optimize performance. There is only one query executed, so your limit constraint will limit the entire result set, rather than on a per model basis.
To circumvent this, you could try creating another belongsToMany method that adds the desired limit constraint. The following code is untested:
public function lastRole() : BelongstoMany
{
return $this->belongsToMany(Role::class)
->withPivot('timestamp')
->orderBy('timestamp', 'desc')
->limit(1);
}
Assuming this works, you can then simply change the relationship method from roles to lastRole and remove your query constraint:
$this->with('lastRole')->all();

Eloquent where owner equals follow

I am trying to make a twitter like feed in an application, I have a database called connections where inside there's user and follow and another database called feed containing owner which would equal to the follow column in connections.
What I could do if had every id of a follower statically is to use where('owner', '=' $follow) on each follower and return it.
I tried this approach but it wasn't ideal:
Get each follower inside connections;
foreach(follower) {
Get 10 of the latest posts orderBy "created_at";
Push into array;
}
shuffle array;
limit array to 15;
return array;
That also ended with the returned array not being ordered by created date.
How would I use eloquent to get the feed item only if the user follows the owner in the best/simplest way?
Are there any specific Laravel tools that can be used?
Also the database layout isn't fixed as it is, it can be altered if needed to better suite this.
You need to create a many-to-many relationship between the user and itself. See the laravel eloquent documentation http://laravel.com/docs/eloquent#relationships. I didn't test this code but it should be enough to get you started down the right track.
Create a pivot table called "user_following" with:
(int) id, (int) user_id, (int) following_id
Then do something like this:
<?php
// Model
class User extends Eloquent {
public function following()
{
return $this->belongsToMany('User', 'user_following', 'user_id', 'following_id');
}
public function tweets()
{
return $this->hasMany('Tweet')->orderBy('created_at', 'desc');
}
}
// controller
$tweetsOfWhoImFollowing = User::find($id)->following->tweets;

Categories