laravel eloquent get related articles based on a tag - laravel

I'm a bit confused about how to do the following.
I have a table of articles and a table of tags with a many to many join and a pivot table between the two. I've got the relationships set up in the models
An articles can have more than one tag.
How can I easily(?) obtain a list of related articles for an article based on the tags attached to the current article.
I've tried querying from the tags side as follows:
foreach($article->tags()->get() as $tag) {
$relatedArticles .= Tag::with('articles')
->where('id','=', $tag->id)
->take(6)
->get();
}
This produces a nil response
I'm not sure about how to query from the articles to find articles with the tags dynamically.
So if an article has attached tag1 and tag2 I then want to retrieve all articles which have either tag1 or tag2 attached to them (ideally sorted on article date). The tags will be different for each article and may just be one or many.
Ideally i'd like to do this with an eloquent query but not essential - I'm not sure how to do in mysql either as a starting point.
Any help appreciated

Provided you are using Laravel 4.1, you can do something like this using the whereHas eloquent method:
$tag_ids = $article->tags()->lists('id');
$relatedArticles = Article::whereHas('tags', function($q) use ($tag_ids) {
$q->whereIn('id', $tag_ids);
})
->orderBy('created_at')
->take(6)
->get();
Breakdown
There's a few things going on here:
lists()
You can use the lists method on a select to get just a particular column, we're only concerned with the ID column in this case.
More info here: http://laravel.com/docs/queries#selects
whereHas()
We're using the new whereHas method which you can read.
More more about here: http://laravel.com/docs/eloquent#querying-relations
use()
Since the whereHas method accepts a closure (or 'anonymous function'), the function doesn't have any access to variables set externally, so we need to send them through to the function. We can do this with use.
More information here: http://www.php.net/manual/en/functions.anonymous.php

Related

Laravel: Ordering posts by category in numerical order

In my application I want to order my posts by category in numerical order.
So I guess I need a mapping table like this:
category_id | post_id | sort_order
then setting up a kind of "Order model" with specific methods that handle it all and use this model as a relationship with the Post model.
But before I start on this project I'd like to know if there is not a Laravel package out there that does the job or another solution that I might have missed.
I'm not 100% sure, if I understand your need correctly, but I guess your posts are stored in a DB.
Then you can simply directly get the posts from DB ordered:
$posts = DB::table('posts')
->orderBy('category_id ', 'asc')
->orderBy('post_id', 'asc')
->get();
See the documentation:
https://laravel.com/docs/8.x/queries#ordering-grouping-limit-and-offset
I don't think, you need a Order model or something similar to realize this.

How do the "has" and "whereHas" methods work in Eloquent?

I have a relationship defined between users and permisson, a user can have many permissions.
When I use "with" I get the data normally.
$user->with('permisson')->get();
I get the user with their permissions.
When I use "has" it only returns the user.
$user->has('permission')->get();
For what I've read, I should get the permissons if the user contains at least one permission.
I am using Postgres driver.
with is used for eager loading a relationship. You'd use it to fetch all of the specified relationships for each model (individually, or in a collection).
has is for using the relationship as a constraint or filter. As you said, using something like has('permission') will add a constraint to the query that says "only get Users that have at least one permission". This does not automatically load the relations like with(), it only creates the constraint.
You can combine the two if you want to take advantage of both the constraint and eager loading the results.
User::has('permission')->with('permission')->get();
Seems a bit redundant, I know.
From laravel docs:
If you wish to limit your results based on the existence of a relationship, you should use the has method. For example, if you want to retrieve all blog posts taht have at least one comment, yo may pass the name of the relationship to the has and orHas methods:
$posts = Post::has('comments')->get();
So, when you use the has method, it will not return the relationship, but just the post models which has at least one comment.
The whereHas and orWhereHas methods allows you to add customized constraints to a relationship constraint, such as checking the content of a comment (using the laravel example):
$posts = App\Post::whereHas('comments', function (Builder $query) {
$query->where('content', 'like', 'foo%');
})->get();
When you use the with method, you are loading the realtionship with the model:
$user->with('permission')->get()
This code will load the user and the permission relationship, while this one
$user->has('permission')->get();
will load the user who has some permission.
Hope it helps.

How to list related blog posts in OctoberCMS

I am building an App with OctoberCMS and wanted to list related blog posts at the bottom of each post page basing on categories. I figured out rainlab_blog_posts table does not have a foreign key pointing to blog categories table. To achieve what wanted I thought of extending the blog_posts table and added category_id as foreign key using a plugin. I defined foreign key constraints in table migration. Everthing seem to be fine. My challenge is, How can I insert category id in posts table each time a new post is created. OctoberCMS create post backend has an option where the author assigns a category to a new blog post by selecting from list. Just don't understand how to pass this category id and insert into rainlab_blog_posts table in category_id field.
This is what I want to achieve in my API:
routes.php
use Rainlab\Blog\Models\Post;
Route::get('apiv1/related-posts/{postid}',function($id)
{
$post = Post::where('id',$id)->first();
$posts = Post::where('category_id',$post->category_id)
->where('id','!=',$id)
->orderBy('views','desc')
->get()->take(5);
return $posts;
});
Or if there is a better way of achieving this will appreciate. Cheers!
Hmm seems something wrong here,
As first I can see from Blog Plugin that Blog <=> Category it is MM relation so you will not find category_id in rainlab_blog_posts table as all relation is maintained by this mm-relation table rainlab_blog_posts_categories.
So I think you will select only one category for blog so you can get related blogs for only that category. [ assuming from your code and description ]
we can utilize relationships for that.
So your code can look like this
use Rainlab\Blog\Models\Post;
Route::get('apiv1/related-posts/{postid}', function($id)
{
$post = Post::where('id',$id)->first();
// we need this because there will be mm relation so
// we fetch first category and this will be based on
// [ name sorting - does not matter as there will be only one cat. ]
$firstCategory = $post->categories()->first();
// now we fetch only that post which are related to that category
// but we skip current post and sort them and pick 5 posts
$posts = $firstCategory->posts()
->where('id', '!=', $id)
->orderBy('views','desc')
->limit(5) // use limit instead of take()
->get();
// use limit instead of take() as we don't need extra data so
// just put limit in sql rather fetching it from db then
// putting limit by code - limit() is more optimised way
return $posts;
});
Good thing now you don't need to add that category_id field on rainlab_blog_posts. So, I guess now you Also don't need to worry about adding it during post insertion.
if any doubts please comment.
Incase you simply want to display related blog posts without having to customize the blog database, there is an OctoberCMS plugin called Related Articles
that requires rainlab blog plugin. It will display all the other articles belonging to the post's category.
On your Backend menu, go to settings,on System, go to Updates and Plugins.
Go to Install plugins. Search for plugin Related articles. The creator is Tallpro. Install it.
Go to CMS. On the page you want related articles, go to components and drag Related articles. Save and preview it. You can also fork the component to edit how you want your related articles to be displayed. You will find more information on the documentation.
If you encounter any problems feel free to inquire or you can check out the plugin reviews for problems faced and their solution.

Laravel Eloquent: Order results by eaged loaded model.property, and paginate the result

Lets say I have three models: Post, Category and Tag.
The Post belongsTo Category and Category hasMany Post.
Theres manyToMany relation between Tag and Category.
I want to list my posts by Category name and paginate the results.
Post::with('category','category.tags')
->orderBy('category.name') //this is the bogus line
->paginate(10);
But this syntax doesn't work.
What I tried is this as:
Post::select('categories.*')
->join('categories','posts.category_id','=','categories.id')
->orderBy('categories.name)
->paginate(10);
But then I lose the eager loaded data.
If I drop the select() clause then I get rubbish data as categories.id overwrites posts.id. See here.
Is there any elegant way to solve this issue? After spending hours on this I'm one step away from iterating through paginated posts and 'manually' loading the relations as:
foreach($posts as $post) {
$post->load('category','category.tags');
}
Not even sure if there's downside to this but it doesn't seem right. Please correct me if I'm wrong.
UPDATE on last step: Eager loading on paginated results won't work so if I go that road I'll need to implement even uglier fix.
You should be able to use both join and with.
Post::select('posts.*') // select the posts table fields here, not categories
->with('category','category.tags')
->join('categories','posts.category_id','=','categories.id')
->orderBy('categories.name)
->paginate(10);
remember, the with clause does not alter your query. Only after the query is executed, it will collect the n+1 relations.
Your workaround indeed loses the eager loading benefits. But you can call load(..) on a collection/paginator (query result) as well, so calling ->paginate(10)->load('category','category.tags') is equivalent to the query above.

How to get models order by relation value null and not null

I am using laravel 5.2 I have a question. There is Post model and Comment model. I want to get all posts with comments, whether they have comments or not. And I want to order posts by their comments, such as posts have comments are front, posts don't have comments are behind. How do I finish this? Thanks.
You can use withCount and order them by comments count as:
$posts = Post::withCount('comments')
->orderBy('comments_count', 'desc')
->get();
This will order the posts which have comments at first place and rest will follow it.
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.

Resources