How to get 5 latest post for each genre Laravel API? - php

I'm building an application (my course project) with Laravel 5.4 as API and Angular 5. My project is a music blog. I need to fulfill requirements such as to have categories and subcategories.
So, I have genres, bands and posts. For my home page I want to display 5 latest posts for each category.
My tables
genres(id, name, slug);
bands(id, name, slug, genre_id);
posts(id, title, content, band_id, slug, created_at, updated_at).
My relationships
Genre.php
protected $fillable = ['name', 'slug'];
public function bands()
{
return $this->hasMany('App\Models\Band');
}
public function posts()
{
return $this->hasManyThrough('App\Models\Post', 'App\Models\Band');
}
Band.php
protected $fillable = ['name','slug','genre_id'];
public function genre()
{
return $this->belongsTo('App\Models\Genre');
}
public function posts()
{
return $this->hasMany('App\Models\Post');
}
Post.php
protected $fillable = ['title','content','image','band_id','slug'];
public function band()
{
return $this->belongsTo('App\Models\Band');
}
In my HomeController I've tried:
$latest5PostsPerGenre = Genre::with([
'posts' => function($query) {
$query->take(5)
->orderBy('id', 'desc')
->get(['posts.id', 'title']);
}])->orderBy('name')
->get();
But it limits the total number of posts to 5 I got for all genres. So, some genres don't have posts at all.
$latest5PostsPerGenre = Genre::with('latest5Posts')
->orderBy('name')
->get();
with such methods in Genre model:
public function latest5Posts()
{
return $this->hasManyThrough('App\Models\Post', 'App\Models\Band')
->orderBy('id', 'desc')
->take(5)
->get();
}
or
public function latest5Posts()
{
return $this->posts()->latest()->take(5)->get();
}
But I got BadMethodCallException Method addEagerConstraints does not exist.
I even tried something like this:
$genres = Genre::with('posts')->orderBy('name')->get();
$latest5PostsPerGenre = [];
foreach ($genres as $genre) {
$genrePosts['posts'] = [];
$posts = $genre->posts()->orderBy('id', 'desc')->take(5)->get();
foreach ($posts as $post) {
$singePost = [];
$singePost['id'] = $post->id;
$singePost['title'] = $post->title;
$singePost['bandName'] = $post->band->name;
array_push($genrePosts['posts'], $singePost);
}
array_push($latest5PostsPerGenre[$genre->name], $genrePosts);
}
or like this
$genres = Genre::get();
foreach ($genres as $genre) {
$post[$genre->name] = $genre->posts()
->take(5)
->orderBy('id', 'desc')
->get();
}
But I understand it performs a lot of queries to db and is not right.
I tried to create method scopeNPerGroup in Model.php according to the link https://softonsofa.com/tweaking-eloquent-relations-how-to-get-n-related-models-per-parent/, but it gives be a bunch of sql errors.
I was thinking about some complex, nested sql query with Eloquent, but don't clearly understand how to write it.
As a result, I'm expecting to have each genre name with 5 latest posts containing id, title and band name for each post in order to write the link for each post in my Angular frontend like rnmblog.com/api/{genre-slug}/{band-slug}/{post-id} or {post-slug} to get single post.

For my home page I want to display 5 latest posts for each category.
Your first attempt already does this, isn't it? Just improved it a bit
$latest5PostsPerGenre = Genre::with([
'posts' => function($query) {
$query->with('bands')
->take(5)
->orderBy('id', 'desc')
->get();
}])
->orderBy('name')
->get();

Related

Display user name from another table Laravel 8

Hellow guys,
so I have 2 tables
is Listings
is Users
in Listings I have some columns, one is user_id, and is related to users tables.
I want to display the user name related to the user table.
in the index blade, I use some tags.
But when I use ->rightjoin("users", "users.id", "=", "listings.user_id") it's works but,
the join broke my tags make them default, same with others posts.
public function index(Request $request)
{
$listings = Listing::where('is_active', true)->with('tags')
//->rightjoin("users", "users.id", "=", "listings.user_id") //don't show tags of the posts
->orderBy('listings.created_at', 'desc')
->get();
//check if posts ar listing by last date or something
$tags = Tag::orderBy('name') // variable displayed in view
->get();
You could just use the with method to get the related user like this
public function index(Request $request) {
$listings = Listing::where('is_active', true)->with(['user', 'tags'])
->orderBy('listings.created_at', 'desc')
->get();
}
but make sure that you add to your Listing model the correct relation like this
Listing Model
public function user() {
return $this->belongsTo(User::class);
}
I recommend this code for controller:
public function index(Request $request) {
return $listings = Listing::query()
->where('is_active', true)->with(['user', 'tags'])
->orderBy('listings.created_at', 'desc')
->get();
}
In the models section, I suggest you put this code: ‌
public function user() {
return $this->belongsTo(User::class);
}
I suggest you fill in the fields foreignKey, ownerkey in relation: as in the following example:
public function user()
{
return $this->belongsTo(User::class, 'user_id', 'id');
}

Laravel how to loop through array in controller and combine relationship

In my laravel-application I have a blogs- and an author-table. On the index page, where you see all published blog posts, I want the authors name to appear. So I tried to do this:
public function index()
{
$blogs = Blog::where("publishes_on", "<=", Carbon::now())
->orderBy('publishes_on', 'desc')
->published()
->get();
foreach ($blogs as $blog) {
$author = Author::where('id', $blog->author_id)->get();
}
return view('app.blog.index', compact('blogs', 'author'));
}
For some reason I do not know, this gives me the last added author to my application and on each post, the name of that author is displayed on all posts.
What am I doing wrong here?
In Blog model add author relation
public function author()
{
return $this->belongsTo(Author::class);
}
In controller
$blogs = Blog::where("publishes_on", "<=", Carbon::now())
->orderBy('publishes_on', 'desc')
->published()
->with('author:id,name')
->get();
In view you can use
#foreach($blogs as $blog)
// blog related data
Author: {{ $blog->author->name ?? '' }}
#endforeach
No need for the foreach loop
Blog::with('author')->where( [...]`
In your view
$blog->author->name
Make sure you define author() as a relationship on the Blog model:
https://laravel.com/docs/master/eloquent-relationships
e.g.
class Blog {
function author(){
return $this->belongsTo(Author::class);
}
}

How to get relationship counts without loading objects in laravel

I have a model customer and it has many projects. I want to find projects count without including its object.
Customer model includes:
public function numberOfProjects()
{
return $this->hasMany(Project::class)->count();
}
Query in my controller:
$customers = Customer::where(['is_active'=>1])
->with(['customerContactInformation'=> function ($query) {
$query->where('is_active',1);
}, 'numberOfProjects'])
->skip($skip)->take(10)
->get();
Its giving me error:Call to a member function addEagerConstraints() on integer
Try this
Customer Model
public function numberOfProjects()
{
return $this->hasMany(Project::class);
}
Controller
$customers = Customer::where(['is_active'=>1])
->with(['customerContactInformation'=> function ($query) {
$query->where('is_active',1);
}])
->withCount('numberOfProjects') //you can get count using this
->skip($skip)
->take(10)
->get();
That should be work
$customers = Customer::withCount('numberOfProjects')->get();
WithCount on the particular status
$customers = Customer::withCount([
'numberOfProjects',
'numberOfProjects as approved_count' => function ($query) {
$query->where('approved', true);
}
])
->get();
class Tutorial extends Model
{
function chapters()
{
return $this->hasMany('App\Chapter');
}
function videos()
{
return $this->hasManyThrough('App\Video', 'App\Chapter');
}
}
And then you can do:
Tutorial::withCount(['chapters', 'videos'])
Counting Related Models
If you want to count the number of results from a relationship without actually loading them you may use the withCount method, which will place a {relation}_count column on your resulting models. For example:
$posts = App\Post::withCount('comments')->get();
foreach ($posts as $post) {
echo $post->comments_count;
}
You may add the "counts" for multiple relations as well as add constraints to the queries:
$posts = App\Post::withCount(['votes', 'comments' => function ($query) {
$query->where('content', 'like', 'foo%');
}])->get();
echo $posts[0]->votes_count;
echo $posts[0]->comments_count;
You may also alias the relationship count result, allowing multiple counts on the same relationship:
$posts = App\Post::withCount([
'comments',
'comments as pending_comments_count' => function ($query) {
$query->where('approved', false);
}
])->get();
echo $posts[0]->comments_count;
echo $posts[0]->pending_comments_count;
If you're combining withCount with a select statement, ensure that you call withCount after the select method:
$posts = App\Post::select(['title', 'body'])->withCount('comments');
echo $posts[0]->title;
echo $posts[0]->body;
echo $posts[0]->comments_count;

Make 'Related To' section based on same Category in laravel

I have a website where i post recipes. each one of them has a category, and i want to display 2-3 more posts with the same category of that post.
how can i build a query to display it? i have a Post Model and Category model that has a belongsToMany relation between the two, and it fills a pivot table, that i bind those categories to a post.
This is the function in my BController, this is the function that passes data to the views where users can access and view.
public function slug(Request $request, $slug)
{
if (Auth::check()) {
$fav = DB::table('post_user')->whereUserId(Auth::id())->pluck('post_id')->all();
}
/*get search query from slug page*/
$query=$request->get('q');
/*display the results of the search*/
if ($query) {
$posts = $query ? Post::search($query)
->orderBy('id','desc')
->paginate(7) : Post::all();
return view('home', compact('posts','fav'));
}
/*the $url veriable is for share buttons*/
else {
$url = $request->url();
$post = Post::where('slug', '=', $slug)->first();
return view('b.s', compact('post','url'));
}
}
this is my Post model:
public function categories(){
return $this->belongsToMany('App\Category');
}
this is in Category model:
public function posts(){
return $this->belongsToMany('App\Post');
}
The pivot table is like so:
$table->increments('id');
$table->integer('post_id')->unsigned();
$table->foreign('post_id')->references('id')
->on('posts')->onDelete('cascade');
$table->integer('category_id')->unsigned()->nullable();
$table->foreign('category_id')->references('id')
->on('categories')->onDelete('cascade');
You can use whereHas to add constraints on related table as:
// get the post usnig slug
$post = Post::where('slug', '=', $slug)->first();
// get the related categories id of the $post
$related_category_ids = $post->categories()->pluck('categories.id');
// get the related post of the categories $related_category_ids
$related_posts = Post::whereHas('categories', function ($q) use($related_category_ids) {
$q->whereIn('category_id', $related_category_ids)
})
->where('id', '<>', $post->id)
->take(3)
->get();
Update
Pass the $related_posts to your view and use it as:
#foreach ($related_posts as $related_post)
<li>{{ related_post->title }}</li>
#endforeach
On possible solution is to include a piece of code to get the required categories.
If I correctly understand your model, you can have several categories. So, we need to take all Categories of your post, and keep only the id ; and we have to exclude the Post ID of the current object :)
$post = Post::where('slug', '=', $slug)->first();
// get Category IDs. There are others ways to do it.
$categoriesId = [];
foreach( $post->categories as $category ) {
$categoriesId[] = $cateogyr->id;
}
// get similar posts
$others = Post::
whereIn('categories', $categoriesId)
->where('id', '!=', $post->id)
->take(3)
->get();
In your case, you have a pivot table:
$others = Post::
with(array('YourPivot' => function($query) use($categoriesId)
{
whereIn('categories', $categoriesId)
})
->where('id', '!=', $post->id)
->take(3)
->get();

Laravel 5.2 Search all records but return only parent elements' data

I made a search function that searches name and content fields of all posts in the database. Some of these posts are in relation (parents and children). I have a problem as if my search returns a hit in a child post, I would like to return only parent data (to minimise the load).
My Post model:
public function children()
{
return $this->hasMany('App\Post','parent_post_id','id');
}
public function children_count()
{
return $this->children()
->selectRaw('parent_post_id, count(*) as answers')
->groupBy('parent_post_id');
}
public function parents()
{
return $this->hasOne('App\Post','id','paren_post_id');
}
public function author()
{
return $this->hasOne('App\User', 'id', 'user_id');
}
My search query:
$posts = Post::with('children')
->with('children_count')
->with('author')
->where('name','like','%'.$search_term.'%')
->orWhere('content','like','%'.$search_term.'%')
->orderBy('created_at','desc')
->groupBy('id')
->paginate(10);
Example: Post#1 has two children, Post#2 and Post#3. My search finds data in Post#3, now I would like it to return data of Post#1.
EDIT:
I see that I need to explain more what exactly would I like to achieve.
Posts table structure:
id
name
slug
content
user_id
parent_post_id
updated_at
created_at
I am searching name and content fields of each post (author (user_id) is irrelevant at this point). When search finds a hit in child post (has parent_post_id set to parent's id), I only want to get parent's data (id, slug, name, content etc.)
I was able to achieve this with:
$posts = DB::table('posts as a')
->leftJoin('posts as b', 'b.parent_post_id', '=', 'a.id')
->where('a.name','like','%'.$search_term.'%')
->orWhere('a.content','like','%'.$search_term.'%')
->orWhere('b.name','like','%'.$search_term.'%')
->orWhere('b.content','like','%'.$search_term.'%')
->select('a.*')->orderBy('a.created_at','desc')->paginate(10)
But was then unable to successfully count all children that returned parent has. This might also give some ideas how to wrap this Laravel way
Can anyone point me in the right direction?
class Post extends Model{
public function children()
{
return $this->hasMany(Post::class, 'parent_post_id', 'id');
}
public function children_count()
{
return $this->children()
->selectRaw('parent_post_id, count(*) as answers')
->groupBy('parent_post_id');
}
public function parents()
{
return $this->hasOne(Post::class, 'id', 'parent_post_id');
}
public function author()
{
return $this->hasOne(User::class, 'id', 'user_id');
}
}
$posts = Post::with(['children_count', 'author', 'children'])
->whereHas('author', function ($query) use ($search_term) {
$query->where('name', 'like', '%' . $search_term . '%');
})
->orWhere('content','like','%' . $search_term . '%')
->orderBy('created_at', 'desc')
->paginate(10);

Categories