Eloquent - access parent value in nested function - php

I'm trying to access the parent value, the posts date, in a nested function.
In my application, a user has many posts, with each post being associated to a product (textbook). Each time someone views a page with the product, a new row is added to the product-views table.
I want to return the cumulative amount of times the users products have been seen. I've been able to get all the users posts, then the associated product, and then the count of all the views of that product.
Now I'd like to add another where() condition to only return views that have occured after the post was created. To do so, I need to get the posts date, e.g. views->product->post, while constructing the query like user->posts->product->views.
// getting all of the users available posts
$user = $request->user()->posts()->available()->with([
// with the textbook (product) associated with the post
'textbook' => function ($q) {
// get the count of textbook-page views
return $q->withCount([
// from the related table views
'views' => function ($q) {
// ===!!! Q !!!=== How do I access the posts (parent (grandparent?)) date, so that I only select the rows that have been viewed after the post was created ===!!!===
->where('date-viewed', '>=', 'posts.date');
},
]);
//some cleanup
}])->distinct()->get()->unique('isbn')->pluck('textbook.views_count')->sum();
How do I go backwards in a nested function to access the posts date?

It looks like as Jonas said in the comments, each with() relationship was a new query, so I ended up creating a hasManyThrough() relationship between the posts and views through the product.
// getting all of the users available posts
$user = $request->user()->posts()->available()->withCount([
'views' => function ($q) {
// ===!!! A !!!=== Fixed ===!!!===
->whereRaw('`date-viewed` >= posts.date');
},
])->distinct()->get()->unique('isbn')->pluck('views_count')->sum();

Related

Laravel pagination with specific foreign key

I need to make pagination in Laravel, and as I read laravel documentation here is a several way to do it. but in my cases it is a little bit complexity. So let say I've two table in my DB, table1 and table2(many to many relationship), I want to paginate table1 with specific ids. So I mean if table1 contains ids 1,2,3,4,5 ... n, I want to paginate only rows which id is 2 and 3
I have tried :
private function check_wear($type){
$products_id = array(); //here is ids which $type is set as 'ON'
$wears = DB::select("SELECT prod_id FROM wears WHERE ".$type." = 'on' "); //Select specific products ids. (in our case it is **table1**)
foreach ($wears as $wr){
array_push($products_id,$wr->prod_id);
}
return $products_id; //return array with ids which should be paginate
}
public function hat($locale){
$hat_prod_ids = $this->check_wear('coat');
$p=12;
$products = DB::table('products')->paginate($p); // 12 per page
dd($products);
my code only paginate one table with all data, is there any built-in function to somehow write a logic condition? for pagination
If you need to filter your pagination with elements which you've in $hat_prod_ids, you're able to use WhereIn() method, that one checks if your elements (in our case ids) exist in your $hat_prod_ids and returns true or false
$products = DB::table('products')
->whereIn('id',$hat_prod_ids)
->paginate($p);
add that code and now you will be able to paginate only that ids which is matched in your array

Getting 2 result from one query with elequent laravel

I want to get 2 result from a filter query in Laravel version 6. I want to get product table information that user can change item exist in page by a select box with name of pageitemcount and can search by a field with name of select. when user select pageitemcount and also search for an name, submit a form and below query is run . When user search anything, available filter of category must be limited to available categories. I want to show distinct product category in additional to product list.
public function productIndex(Request $request)
{
$take = 10;
$query = Product::query();
if ($request->has('pageitem')){
$take = $request->input("pageitem");
}
// Search
if ($request->has('search')){
$query = $query->where('name', $request->input("search"));
}
$availableCategoriesQuery= $query;
// Get product unique product category to show them to user
// that exist in prodcut list that show to user
$availableCategories = $availableCategoriesQuery->select("category_id")->distinct()->get();
// Get product list by pagination
$productList = $query->paginate($take);
return view('admin.manage_product', ['productList' => $productList, 'availableCategories' => $availableCategories]);
}
but when $availableCategories is executed, affected $query variable, and i cant get right result from next line, i get result of $availableCategories in $productList variable. i do'nt want to repeat my code again, how can i deal with?
the result of $availableCategories is completely true, but result of productList is $availableCategories that is paginated.
OK, I think I see the problem. You are trying to reuse the $query variable. You probably want something like this:
I assume that product_categories is the table for your product categories. You can also use the model if you want.
Also, Since the DB has no intrinsic order, you should specify an OrderBy() as well.
$take = $request->input("pageitem", 10);
$productList = Product::paginate($take)->get();
$availableCategories = DB::table('product_categories')->distinct()->get("category_id");

Eloquent HasMany relationship, with limited record count

I want to limit related records from
$categories = Category::with('exams')->get();
this will get me exams from all categories but what i would like is to get 5 exams from one category and for each category.
Category Model
public function Exams() {
return $this->hasMany('Exam');
}
Exam Model
public function category () {
return $this->belongsTo('Category');
}
I have tried couple of things but couldnt get it to work
First what i found is something like this
$categories = Category::with(['exams' => function($exams){
$exams->limit(5);
}])->get();
But the problem with this is it will only get me 5 records from all categories. Also i have tried to add limit to Category model
public function Exams() {
return $this->hasMany('Exam')->limit(5);
}
But this doesnt do anything and returns as tough it didnt have limit 5.
So is there a way i could do this with Eloquent or should i simply load everything (would like to pass on that) and use break with foreach?
There is no way to do this using Eloquent's eager loading. The options you have are:
Fetch categories with all examps and take only 5 exams for each of them:
$categories = Category::with('exams')->get()->map(function($category) {
$category->exams = $category->exams->take(5);
return $category;
});
It should be ok, as long as you do not have too much exam data in your database - "too much" will vary between projects, just best try and see if it's fast enough for you.
Fetch only categories and then fetch 5 exams for each of them with $category->exams. This will result in more queries being executed - one additional query per fetched category.
I just insert small logic inside it which is working for me.
$categories = Category::with('exams');
Step 1: I count the records which are coming in response
$totalRecordCount = $categories->count()
Step 2: Pass total count inside the with function
$categories->with([
'exams' => function($query) use($totalRecordCount){
$query->take(5*$totalRecordCount);
}
])
Step 3: Now you can retrieve the result as per requirement
$categories->get();

Counting total distant relationships in Laravel (eager loading?)

I'm having issues getting a proper count total with my Laravel model.
Model Structure
User
Item
ItemLike
A user can have multiple Items, and each of these Items can have multiple ItemLikes (when a user 'likes' the item).
I can easily get the individual ItemLike counts when using an Item model:
return $this->itemLikes()->count();
But I can't figure out how to get the total # of ItemLike's a User has across all the Item's he owns.
EXAMPLE
User A has 3 Items. Each Item has 5 ItemLike's, for a grand total of 15.
I tried using eager loading on the User model like this:
return $this->items()->with('itemlikes')->get()->count();
But that returns 3 (the # of Items)
These are the queries it ran, which appears like the second query is the one I want, yet every way I try it I still get 3 instead of 15
select * from `items` where `items`.`user_id` = '1000'
select * from `item_likes` where `item_likes`.`item_id` in ('1000', '1001', '1002')
After suggestions from others I found 2 solutions to get the result.
Using whereIn:
$itemViewCount = ItemView::
whereIn('item_views.item_id', $this->items()->lists('id'))
->count();
return $itemViewCount;
2 queries for a total of 410μs
Using join:
$itemViewCount = $this->items()
->join('item_views', 'item_views.item_id', '=', 'items.id')
->count();
return $itemViewCount;
2 queries for a total of 600μs
Isn't it just a case of creating a method that would return the number of items for the model. e.g.:
#UserModel
public function nbLikes()
{
$nbLikes = 0;
foreach($this->items() as $item) {
$nbLikes += $item->itemLikes()->count();
}
return $nbLikes;
}
And then User::nbLikes() should return the piece of data you are looking for?
try this:
$query="select count(il.id) from item_likes il,item itm where il.item_id=itm.id and tm.user_id=1000";

How to get a list of posts and check if the user already "Liked" them? (Check existing relation between two models)

I'm having fun with Laravel framework trying to build a little "blog" site that works the following way:
When you enter the webpage, a cached index of the latest / most popular posts is shown
There is a ManyToMany Relationship "user_likes" between the User and Post models, so that if you're logged in then you can "Like" or "upvote" each of these posts with a "heart" button, so that a relation between the two models will be created in the user_likes pivot table.
I have two database tables
users table:
user_id name
1 TheUser
posts table:
post_id title
1 MyPost
2 Another Post
3 Yet another post
user_likes pivot table:
post_id user_id
1 1
3 1
And the User Model Class:
class User extends BaseUser {
protected $table = 'users';
public function likes()
{
return $this->belongsToMany('Post', 'user_likes','user_id','post_id');
}
}
The question is, what's an efficient way of retrieving a list of Post models and checking if the logged in User has already liked / upvoted them?
The thing is that i'd like to check for every Post if the relation between the logged in User and the Post model exists in the most efficient way.
Thanks in advance!
EDIT:
This SQL Fiddle would be a way of achieving what I'd like to do, the problem is that If i'm not wrong, I won't be able to cache the posts, as I'll need to check them every time a new user logs in.
http://www.sqlfiddle.com/#!2/e5bab/2
Using Laravel's Query builder:
DB::table('posts')
->leftJoin('user_likes', function($join) {
$join->on('user_likes.post_id', '=', 'posts.id')
->where('user_likes.user_id', '=', 1);
})
->select('posts.id as post_id', 'posts.title', 'user_likes as did_i_like')
->groupBy('posts.id')
->orderBy('posts.id');
Right now, I'm doing it in my application in a different way:
I have the paginated posts cached, and then each User has a cached collection of the posts ids that they liked, so when they log in, I'll check if each post is in the user's liked post collection:
$liked = Auth::user()->getLikedPosts(); // returns a cached collection of the User liked posts ids
$posts = Post::popular(1); // returns a cached and paginated index of the most popular posts.
Then, I'll check if the liked collection contains the post id:
foreach($posts as $post)
{
.......
if($liked->contains($post->id)
{
// show red Heart
}
else
{
// show gray heart
}
.......
I think It's not a good way of doing it, since the $liked collection will be growing a lot... However, with the new Query, I really don't know how could I cache the posts, so that I don't have to query the Database each time a user logs in or navigates throught the posts pages.
Thanks in advance!
I am honestly not sure if it's good or not, but I did it like this:
Post Model:
public function likes()
{
return $this->hasMany('Like');
}
public function likedByUser()
{
return $this->likes()->where('user_id', Auth::user()->id);
}
than in blades:
#if(isset($post->likedByUser[0]))
style="background-position: right;" data-value="liked"
#endif
I don't much about speed, but I'm paginating 9-12 each time, so It's not that bad, I hope :)
What's an efficient way of retrieving a list of Post models and
checking if the logged in User has already liked / upvoted them?
in your Post model declare a relationship:
public function likedByUser()
{
return $this->belongsToMany('User', 'user_likes', 'post_id', 'user_id')
->where('user_id', Auth::user()->id);
}
Then retrieve posts which has been liked by the logged in user like this:
$posts = Post::has('likedByUser')->get();
Update:
$posts = Post::get();
$firstPost = $posts->first();
$firstPost->has('likedByUser');
Also you may loop and check:
$posts = Post::get();
foreach($posts as $post) {
if($post->has('likedByUser')) {
// liked it
}
}

Categories