getting data from a pivot table in laravel - php

I have 3 tables :post , tag , tag_post.
I save post_id in post / tag_id in tag / and both of them in tag_post.
how can I show every post's tags ? how can I select data from tag_post table?
it's my Post model :
public function tag()
{
return $this->belongsToMany('Tag','tag_post');
}
and it's my Tag model :
public function post()
{
return $this->belongsToMany('Post','tag_post');
}
and it's my controller :
$posts=Post::orderBy('id','DESC')->paginate(5);
///but I dont know how can i show each post's tags under it
thanks for your time.

A few things here (I will keep it simple so no orderBy or anything else, also I assume you rename relations to plural: tags() and posts() to make it easier to read and work with):
$posts = Post::paginate(5); // returns a Collection of Post models, 1 db query
foreach ($posts as $post) {
$post->tags; // Collection of Tag models, fetched from db for each $post
}
This means 5+1 queries. It doesn't scale at all of course, so we need http://laravel.com/docs/eloquent#eager-loading
Which leads us to:
$posts = Post::with('tags')->paginate(5); // returns a Collection of Post models
// runs 1 query for posts and 1 query for all the tags
foreach ($posts as $post) {
$post->tags; // Collection of Tag models, no more db queries
}
So to list all the tags you could do this:
#foreach ($posts as $post)
<tr>
<td>{{ $post->title }}</td>
<td>
#foreach ($post->tags as $tag)
{{ $tag->name }} // or whatever it is that you want to print of the tag
#endforeach
</td>
</tr>
#endforeach

If you need to get the tags from each post you need a foreach loop.
foreach ($posts as $post)
{
var_dump($post->tags); // your individual post's tags will be here
}
Also, as much as I don't like to poke my nose around, it would be better if you follow the conventions in the framework itself. (i.e. use plurals form in many-to-many relationship)
Post Model
public function tags() // <-- note the plurals
{
$this->belongsToMany('Tag', 'tag_post');
}
Tag Model
public function posts() // <-- note the plurals
{
$this->belongsToMany('Post', 'tag_post');
}
If you need to get data from the tag_post table, check out the documentation on working with pivot table.
http://laravel.com/docs/eloquent#working-with-pivot-tables

Related

Laravel - getting relation in loop - best practices

Just an example:
let's say I have Post model, and the Comment model. Post, of course, have Comments, one-to-many relation.
I have to display list of posts with comments below it.
I'll get my posts in the controller:
$posts = Post::get(), I'll pass it to the blade view and then I'll loop through it
#foreach($posts as $post)
{{ $post->title }}
{{ $post->comments }}
#endforeach
where $post->comments is some relation
public function comments()
{
return $this->hasMany(Comment::class);
}
As we know, that query will be executed many times.
Now my question: how we should optimize it?
Return Cache::remember in the getter?
Get (somehow?) those comments, when getting the posts in one query? Something like join query? I know that I can write that kind of query, but I'm talking about Eloquent's query builder. And then how get the comments within the loop? Wouldn't {{ $post->comments }} call the relation again instead of getting stored data?
Different solution?
You can do $posts = Post::with('comments')->get() to eager load the comments with the post. Read more about it in the documentation: https://laravel.com/docs/5.7/eloquent-relationships#eager-loading
Also, to display the comments you would want to add another foreach loop. It would look something like this:
#foreach($posts as $post)
{{ $post->title }}
#foreach($post->comments as $comment)
{{ $comment->title }}
#endforeach
#endforeach
You’ve probably cached some model data in the controller before, but I am going to show you a Laravel model caching technique that’s a little more granular using Active Record models
Note that we could also use the Cache::rememberForever() method and rely on our caching mechanism’s garbage collection to remove stale keys. I’ve set a timer so that the cache will be hit most of the time, with a fresh cache every fifteen minutes.
The cacheKey() method needs to make the model unique, and invalidate the cache when the model is updated. Here’s my cacheKey implementation:
public function cacheKey()
{
return sprintf(
"%s/%s-%s",
$this->getTable(),
$this->getKey(),
$this->updated_at->timestamp
);
}
public function comments()
{
return $this->hasMany(Comment::class);
}
public function getCachedCommentsCountAttribute()
{
return Cache::remember($this->cacheKey() . ':comments_count', 15, function () {
return $this->comments->count();
});
}
yes u can do like that in controller
$minutes = 60;
$posts = Cache::remember('posts', $minutes, function () {
return Post::with('comments')->get()
});
in blade u can get like that
#foreach($posts as $post)
{{ $post->title }}
#foreach($post->comments as $comment)
{{ $comment->title }}
#endforeach
#endforeach
for more information read this article

A correct query to get data from 3 tables and show it as a single element in laravel?

Suppose I have 3 tables, posts, post_images, and post_links.
post.id is a foreign key in both post_images and post_links.
Each post have multiple images.
I need a data which contains post, its images and its links as single element/array item. If there are 3 posts, I need 3 arrays with each array containing the posts images and links.
My code so far,
$data = DB::table('posts')
->join('post_images','posts.id' ,'=', 'post_images.post_id')
->join('post_links','posts.id' ,'=', 'post_links.post_id')
->select('posts.*')
->get();
with the above query I am getting all the records joined, If i have 3 records with 3 images each, I am getting 9 records, I just need 3 posts with its data as its sub arrays.
Any suggestion?
Here is the PostImage model
class PostImage extends Model
{
public function post() {
return $this->belongsTo(Post::class);
}
}
Here is the PostLink model
class PostLink extends Model
{
public function post() {
return $this->belongsTo(Post::class);
}
}
Here is the Post model
class Post extends Model
{
public function links() {
return $this->hasMany(PostLink::class);
}
public function images() {
return $this->hasMany(PostImage::class);
}
}
In the view you can reach everything you need.
#foreach ($posts as $post)
{$post->title} <br>
#foreach ($post->links as $link)
{$link->url} <br>
#endforeach
#foreach ($post->images as $image)
{$image->src} <br>
#endforeach
#endforeach
And if you want use less queries you could use eager loading to fetch all this data the first time. Eager Loading Laravel
Should look something like this
$posts = Post::with('images','links')->get();
if you already have relation in model you just have to use with method like
$data = PostModel::with('post_images','post_links')->get();
make it dd($data) and look at this. hope it will work.
References: https://laravel.com/docs/5.4/eloquent-relationships#eager-loading

Combining query results in laravel

On my website, users can upload images and attach tags to those images.
I've got an images table,a tag table and an images_tag pivot table.
Images can have many tags, and tags can belong to many images.
I want to be able to generate a list of all the tags a user has used in his/her images.
$imageIDs = Images::where('created_by', Auth::user()->id)->lists('id');
So this would create a list of all the image IDs that a user has upload.
What I want is essentially "foreach $imageIDs, check the images_tag table and for every match go to the tags table and get me back the tagname value."
But I have no idea how I'd do that.
Maybe a foreach then use the merge method on all the results? Any help would be appreciated!
You need to use whereHas() to check the relationship:
$userTags = Tags::whereHas('images', function($q) {
$q->where('created_by', auth()->user()->id);
})->get();
Then just pass this data to a view:
return view('some.view', compact('userTags'));
And iterate over tags in a view:
#foreach ($userTags as $tag)
{{ $tag->name }}
#endforeach
What you could do is this.
class Tag extends Model
{
public function images()
{
return $this->belongsToMany(Image::class);
}
}
class SomeController
{
public function someMethod()
{
$tags = Tag::with(['images' => function ($image) {
return $image->where('created_by', Auth::user()->id);
}])->select('id', 'tagname')->get();
// these are your $tags
}
}
You should not use a query inside foreach(). Then it would result N+1 problem. What you instead do is eager loading using with() statement.

Laravel 5 - Count the number of posts that are assigned to each category in a blog

I was wondering what the cleanest way was to count the number of posts that are connected to a category in my blog.
Here is how the table relationship is set up.
What I have is a hasMany relationship from the Category to the Post models like this:
In Categories Model
public function blog_posts()
{
return $this->hasMany('App\Http\Models\Blog_Post', 'category_id');
}
And in the Blog_Post Model
public function blog_categories()
{
return $this->belongsTo('App\Http\Models\BlogCategories', 'category_id');
}
In effect all I want to do is be able to return to my view the total number of posts that each category has as shown below. Where x is the number of posts within each category.
cat1 (x)
cat2 (x)
cat3 (x)
It's not hard to count I know however as I only want a count I do not want to also retrieve the records as they are not required and I also do not want to create more queries than is necessary.
I have not completed the view as yet but probably a start would be to pass through the categories in a loop to display each and add the count at the same time?
#foreach ($categories as $category)
{!! $category->name !!} - {!! Count of posts here !!}
#endforeach
Hopefully that is clear(ish)!
Eager load the relation in your controller:
public function index()
{
$categories = Category::with('blog_posts')->get();
return view('categories.index', compact('categories'));
}
You can then use the count() method on the blog_posts relation when looping over categories in your view:
#foreach ($categories as $category)
<li>{{ $category->name }} ({{ $category->blog_posts->count() }})</li>
#endforeach
EDIT: Since Laravel 5.3, you can use withCount() to load counts of relations, i.e.
$categories = Category::withCount('blog_posts')->get();
This will make the count available via a property:
foreach ($categories as $category) {
$blog_posts_count = $category->blog_posts_count;
}
The nicest way to do it with eager loading support I know is to create a separate relation with the post count. Check this out:
public function blog_posts_count() {
return $this->hasOne('App\Http\Models\Blog_Post', 'category_id')
->selectRaw('category_id, count(*) as aggregate')
->groupBy('category_id');
}
public function getBlogPostsCountAttribute() {
if(!array_key_exists('blog_posts_count', $this->relations))
$this->load('blog_posts_count');
$related = $this->getRelation('blog_posts_count');
return $related ? (int) $related->aggregate : 0;
}
Usage is simple:
{{ $category->blog_posts_count }}

Data from Two tables duplicating on join

I have got two tables , I would use facebook POST as an example.
Post Table
Comment Table
My query
$result = DB::table('posts')
->join('comments', 'posts.post_ID', '=', 'comments.post_ID')
->get();
I will receive an array of posts and comments merge. For each comments that exist , they will have the posts data.
What i want is to be able to do something like
foreach($posts as $post){
foreach($post['comment']{
}
}
Any idea how i can do that?
Something like this should work:
$result = DB::table('posts')
->join('comments', 'posts.id', '=', 'comments.post_id')
->get();
In the view:
#foreach($posts as $post)
{{ $post->post_title }}
{{ $post->message }}
#endforeach
Make sure that, field names in ->join('comments', 'posts.id', '=', 'comments.post_id') are right or change accordingly. post_title/comment_text is used to demonstrate the example, change to it's original table's field name and {{ }} is used in the example to echo the data, if you are not using Blade then use echo $post->post_title instead.
Update::
If you use Eloquent then use:
// Assumed you have defined comments as relationship method
$posts = Post::with('comments')->get(); // it's faster and called eager loading
Then in the view:
#foreach($posts as $post)
{{ $post->post_title }}
#foreach($post->comments as $comment)
{{ $comment->message }}
#endforeach
#endforeach
I will receive an array of posts and comments merge. For each comments that exist , they will have the posts data.
This is correct SQL behavoir when using a join. You will get the contents of both the rows inside the posts and comments rows on your JOIN.
According to Laravel 4.2's documentation on the join method, the parameters of the join function are:
join($table, $one, $operator = null, $two = null, $type = 'inner', $where = false)
Using an INNER JOIN, you are only going to get rows returned to you with your query (using an inner join) if you have a comment for all of the posts that you want data from. Additionally, with your INNER JOIN, if there is no comment on your post, you will not get any posts returned to you.
Also, you are not going to be able to separate all of your comments from your posts, which may mean that you are getting results returned for posts that you
The simple way to solve this for you would be to make two eloquent models:
class Post extends Eloquent {
protected $table = 'posts';
public function getComments() {
return $this->hasMany('Comments', 'post_id');
}
}
class Comments extends Eloquent {
protected $table = 'comments';
}
From that you can query for all of the posts with eager loading:
$posts = Post::with('comments')->get();
and inside your view, you go:
foreach($posts as $post) {
// echo the $post? (title, author etc)
foreach($post->comments() as $comment) {
// echo the $comment? (author, content etc)
}
}

Categories