I have a simple script in post.blade.php
{{ Auth::user() ? ($post->ratings()->where('user_id', Auth::user()->id)->exists() ? 'You rated ' . $post->userRating($post) : '') : '' }}
The 2 functions that get access in my post model are:
public function ratings() {
return $this->hasMany(Rating::class);
}
public function userRating(Post $post) {
return $this->ratings()->where([
['post_id', '=', $post->id],
['user_id', '=', Auth::user()->id]
])->first('stars')->stars / 2;
}
This is the index in my postcontroller:
public function index() {
$posts = Post::with('user')->withAvg('ratings', 'stars')->paginate(100);
return view('posts.index', [
'posts' => $posts,
]);
}
This however takes about 1 second to load each page because it has to execute that code 100 times.
If I remove the script mentioned at the top from the blade file the page loads way faster.
I made this query in raw MySQL and it loads the results significantly faster:
select `posts`.*, (select avg(`ratings`.`stars`) from `ratings` where `posts`.`id` = `ratings`.`post_id`) as `ratings_avg_stars`, (SELECT count(*) FROM ratings WHERE post_id = posts.id and user_id = 1) as rated from `posts` where `posts`.`deleted_at` is null
If I were to put that in my postcontroller I think the page would load faster, I don't know how to convert the MySQL to Eloquent, I have tried a query converter but those get stuck on the subqueries.
How can I convert the MySQL query to an Eloquent query?
I believe you are looking to Constrain Your Eager Loading and create an access mutator for Post.
You can see my fiddle here.
What this approach does, is load all the necessary data in one go, using the constrained eager load, and the access mutator will have this relationship loaded already when it appends the ratingAverage to the post.
This way you can avoid any unnecessary database calls, or method calls to recompute values you already have.
Schema:
Schema::create('users', function (Blueprint $table) {
$table->increments('id');
$table->string('name');
$table->timestamps();
});
Schema::create('posts', function (Blueprint $table) {
$table->increments('id');
$table->unsignedInteger('user_id');
$table->string('title');
$table->string('body');
$table->timestamps();
});
Schema::create('ratings', function (Blueprint $table) {
$table->increments('id');
$table->unsignedInteger('user_id');
$table->unsignedInteger('post_id');
$table->integer('stars');
$table->timestamps();
});
Models:
use \Illuminate\Database\Eloquent\Relations\Pivot;
class User extends Model
{
protected $guarded = [];
}
class Rating extends Pivot
{
protected $table = 'ratings';
protected $guarded = [];
public static function rate(User $user, Post $post, int $stars)
{
return static::create([
'user_id' => $user->id,
'post_id' => $post->id,
'stars' => $stars
]);
}
public function user()
{
return $this->belongsTo(User::class);
}
public function post()
{
return $this->belongsTo(Post::class);
}
}
class Post extends Model
{
protected $guarded = [];
public function user()
{
return $this->belongsTo(User::class);
}
public function ratings()
{
return $this->hasMany(Rating::class);
}
public function getRatingAverageAttribute()
{
return $this->ratings->sum('stars') / $this->ratings->count();
}
}
Controller:
public function index() {
$posts = Post::query()->with(['ratings' => function ($query) {
$query->where('user_id', Auth::id());
}])->get();
return view('posts.index', [
'posts' => $posts,
]);
}
And finally in blade:
#if($post->ratings->count())
{{ 'You rated ' . $post->ratingAverage }}
#endif
Related
Comment Model:
public function commentable()
{
return $this->morphTo();
}
public function comments()
{
return $this->hasMany(Comment::class , 'parent_id' , 'id');
}
public function setCommentAttribute($value)
{
$this->attributes['comment'] = str_replace(PHP_EOL , "<br>" , $value);
}
Post Model:
public function comments()
{
return $this->morphMany(Comment::class, 'commentable');
}
And Controller:
public function show_comments(Post $post)
{
$comments = $post->comments()
->where('approved' , 1)
->where('parent_id', 0)
->latest()
->with(['comments' => function($query) {
$query->where('approved' , 1)->latest();
}])->get();
dd($comments);
return view('post',compact('comments'));
}
Database table Comments:
Schema::create('comments', function (Blueprint $table) {
$table->increments('id');
$table->integer('user_id')->unsigned();
$table->integer('parent_id')->unsigned()->default(0);
$table->boolean('approved')->default(0);
$table->text('comment');
$table->integer('commentable_id')->unsigned();
$table->string('commentable_type');
$table->timestamps();
});
$dd($comments) returns #items: [] or Empty. There are database records and I can access them with another methods.
I did searching alot before asking but no luck.
I was trying to resolve the same issue for a few hours. For anyone searching for the answer :
Check if the commentable_type field in comments table has properly formatted route strings
'commentable_type' => 'App/Models/Comment', // Does not work
'commentable_type' => 'App\Models\Comment', // Works 🥳
I have a blog built with laravel.
and I want to add likes to my posts.
so I created a Like model with a likes table.
this is what i have done in my Like model
public function post(){
return $this->belongsTo(Post::class);
}
public function user(){
return $this->belongsTo(User::class);
}
in my Post and User models
public function likes(){
return $this->hasMany(Like::class);
}
and my migration file for likes table
Schema::create('likes', function (Blueprint $table) {
$table->bigIncrements('id');
$table->foreign('post_id')->references('id')->on('posts');
$table->foreign('user_id')->references('id')->on('users');
$table->boolean('value');
$table->timestamps();
});
I want to set the values in my controller on this way!
public function liker($postID, $userID, $value){
$like = new Like([
'post_id' => $postID,
'user_id' => $userID,
'value' => $value
]);
$like->save();
return redirect()->back();
}
but the view return 419 error page. (Page Expired)
and also no changes (no row) adds to my database(likes table)!
can you help me?
you dont need value on a like, if it exists, it's a "like" and you should use is as a pivot table (you already have 2 foreign IDs in it)
Schema::create('likes', function (Blueprint $table) {
$table->unsignedInteger('post_id');
$table->unsignedInteger('user_id');
$table->foreign('post_id')->references('id')->on('posts');
$table->foreign('user_id')->references('id')->on('users');
$table->tinyInteger('is_dislike')->default(0);
$table->timestamps();
});
then declare the relation between Post and User
Post
public function votedUsers(){ //or simply likes
return $this->belongsToMany(User::class, 'likes')->withPivot('is_dislike')->withTimestamps();
}
User
public function votedPosts(){
return $this->belongsToMany(Post::class, 'likes')->withPivot('is_dislike')->withTimestamps();
}
Next to create a like just do it like this
public function liker($postId, $userId, $value){
$user = User::findOrFail($userId); //or use auth()->user(); if it's the authenticated user
$user->votedPosts()->attach($postId);
return redirect()->back();
}
to Remove a like use detach($postId) instead.
For dislike
$user->votedPosts()->attach($postId, ['is_dislike' => 1]);
I have 4 tables, 1->user, 2->category, 3->comment, 4->post
I want to get the category for the related post that user already commented
SELECT kategoris.* FROM kategoris
INNER JOIN yazis on yazis.kategori_id = kategoris.id
INNER JOIN yorums on yorums.yazi_id = yazis.id
INNER JOIN users on users.id = yorums.user_id
where users.id = 1
Relations
Depending on how your models are setup, this is how the query should be with Eloquent
$category = Post::whereHas('comments', function($query) {
$query->where('user_id', auth()->user()->id);
})->first()->category;
Update:
This is how your models and table migrations should look
User has many posts and comments
public function posts()
{
return $this->hasMany(Post::class);
}
public function comments()
{
return $this->hasMany(Comment::class);
}
Category has many posts
public function posts()
{
return $this->hasMany(Post::class);
}
Post belongs to a category and a user, has many comments
public function category()
{
return $this->belongsTo(Category::class);
}
public function comments()
{
return $this->hasMany(Comment::class);
}
public function user()
{
return $this->belongsTo(User::class);
}
Posts Table Migration
Schema::create('posts', function (Blueprint $table) {
$table->bigIncrements('id');
$table->unsignedBigInteger('user_id');
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
$table->unsignedBigInteger('category_id');
$table->foreign('category_id')->references('id')->on('categories')->onDelete('cascade');
$table->timestamps();
});
Comment belongs to a post and a user
public function post()
{
return $this->belongsTo(Post::class);
}
public function user()
{
return $this->belongsTo(User::class);
}
Comments Table Migration
Schema::create('comments', function (Blueprint $table) {
$table->bigIncrements('id');
$table->unsignedBigInteger('user_id');
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
$table->unsignedBigInteger('post_id');
$table->foreign('post_id')->references('id')->on('posts')->onDelete('cascade');
$table->timestamps();
});
Let's populate some data like this...
Database Seeder
$user = factory(User::class)->create([]);
$category = Category::create([]);
$post = $user->posts()->create(['category_id' => $category->id]);
$post->comments()->create(['user_id' => $user->id]);
And get the category of the post that the authenticated user commented on with the query above...
Hope this helps :)
I have three table below:
I want to display all Related job post by category in Single jobpost. and I already have single job post page but in the same single job page I want to display related jobs in the left side. see my picture!
what is controller should be and in the Single job page (view) should be? please help?
My jobController
public function show($id, $company_id)
{
$singleJob = Job::find($id);
$company = Company::find($company_id);
$similarJob = Job::with('company')->where('category_id', $id)->get();
return view('pages.single-job')->with([
'singleJob'=> $singleJob,
'company'=> $company,
'similarJob' => $similarJob,
]);
}
My relationship
job.php
public function company(){
return $this->belongsTo(Company::class);
}
Job.php
public function category(){
return $this->belongsTo(Category::class);
}
//category.php
public function job(){
return $this->hasMany(Job::class);
}
//company.php
public function job(){
return $this->hasMany(Job::class);
}
Job table
Schema::create('jobs', function (Blueprint $table) {
$table->increments('id');
$table->integer('company_id');
$table->string('jobTitle');
$table->longText('jobDescription');
Company Table
Schema::create('company_types', function (Blueprint $table) {
$table->increments('id');
$table->integer('admin_id');
$table->string('name');
$table->timestamps();
});
Category table
Schema::create('categories', function (Blueprint $table) {
$table->increments('id');
$table->integer('user_id');
$table->string('name');
$table->timestamps();
});
You can use whereHas like this :
public function show($id, $company_id)
{
$singleJob = Job::find($id);
$company = Company::find($company_id);
$similarJobs = Job::with('company')
->whereHas('category', function ($query) use($singleJob) {
$query->where('id', $singleJob->category->id);
})
->get();
return view('pages.single-job')->with([
'singleJob'=> $singleJob,
'company'=> $company,
'similarJobs' => $similarJobs,
]);
}
And in the view you can use it like this :
#foreach ($similarJobs as $similarJob)
// Add the job partial, componnent or just your HTML here for showing the Job
#endforeach
For the question in the comment, to find jobs that have a company that belongs to a given industry :
$some_industry_type_id = 1;
$jobsOfIndustryType = Job::whereHas('company', function ($query) use($some_industry_type_id) {
$query->where('industry_type_id', $some_industry_type_id);
})
->get();
I am building a forum. I want to bring published topics on top whenever users leave a reply. For topics without replies, I want to order them by created_at column.
How do you do that?
Forum controller
public function index()
{
$categories = Category::all();
$topics = Topic::with(['comments' => function ($query) {
$query->orderBy('comments.created_at', 'desc');
}])->paginate(20);
}
Here is my topic table
Schema::create('topics', function (Blueprint $table) {
$table->increments('id');
$table->integer('category_id')->unsigned();
$table->integer('user_id')->unsigned();
$table->string('title');
$table->text('body');
$table->timestamps();
});
Here is my comment table
$table->increments('id');
$table->text('reply');
$table->integer('user_id')->unsigned();
$table->integer('topic_id')->unsigned();
$table->foreign('topic_id')->refrenced('id')->on('topics')->onDelete('cascade');
$table->timestamps();
Comments model
class Comment extends Model
{
protected $fillable = [
'reply',
'user_id',
'topic_id'
];
public function topic()
{
return $this->belongsTo('App\Topic');
}
public function user()
{
return $this->belongsTo('App\User');
}
}
Topic model
class topic extends Model
{
protected $fillable = [
'title',
'body',
'category_id'
];
public function category()
{
return $this->belongsTo('App\category');
}
public function user()
{
return $this->belongsTo('App\User');
}
public function comments()
{
return $this->hasMany('App\Comment');
}
}
still trying to figure this out. any help will be hugely appreciated!!
Try using eager load with constraint:
public function index()
{
$categories = Category::all();
$topics = Topic::with(['comments' => function ($query) {
$query->orderBy('created_at', 'desc');
}])->paginate(20);
return view('forums.index',compact('categories','topics'));
}
DB::table('topics')
->leftjoin('comments', function($join) {
$join->on('topics.id', '=', 'comments.topic_id');
})
->select(DB::raw('IF(comments.id IS NULL,0, 1) as topic_comment'))
->orderBy('topic_comment', 'DESC')
->orderBy('topics.created_at', 'DESC')
->get();
the topic_comment as 1 records you can show on top right and rest wherever you want