Laravel: Ordering data at a query level when using pivot tables? - php

In my routes/web.php I have a route like this...
Route::get('/tags/{tag}', 'TagsController#show');
Then, inside TagsController because I have a post_tag pivot table that has been defined as a many-to-many relationship.
Tag.php...
public function posts(){
return $this->belongsToMany(Post::class);
}
public function getRouteKeyName(){
return 'name';
}
Post.php...
public function tags(){
return $this->belongsToMany(Tag::class);
}
I get the posts for a certain tag like this...
public function show(Tag $tag){
$posts = $tag->posts;
return view('posts.index', compact('posts','tag'));
}
Then, to sort the posts into newest first I can do this in index.blade.php...
#foreach ($posts->sortByDesc('created_at') as $post)
#include('posts.post')
#endforeach
This works fine, but I'm doing the re-ordering at collection level when I'd prefer to do it at query level.
From Eloquent: Relationships I can see that I can do something like this, which also works...
$user = App\User::find(1);
foreach ($user->roles as $role) {
//
}
But, something like this does not seem to work...
public function show($tag){
$posts = \App\Tag::find($tag);
return view('posts.index', compact('posts'));
}
My question is, how can I filter/order the data at a query level when using pivot tables?

To order your collection you must change
public function tags(){
return $this->belongsToMany(Tag::class);
}
to
public function tags(){
return $this->belongsToMany(Tag::class)->orderBy('created_at');
}

Extending #leli. 1337 answer
To order content without changing the relation created.
First, keep the original relation
class User
{
public function tags
{
return $this->belongsToMany(Tag::class);
}
}
Second, during query building do the following
//say you are doing query building
$users = User::with([
'tags' => function($query) {
$query->orderBy('tags.created_at','desc');
}
])->get();
With this, you can order the content of tags data and in query level also if needed you can add more where clauses to the tags table query builder.

Related

How to filter entries via hasMany relationship in Laravel

I'm trying write an website with Laravel (current version is 5.7) and I have 3 models as: Post, User and Fav. I'm using a simple form to add posts to "favs" table which has 3 columns as; id, user_id and post_id. And I want to list posts that user added favorites bu I can't use "hasMany" method properly.
I can use variables like; $post->user->name but I can't figure it out how to use relationship with "favs" table.
Post Model
public function user() {
return $this->belongsTo('App\User');
}
public function favs() {
return $this->hasMany('App\Fav');
}
Fav Model
public function users() {
return $this->hasMany('App\User');
}
public function posts() {
return $this->hasMany('App\Post', 'post_id', 'id');
}
User Model
public function posts() {
return $this->hasMany('App\Post');
}
public function favs() {
return $this->hasMany('App\Fav');
}
Controller
public function user($id){
$favs = Fav::orderBy('post_id', 'desc')->get();
$user = User::find($id);
$posts = Post::orderBy('id', 'desc')->where('user_id', $id)->where('status', '4')->paginate(10);
return view('front.user')->with('user', $user)->with('posts', $posts)->with('favs', $favs);
}
The Fav model only has one User and Post each, so you need to use belongsTo() instead of hasMany and change the method names to singular. You can also remove the additional parameters in post() since they're the default values.
public function user() {
return $this->belongsTo('App\User');
}
public function post() {
return $this->belongsTo('App\Post');
}
Loading all Posts that a user has favorited:
$user->favs()->with('post')->get();
The with() method is used to eager load the relationship.
Now you can loop through the Favs:
#foreach($favs as $fav)
{{ $fav->post->name }}
#endforeach
I think you can change these two lines of your code to
$posts = Post::orderBy('id', 'desc')->where('user_id', $id)->where('status', '4')->paginate(10);
return view('front.user')->with('user', $user)->with('posts', $posts)->with('favs', $favs);
to
$posts = Post::where('user_id', $id)->where('status', '4')->latest()->paginate(10);
return view('front.user', compact('user', 'posts', 'favs'));
And for retrieving favorite posts of an user,
if you will change the fav table to make it a pivot table only to handle a many to many relationships between Post and User, you can get it as $user->posts, for a separate model, I think you can consider something like $user->favs and in view
In Fav model
public function user() {
return $this->belongsTo('App\User');
}
public function post() {
return $this->belongsTo('App\Post');
}
and in view
#foreach ( $user->favs as $fav )
{{ $fav->post->id }}
#endforeach
If an User, per example, have many Favs you need to use a Iteration Block, like foreach.
Example:
foreach($user->favs as $fav) {
dd($fav) // do something
}
Ps.: Be careful not to confuse hasMany and belongsToMany.

How to create a relationship between User and Posts in Laravel

I have a blog and want to include the Users Name when shown to the public.
When creating the blog I make sure to include the user_id in the blogs table
In my Blog model I have the following:
public function users()
{
return $this->belongsTo(User::class);
}
In my Users model I have:
public function blogs()
{
return $this->hasMany(Blog::class);
}
In my Blog Controller I have:
public function index(User $user)
{
$users = User::get();
$blogs= DB::table('blogs')->where('user_id', '=', $users->id)->orderBy('id', 'DESC')->paginate(6);
return view('blogs.index',compact('blogs'));
}
Then in my view:
#foreach($blogs as $blog)
<h1>{{$blog->title}}</h1>
Source:{{$blog->users->first_name}} // This does not work
Source:{{$blog->first_name}} // This does not work either
#endforeach
I thought I could do something like this to show the names:
{{ $blogs->users->first_name }} {{ $blogs->users->last_name }}
But this isn't working either...
Try this:
#foreach($blogs as $blog)
<h1>{{$blog->title}}</h1>
{{$blog->user->first_name}}
#endforeach
And on your Blog Model
public function user()
{
return $this->belongsTo(User::class);
}
In your Blog controller the variable $blog needs to be $blogs. You also have extra characters (right parenthesis) in your Blade. It should be:
#foreach($blogs as $blog)
Source: {{ $blog->user->first_name }}
...
#endforeach
Blog Model
This function replaces the old "users" function, as only one user is returned (belongsTo is a singular relationship).
class Blog extends Model
{
public function user()
{
return $this->belongsTo('App\User');
}
}
User Model
public function blogs()
{
return $this->hasMany('App\Blog');
}
Controller Function
And, as such, you can cut down your controller code, including removing the redundant elements.
public function index(User $user)
{
$blogs = Blog::where('user_id', '=', $user->id)->orderBy('created_at','desc')->paginate(6);
return view('blogs.index', compact('blogs'));
}
The way you did is called Query Builder
$blogs= DB::table('blogs')->where('user_id', '=', $users->id)->orderBy('id', 'DESC')->paginate(6);
Query Builder does not support lazy loading, cause lazy loading is only supported for the Eloquent method
$blog->users->first_name
For Eloquent way you can try this instead:
$blogs = Blog::where('user_id', $user->id)->get()
foreach($blogs as $blog){
dd($blog->user); // you will get the user detail here
}
For lazy loading have a performance issue when come to load heavy data so to prevent lazy loading can use this
$blogs = Blog::with('user')->where('user_id', $user->id)->get()
For more information can look at Eloquent Relationship Documentation
For query builder, the only way to link your user is use join, which will be something like this
$blogs = DB::table('blogs')
->join('users', 'users.id', '=', 'blogs.user_id')
->get();
foreach($blogs as $blog){
dd($blog->first_name) // user first name
}
For more information can look at Query Builder Join
BlogController.php
public function index(){
$blogs = Blog::with('user')->get();
return view('blogs.index')->with('blogs',$blogs);
}
Blog.php
public function user()
{
return $this->belongsTo('App\User');
}
User.php
public function blogs()
{
return $this->hasMany('App\Blog');
}

Laravel has and orHas not working with where

I need get categories with products, only where products exists on category, I have code:
$categories = Category::with(['children.products', 'products'])->has('children.products')->orHas('products')->whereNull('parent_id')->whereId($category->id)->paginate(15);
But whereId = $category->id not working.. Why? I get category with other categories.. but I need get only certain category, where id = categoryId
Model category:
public function children()
{
return $this->hasMany(self::class, 'parent_id');
}
public function parent()
{
return $this->belongsTo(self::class, 'parent_id')->withoutGlobalScope('active');
}
public function products()
{
return $this->hasMany(Product::class);
}
You should combine has and orHas within enclosed function, something similar to below:
$query->where(function($query) {
$query->has('children.products');
$query->orHas('products');
});
Additionally, to above, I would recommend using "join" because "has" is a lot slower.

Laravel scope on collection

I'm in Pain because of this problem.
Problem
I have categories table which contains id, slug and parent_id.
lessons table which contains category_id (the child of a Category)
I want by Eloquent scope get all lessons posted by Category parent id
example:
parent category of id=1 have children categories of ids 3, 4 and 5.
Then retrieve Lessons under the parent category of id=1, so this must return the lessons of all of the childrens.
Categories table
public function parent()
{
return $this->belongsTo(self::class, 'parent_id');
}
public function lessons()
{
return $this->hasManyThrough(
\App\Lesson::class, \App\Category::class,
'parent_id', 'category_id', 'id'
);
}
//Pointless try
// public function scopeGetParent($query, $slug)
// {
// $query->where('slug', $slug)->first()->lessons;
// }
public function children()
{
return $this->hasMany(self::class, 'parent_id');
}
Lessons.php
return Lesson::recentLessons()
->byParentCategory($args['category'])
->simplePaginate($page)
->shuffle();
how can I construct scopeByParentCategory() function??
sorry for the fuzzy question! Spent 3 hours trying :(
update: I got this idea right after posting the question but still wanna know what do you think about it and if this the best way to handle it.
public function scopeByParentCategory($query, $slug)
{
$ids = [];
foreach (Category::where('slug', $slug)->first()->children as $value)
{
$ids[] = $value->id;
}
return $query->whereIn('category_id', $ids);
}
First put this code inside of your Category model:
public function scopeOfSlug($query, $slug)
{
$query->where('slug', $slug);
}
Then in your controller
return Category::ofSlug($args['category'])
->lessons
->simplePaginate($page)
->shuffle();

Laravel combine two tables posts + tags

I have two tables one for links and second one for tags.
When user create new tag it will write to tags table new row.
Now what I want to do is to get all user links with tags. I have done this:
$data['links'] = User::find($user->id)->links()->get();
foreach ($data['links'] as $key => $value) {
$data['links'][$key]['tags'] = DB::table('tags')
->where('user_id', $user->id)
->where('link_id', $value->id)
->get();
}
It's working, but I think that there is better way to do that than this.
user model
public function links()
{
return $this->hasMany('Link');
}
link model
public function user()
{
return $this->belongsTo('User');
}
public function tags()
{
return $this->hasMany('Tag');
}
tag model
public function link()
{
return $this->belongsTo('Link');
}
So we are ready to go..
User::with('links.tags')->get()

Categories