Okay,
So I can't figure this one out.
Eloquent model is new to me.
I'm trying to fetch comments for specific posts.
This is my post model:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Post extends Model
{
public function user()
{
return $this->belongsTo('App\User');
}
public function likes()
{
return $this->hasMany('App\Like');
}
public function comments()
{
return $this->hasMany('App\Comment');
}
}
Comment model:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Comment extends Model
{
public function user()
{
return $this->belongsTo('App\User');
}
public function post()
{
return $this->belongsTo('App\Post');
}
}
Routes:
public function getDashboard(){
$posts = Post::orderBy('created_at', 'desc')->get(); // MySQL ORDER BY
$comments = Comment::orderBy('created_at', 'desc')->get();
return view('dashboard', ['posts' => $posts], ['comments' => $comments]);
}
($comments might be unnecessary?)
View:
#foreach($posts as $post)
<article class="post" data-postid=" {{ $post->id }} ">
<p>{{ $post->body }}</p>
<div class="info">
Posted by {{$post->user->first_name}} on {{ $post->created_at }}
</div>
</article>
I tried to loop through the comments using:
#while($comments->post_id = $post->id)
<p>{{$comments->body}}</p>
#endwhile
I got an error message "Undefined property: Illuminate\Database\Eloquent\Collection::$body" even though the comments table have a column named "body".
#foreach($post->comments as $comment)
Is what you want. You can use eager loading in the query to speed this up as well:
Post::with('comments')->orderBy('created_at', 'desc')->get()
You code with create multiple queries because of $post->user->first_name. For example, if you have 70 posts, your code will create 72 query which is awful.
It's much better to use eager loading to load posts with comments and users. This code will create just 3 queries:
Post::orderBy('created_at', 'desc')
->with(['user', 'comments' => function ($q) {
$q->orderBy('created_at', 'desc');
}])->get();
And then itarate over collection:
#foreach ($posts as $post) {
<article class="post" data-postid=" {{ $post->id }} ">
....
#foreach ($post->comments as $comment) {
$comment->content;
}
}
Related
should I give id for posts? How Laravel know which comments belong to which post?
This is my controller :
public function show($id){
$posts=Post::where('id',$id)->get();
return view('user',compact('posts'));
}
This is my blade :
#foreach($posts->comments as $comment)
<div class="comment-form">
<section class="post-body">
<p>{{ $comment->description }}</p> <!-- burda ilisdim -->
</section>
</div>
#endforeach
So I built polymorphic relations about comments. but I don't know how to use it.
UPDATE
here is my Post Model
public function comments()
{
return $this->morphMany('App\Comment', 'commentable');
}
}
You need to define the relation on Post model, as like :
public function comments()
{
return $this->hasMany(Comment::class);
}
Then you can access like this :
#foreach($posts->comments as $comment) // $posts->comments will call your comments() function
{{ $comment->description }}
#endforeach
As I can see you doing something wrong. Please replace your code with below:
To Get Single post you have to replace code with given below in controller
public function show($id){
$posts=Post::where('id',$id)->first();
return view('user',compact('posts'));
}
I want to retrieve products of the selected category, as well as sub-categories and I may even need to order them according to price or date. Please let me know what changes should be made to the controller!
I am using a Many to Many Relationship for the 2 tables: Categories and Products, and a category_product to relate both.
Example
(Not all categories have sub-categories)
Gifts
Books
Toys
Boys
Girls
Phone
Samsung
Nokia
If a user clicks Phone, all products of the category 'Phone', 'Samsung' or 'Nokia' should appear!
Database
Products: id, name, price, created_at
Categories: id, name, slug, parent_id, sorting_order, created_at
category_product: id, category_id, product_id
Code:
Category Model:
class Category extends Model
{
public function products()
{
return $this->belongsToMany('App\Product');
}
public function parent()
{
return $this->belongsTo('App\Category', 'parent_id');
}
public function children()
{
return $this->hasMany('App\Category', 'parent_id');
}
}
Product Model
class Product extends Model
{
public function categories()
{
return $this->belongsToMany('App\Category');
}
}
class ProductController extends Controller {
public function index($slug, Request $request)
{
if ( ! isset($_GET['sortBy']))
{
$category = Category::where('slug', $slug)->with('products')->first();
if ( ! $category)
{
abort(404, 'Page not found');
}
}
else
{
$slug = $request->segments()[1];
$products = Category::where('slug', $slug);
switch ($request->sortBy)
{
case 'latest':
$category = $products->with(['products' => function ($q) {
$q->orderBy('created_at', 'desc');
}])->first();
break;
case 'asc':
$category = $products->with(['products' => function ($q) {
$q->orderBy('price', 'asc');
}])->first();
break;
case 'desc':
$category = $products->with(['products' => function ($q) {
$q->orderBy('price', 'desc');
}])->first();
break;
default:
$category = $products->with('products')->first();
break;
}
}
return view('products', compact('category'));
}
}
View
<form id="sortProducts" class="form-inline" method="get">
{{ csrf_field() }}
<label for="sortBy">Sort By:</label>
<select name="sortBy" id="sortBy">
<option value="latest">Latest</option>
<option value="asc">Price Low to Hight</option>
<option value="desc">Price High to Low</option>
</select>
</form>
#foreach($category->products as $product)
<div class="product">
<img border="0" src="{{Voyager::image($product->image)}}" alt="{{$product->name}}">
<div class="product-name">{{$product->name}}</div>
<div class="product-price">${{$product->price}}</div>
</div>
#endforeach
I am using Laravel Version 6.2 along with Voyager Version 1.3.
If your category depth is unlimited, you'll need a recursive relationship.
I think something like this could work:
In your Category Model:
public function nestedStructure()
{
return $this->children()->with([
'nestedStructure',
'products' => function($q) {
$q->orderBy('created_at', 'desc');
}
);
}
Okay so first you need to modify your Category Model to get all children along with parent
Category Model
class Category extends Model
{
public function products()
{
return $this->belongsToMany('App\Product');
}
public function parent()
{
return $this->belongsTo('App\Category', 'parent_id');
}
public function children()
{
return $this->hasMany('App\Category', 'parent_id');
}
// recursive, loads all descendants
public function recursiveChildren()
{
return $this->children()->with('recursiveChildren');
}
}
Now according to given many-to-many relation a single Product might belong to multiple categories but we wont like to have same product show up again and again. So a possible fix for your Controller can be
class ProductController extends Controller {
public function index($slug, Request $request)
{
$categories = Category::where('slug', $slug)->with('recursiveChildren')->whereNull('parent')->get();
if($categories->count() == 0) abort(404, 'Page not found');
$products = collect([]); // its a helper method to make collections
foreach($categories as $category) {
$category_products = $category->products;
foreach($category_products as $product) {
if(!$products->contains($product)) $products->push($product);
}
}
if($request->input('soryBy')) {
switch ($request->sortBy)
{
case 'latest':
$products = $products->sortBy('created_at');
break;
case 'asc':
$products = $products->sortBy('price');
break;
case 'desc':
$products = $products->sortByDesc('price');
break;
default:
$products;
break;
}
}
return view('products', compact('products'));
}
}
Now lets modify the view a little
<form id="sortProducts" class="form-inline" method="get">
{{ csrf_field() }}
<label for="sortBy">Sort By:</label>
<select name="sortBy" id="sortBy">
<option value="latest">Latest</option>
<option value="asc">Price Low to Hight</option>
<option value="desc">Price High to Low</option>
</select>
</form>
#foreach($products as $product)
<div class="product">
<img border="0" src="{{Voyager::image($product->image)}}" alt="{{$product->name}}">
<div class="product-name">{{$product->name}}</div>
<div class="product-price">${{$product->price}}</div>
</div>
#endforeach
There are at least two solutions.
Solution 1 (Pure Laravel):
Add these two methods to your Category model:
public function descendants()
{
$collection = new \Illuminate\Support\Collection();
foreach ($this->children as $child) {
$collection->add($child);
$collection = $collection->merge($child->descendants());
}
return $collection;
}
public function getRouteKeyName()
{
return 'slug';
}
And use it in your ProductController controller like so:
class ProductController extends Controller
{
public function index(Request $request, Category $category)
{
$categories = $category->descendants()->add($category)->pluck('id');
$products = DB::table('category_product AS cp')
->join('products', 'cp.product_id', '=', 'products.id')
->select('products.*')
->whereIn('cp.category_id', $categories)
->get();
return view('products', compact('category', 'products'));
}
}
You can then output them in your view file:
#forelse($products as $product)
<div class="product">
<img border="0" src="{{ Voyager::image($product->image) }}" alt="{{ $product->name }}">
<div class="product-name">{{ $product->name }}</div>
<div class="product-price">${{ $product->price }}</div>
</div>
#empty
<div class="product">
There are no products in this category.
</div>
#endforelse
Solution 2 (Using a package):
First of all, install the package:
composer require kalnoy/nestedset
Replace parent_id column with $table->nestedSet(); in your categories table and related migration file:
Schema::create('categories', function (Blueprint $table) {
$table->bigIncrements('id');
$table->string('name');
$table->string('slug')->unique();
$table->nestedSet();
$table->timestamps();
});
Then, update your Category model like so:
use Illuminate\Database\Eloquent\Model;
use Kalnoy\Nestedset\NodeTrait;
class Category extends Model
{
use NodeTrait;
protected $fillable = [
'name',
'slug',
];
public function products()
{
return $this->belongsToMany('App\Product');
}
public function parent()
{
return $this->belongsTo(self::class, 'parent_id');
}
public function children()
{
return $this->hasMany(self::class, 'parent_id');
}
public function getRouteKeyName()
{
return 'slug';
}
}
You can now use it in your controller:
class ProductController extends Controller
{
public function index(Request $request, Category $category)
{
$categories = Category::descendantsAndSelf($category->id)->pluck('id');
$products = DB::table('category_product AS cp')
->join('products', 'cp.product_id', '=', 'products.id')
->select('products.*')
->whereIn('cp.category_id', $categories)
->get();
return view('products', compact('category', 'products'));
}
}
You can output as shown in Solution 1.
Please note that I assumed you use {category} key in your route definition. (See Route Model Binding) For example:
Route::get('/products/category/{category}', 'ProductController#index');
Read the documentation first to create, update and delete an item in a nested set (categories).
I'm trying to code a simple social media page. The show view should display a post with all the comments for that particular post listed below.
I've tried a couple different approaches with no luck, this is the approach I feel like I might be closet to success with, any suggestions?
I can provide other extracts of code if you think the problem lies elsewhere but I think my problem lies within these 4 files.
web.php
Route::resource('post', 'PostController');
Route::resource('post', 'CommentController');
show.blade.php
<h1>{{$post->title}}</h1>
<p>Description: {{$post->description}}</p>
<h3>Comments</h3>
<ul>
#foreach ($comments as $comment)
<li>
User: {{$comments->user_name}} <br>
Comment: {{$comments->comment}} <br>
</li><br>
#endforeach
</ul>
PostController.php
public function show($id)
{
$post= Post::find($id);
return view('post.show')->with('post', $post);
}
CommentController.php
public function show($id)
{
$comments= Comment::find($id);
return view('post.show')->with('comments', $comments);
}
EDIT 1
Post.php
class Post extends Model
{
protected $fillable = ['title', 'description'];
function user() {
return $this->belongsTo('App\User');
}
function comment() {
return $this->hasMany('App\Comment');
}
}
firstly set the relationship between Post and Comment Model
In Post Model
class Post extends Model
{
public function comments(){
return $this->hasMany(Comment::class);
}
}
In Comment Model
class Comment extends Model
{
public function post(){
return $this->belongsTo(Post::class);
}
}
Post Controller
public function show($id)
{
$post= Post::find($id);
return view('post.show')->with('post', $post);
}
Write in the blade file
<h1>{{$post->title}}</h1>
<p>Description: {{$post->description}}</p>
<h3>Comments</h3>
<ul>
#foreach ($post->comments as $comment) //get all the comments related to this post
<li>
User: {{$comment->user_name}} <br>
Comment: {{$comment->comment}} <br>
</li><br>
#endforeach
</ul>
You can get both data by make relations between your models
I think in your two models the relation would be like that,
Post Model
namespace App\Post;
use Illuminate\Database\Eloquent\Model;
class Post extends Model
{
public function comments(){
return $this->hasMany(Comment::class);
}
}
Comment Model:
namespace App\Comment;
use Illuminate\Database\Eloquent\Model;
class Comment extends Model
{
public function post(){
return $this->belongsTo(Post::class);
}
}
so if you want to return the data to view then,
PostController.php
public function show($id)
{
$post= Post::with('comments')->find($id);
$data = [
'post' => $post,
'comments' => $post->comments,
];
return view('post.show', $data);
}
CommentController.php
public function show($id)
{
$comments= Comment::with('post')->find($id);
$data = [
'post' => $comments->post,
'comments' => $comments,
];
return view('post.show' , $data);
}
For more details: https://laravel.com/docs/5.7/eloquent
I am using eloquent relationships for post and tags through post_tags. What i'm trying to do is make sections of posts in my front-end view with specific tags. Like if i have a section 1 it should have all posts of tag "new", section 2 should have all posts of tag "old" etc. And i want all of this to happen in a same view.
Post Model
public function tags()
{
return $this->belongsToMany('App\Tag', 'post_tag');
}
Tag Model
public function posts()
{
return $this->belongsToMany('App\Post', 'post_tag');
}
Controller
public function index()
{
$posts = Post::all();
return view('frontend.index')->withPosts($posts);
}
Please help me out:)
You can get all the tags with loading the posts :
public function index()
{
$tags = Tag::with('posts')->get();
return view('frontend.index')->withTags($tags);
}
In the view you can do something like this :
#foreach ($tags as $tag)
<h2>Tag : {{ $tag->name }}</h2>
#foreach ($tag->posts as $post)
<p>Post : {{ $post->title }}</p>
#endforeach
#endforeach
For geting the posts where you have a tag :
public function index()
{
$tagName = 'new';
$posts = Post::whereHas('tags', function ($q) use($tagName) {
$q->where('name', $tagName);
})->get();
return view('frontend.index')->withTagName($tagName)->withPosts($posts);
}
And in the view you can do this :
<h2>Tag : {{ $tagName }}</h2>
#foreach ($posts as $post)
<p>Post : {{ $post->title }}</p>
#endforeach
If you want to do queries from view you can do it like this (but it's not a god practice because the view is just for viewing the content not geting it from database) :
<?php foreach ((\App\Post::whereHas('tags', function ($q) use($tagName) {
$q->where('name', $tagName);
})->get()) as $post) { ?>
<p>Post : {{ $post->title }}</p>
<?php } ?>
To get all posts with tags with name new, do this:
Post::whereHas('tags', function ($q) {
$q->where('name', 'new');
})->get();
It is solution not perfect answer
You can handle this situation in two ways
1- send two objects from controller to view seprate one for only new tag and second without new tag
2- you can handle this situation in front-end by using conditions
#if($user->tag == "new")
{$user->show}
#endif
Edit3: Could a reason be because both controllers are leading to the same page?
Edit2: Still not working after the answers I got.
Edit: Error one is solved, now I'm getting:
Undefined variable: project (View:
/var/www/resources/views/pages/showProject.blade.php)
Can this be because both variables are leading to the same page? The projects variable was working perfectly before the comment system.
public function index()
{
$projects = Project::all();
return view('pages.projects', compact('projects'));
}
Project variable declare.
I'm trying to get my comments from my database to show on a specific 'project page' in the laravel 5 project I'm working on. The idea is that the user can add art projects and other users can comment on them, but whenever I try to visit the page I get
Undefined variable: comments (View:
/var/www/resources/views/pages/showProject.blade.php)
This is my controller
public function index()
{
$comments = Comment::all();
return view('pages.showProject', compact('comments'));
}
public function store()
{
$input = Request::all();
$comment = new Comment;
$comment->body = $input['body'];
$comment->project_id = $input['project_id'];
$comment->user_id = Auth::user()->id;
$comment->save();
return redirect('projects/'.$input['project_id']);
}
These are my routes
// add comment
Route::post('projects/{id}','CommentController#store');
// show comments
Route::post('projects/{id}','CommentController#index');
And my view
#if (Auth::check())
<article> <!--Add comment -->
<br/>
{!! Form::open() !!}
{!! form::text('body', null, ['class' => 'form-control']) !!}
<br/>
{!! Form::Submit('Post Comment', ['class' => 'btn btn-primary form-control']) !!}
{!! Form::hidden('project_id', $project->id) !!}
{!! Form::close() !!}
<br/>
</article>
<article>
#foreach ($comments as $comment)
<article>
<p>Body: {{ $comment->body }}</p>
<p>Author: {{ $comment->user->name }}</p>
</article>
#endforeach
</article>
#else
<p>Please log in to comment</p>
#endif
The Model
class Comment extends Model
{
//comments table in database
protected $guarded = [];
// user who has commented
public function author()
{
return $this->belongsTo('App\User','user_id');
}
// returns post of any comment
public function post()
{
return $this->belongsTo('App\Project','project_id');
}
public function comments()
{
return $this->hasMany('App\Comment');
}
public $timestamps = false;
}
Is there any way I can solve this?
Thanks in advance
First, you need to make sure that you are aliasing your 'Comment' model in your controller. This is done with the use statement.
use App\Comment;
class CommentController extends Controller
{
public function index()
{
$comments = Comment::all();
return view('pages.showProject', compact('comments'));
}
}
Second, you will need to change your route for showing comments from a POST request to a GET request. At the moment you are making identical routes and furthermore GET is the correct request type for retrieving data.
Route::get('projects/{id}','CommentController#index');
Third, you are referencing a $project variable in your view, but never passing it in from the controller. That needs to reference something.
I think your relationship is wrong. Try this:
Comment model:
class Comment extends Model
{
public function user()
{
return $this->belongsTo('App\User');
}
}
User model:
class User extends Model
{
public function comments()
{
return $this->hasMany('App\Comment');
}
}
In the view you can use:
#foreach($comments as $comment)
<p>{{ $comment->user->name }}</p>
#endforeach
I needed to empty the show in the commentsController and only use it for saving the comments. For showing them I made an extra function in my projectsController. It ended up being this;
public function show($id)
{
$project = Project::findOrFail($id)->load("User");
$input = Request::all();
//-----------------------DB-----------------------------//
$project_comments = DB::table('comments')
->select('body', 'name')
->where('project_id', '=', $id)
->join('users', 'users.id', '=', 'user_id')
->get();
//-----------------------DB-----------------------------//
return view('pages.showProject', ['project' => Project::findOrFail($id), 'comments' => $project_comments]);
}