Laravel migration with relation - mysql

I am new with Laravel, so I do not know what exactly is this problem. So I will describe it in detail. First I have 2 table Article and Comment, with the relation 1-1, it means that an article can only have 1 comments.
The code is:
$article = Article::with('comments')
and the result is
and below is the attribute of comment I get in relation:
But now, I want the builder select only the article where the article's content length > its comment's content length. In MySQL statement, it is
"Select from articles a where length(a.content) > (Select length(c.content) from comments c where c.article_id = a.id)
I have tried with raw and join and it work. But I want to find out that can I do the same with "with" relation? How can I do this?

Try this
$article = Article::where(DB::raw('length(content)','>' ,DB::raw('Select length(content) from comments where article_id = id'))->with(['comments'])->get();

Try this:
$articles = Article::with('comments')->get();
$onlyWithLongerContent = $articles->filter(function($article) {
return strlen($article->content) > strlen($article->comments->content);
})->values();
I will try to think of another way, but this is one way of doing it.
Another way would probably be about building queries for that using join and where clauses.

Have you tried something like this:
/**
* Get the comments.
*
* #return
*/
public function comments()
{
return $this->hasmany('App\Comments')->where('content', '!=', null);
}
Or
/**
* Get the comments
*
* #return
*/
public function comments()
{
return $this->hasManyThrough('App\Comments', 'App\Articles', 'id', 'article_id')->where('content', '!=', null);
}

Related

I want to show posts and all of their comments in laravel

I am trying to show all posts of mine and my friends and also wanna show the comments on that posts
here is my controller
$user = Auth::user();
$friend_ids = $user->friends()->pluck('friend_id')->toArray();
$posts=PostModel::whereIn('users_id',$friend_ids)
->orWhere('users_id',Auth::user()->id)
->leftJoin('users as p_user','posts.users_id','=','p_user.id')
->leftJoin('post_comments','posts.id','=','post_comments.post_id')
->leftJoin('users as c_user','post_comments.friend_id','=','c_user.id')
-select('posts.caption','posts.image','posts.created_at','p_user.name','p_user.user_img as user_image','posts.id','c_user.user_img as commenter_img','post_comments.comment')
->get();
but the issue is that whenever any post have more than one comments it create more than one post and show one comment on any post , hope so you understand my question if not then I return my data here is the result
[{"id":5,"caption":"5thpost","image":"s1.jpg","name":"roger","user_image":"roger.jpg","commenter_img":"alex.jpg","comment":"nice one"},
{"id":5,"caption":"5thpost","image":"s1.jpg","name":"alex","user_image":"alex.jpg","commenter_img":"sufi.jpg","comment":"wow"}]
here you can see the id 5 is repeating I want to show all comments of id 5
You can go a step further and eager load from friends
$friends = $user->friends()->with(['posts.comments'])->get()
and you can chain on extra functions inside the with statement if required!
Likely you would want to add a between dates for the posts function for instance like:
$friends = $user->friends()->with(['posts' => function($q) use ($start, $end){
return $q->whereBetween('created_at', [$start, $end]);
},'posts.comments'])->get()
you can get the posts with $friends->posts and the comments with $friends->posts->comments and all the data you want will already be loaded and it stops N + 1 queries!
In Friends Model:
public function posts()
{
return $this->hasMany(Post::class);
}
In Post Model:
public function comments()
{
return $this->hasMany(Comments::class);
}
Don't use joins, use Model Relationships. Then you can eager-load related records like:
$posts = $postModel->with('comments')->where...
The result is that each Post Model within the Collection would have a nested attribute called 'comments', the name of the method within the Model that describes the relationship. And this 'comments' attribute would contain an Eloquent\Collection of Comment Model records.

Filter With() in Query Scope

Controller
$r = \App\User::whereIn('id', $user_ids)->withPosts($category_id)->get();
User model
public function scopeWithPosts($query, $category_id)
{
return $query->with('posts')->where('category_id', $category_id);
}
I have been at this for too many hours now.
I am trying to use with() along with an query scope to add an extra filter to the relationship.
However it gives me the error " category_id not existing in users table"? What am I missing?
Laravel 6
The problem you are experiencing is that you are expecting the with('posts') function to return a query that is relative to the Posts ORM model. It won't, it will still return a reference to the original query. What you will find is that the with function returns $this, so you'll always get the original query.
What you are attempting is a SQL query to find the User, followed by another SQL query to get all the Post records of that user, with those posts filtered by category. So
SELECT * FROM Users WHERE id=?;
SELECT * FROM Posts WHERE user_id = ? AND category_id = ?
To do that in the Eloquent relationship, you need to subquery, like so:
return $query->with(['posts' => function ($q) use ($category_id) {
$q->where('category_id', $category_id);
}]);
Please comment if you need further info and I'll edit my answer.

Fastest way to order table by related table column

I have 2 tables comments and images
I'm trying to order the comments that have an image first and then comments without images.
I used this query below to solve this issue but it's very slow on large data it takes about 12 seconds
$data = Comment::withCount([
'images' => function ($query) use ($shop)
{
$query->where('shop_name', $shop);
},
])->where('shop_name', $shop)->orderBy('images_count', 'desc')->paginate(10);
How can I improve the performance or is there any other way to get similar results in faster way ?
The problem lies in how Laravel makes withCount() work - it will generate something like this:
SELECT `comments`.*,
(SELECT Count(*)
FROM `images`
WHERE `comment_id`.`id` = `comments`.`id`
AND `images`.`shop_name` = 'your shop') AS `images_count`
FROM `comments`
WHERE `shop_name` = 'your_shop'
ORDER BY `images_count`
This will force MySQL to execute count() subquery for every comment of specified shop.
What you need to do here is to make this correlated subquery (that executes for every row) into an independent query (that executes only once) and then utilize joins to let MySQL pair it all up:
$imagesCountQuery = DB::table('comments')
->selectRaw('comments.id AS comment_id, COUNT(comments.id) AS images_count')
->join('images', 'images.comment_id', '=', 'comments.id') /* !!! */
->where('images.shop_name', '=', $shop)
->groupBy('comments.id');
$data = Comment::joinSub($imagesCountQuery, 'images_count_sub', function ($join) {
$join->on('comments.id', '=', 'images_count_sub'.'comment_id'); /* !!! */
})->where('shop_name', $shop)->orderBy('images_count_sub.images_count', 'desc')->paginate(10);
!!! - this lines should be modified to represent your comment's images relation. In this example I just assumed it was hasMany relation since you didn't point that out in your question.
you need to create a hasMany relation in Comment model.
class Comment extends \Eloquent {
public function images()
{
return $this->hasMany('Images', 'comment_id');
}
}
Now you can use below query to fetch records.
$comments = Comments::with('images')->get()->sortBy(function($comment)
{
return $comment->images->count();
});

Symfony4.1 Doctrine ManyToMany Reduce No of Queries

I'm working on a project. Entity are Blog,Category,Tags. Blog and Tags are in ManyToMany Relation. My repository query to fetch data by Tags filter is.
CODE1:
/**
* #return BlogPost[]
*/
public function getAllActivePostsByTags($value, $order = "DESC", $currentPage = 1, $limit = 10)
{
$query = $this->createQueryBuilder('p')
// ->select('p','t')
->innerJoin('p.blogTags', 't')
->where('t.slug = :val')
->setParameter('val', $value)
->orderBy('p.id', $order)
->getQuery();
$paginator = $this->paginate($query, $currentPage, $limit);
return $paginator;
}
This code works fine. All the tags(No of tags in a post)are displayed correctly. But the No of DB Query is 14. Then When I uncomment select as this,
CODE2:
/**
* #return BlogPost[]
*/
public function getAllActivePostsByTags($value, $order = "DESC", $currentPage = 1, $limit = 10)
{
$query = $this->createQueryBuilder('p')
->select('p','t')
->innerJoin('p.blogTags', 't')
->where('t.slug = :val')
->setParameter('val', $value)
->orderBy('p.id', $order)
->getQuery();
$paginator = $this->paginate($query, $currentPage, $limit);
return $paginator;
}
No of Query is 9. But The Tags per Post is only one(Not displaying all the tags of a single post).
To be clear info:
It displays entire list of BlogPost.
But not all Tags of a Post.
Only one Tag per Post is shown.
Question: Is code1 is correct (No of DB Query = 14) or Do I have to tweak little bit to reduce no of DB Hits. Please guide me on this.
This is the expected behaviour in both cases.
Case 1) You just select the BlogPost entities. So you tell doctrine to fetch all BlogPosts that have the BlogTag that has slug = value.
The SQL query produced returns only column values from the blog_post table and so only hydrates the BlogPost entities returned, it does not hydrate the collection of BlogTags inside each BlogPost.
When you try to access the tags of a BlogPost a new query is generated to get and hydrate its collection.
That is the reason you get more queries in this case.
Case 2) You select also the filtered BlogTag entities, and doctrine hydrates(puts) only this filtered BlogTag to each BlogPost `s collection.
When you try to access the BlogTags of a BlogPost, you get the filtered one that meets the condition in the querybuilder.
To force doctrine to "reload" the data from the database, you should refresh the blogPost entity:
$em->refresh($blogPost);
and also include refrech option on cascade operations of the relation definition:
#OneToMany(targetEntity="BlogTag", mappedBy="post", cascade={"refresh"})
References:
what cascade refresh means in doctrine 2
refresh objects: different question but same solution
Thanks #Jannes Botis for refresh. But in my case the code itself is wrong. There need a slight change in it.
BlogTags.php
/**
* #ORM\ManyToMany(targetEntity="BlogPost", mappedBy="blogTags")
*/
private $blogPosts;
BlogPost.php
/**
* #var Collection|BlogTags[]
*
* #ORM\ManyToMany(targetEntity="BlogTags", inversedBy="blogPosts", cascade={"refresh"})
* #ORM\JoinTable(
* name="fz__blog_n_tag",
* joinColumns={
* #ORM\JoinColumn(name="blog_id", referencedColumnName="id")
* },
* inverseJoinColumns={
* #ORM\JoinColumn(name="tag_id", referencedColumnName="id")
* }
* )
* #ORM\OrderBy({"name": "ASC"})
*/
private $blogTags;
This created the join_table. Allready I have a join_table. Although This code is for reference to someone.
Controller.php
// This is my old Code
$bp = $em->getRepository('App:BlogPost')->getAllActivePostsByTags($slug, "DESC", $page, self::PAGE_LIMIT);
// This is my New Code
$bp = $em->getRepository('App:BlogTags')->getAllActivePostsByTags($slug, "DESC", $page, self::PAGE_LIMIT);
Repository.php
public function getAllActivePostsByTags($value, $order = "DESC", $currentPage = 1, $limit = 10)
{
$query = $this->createQueryBuilder('t')
->select('t','p','tx')
->innerJoin('t.blogPosts', 'p')
->innerJoin('p.blogTags', 'tx')
->where('p.isActive = :val1')
->andWhere('t.slug = :val2')
->setParameter('val1', true)
->setParameter('val2', $value)
->orderBy('p.id', $order)
->getQuery();
$paginator = $this->paginate($query, $currentPage, $limit);
return $paginator;
}
I not changed my old twig file completely. As it throws error at many places. Because now i'm using tags repo instead of blog. So i modified the twig with
{% include 'frontend/page/blog_home.html.twig' with { 'bp':bp|first.blogPosts } %}
Help me on this (twig file): There is only one tag, that's why |first twig filter
Clarify me with this twig filter. Do I'm doing right. Give me suggestion to improve on it. I tried bp[0] This trows error.
Finally: By using old code in controller it returns 14 db hits. Now it returns only 8. Even there are more tags in a post (but old one returns more).

How to do a join on two entities in Symfony and Doctrine?

I have a select query and I'm trying to add a join to it.
In the example below, I have a Questionentity that I use to return some results, and I want to add a join with the User entity, like:
SELECT question FROM question AS q LEFT JOIN USER u ON q.user_id= u.id;
I would like the result to be a User entity inside a Question entity, something like:
private Question (entity)
private id
private user_id
private User (entity)
private id
private name
here is my class
namespace AppBundle\Repository;
use AppBundle\Entity\User;
use AppBundle\Entity\Question;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\Tools\Pagination\Paginator;
class QuestionRepository extends EntityRepository
{
/**
* #param int $currentPage
*
* #return Paginator
*/
public function getQuestions($currentPage = 1)
{
$questions = $this->createQueryBuilder('question')
->where('question.active is NULL')
->getQuery();
$paginator = $this->paginate($questions, $currentPage);
return $paginator;
}
}
I call it like this
$questionRepo = $this->container->get('doctrine')->getManager()->getRepository('AppBundle:Question');
$questions = $questionRepo->getQuestions(1);
Any ideas?
How about that:
$questions = $this->createQueryBuilder('question')
->leftJoin('question.user', 'question_user', 'WITH', 'question_user.user = :user_id')
->where('question.active is NULL')
->setParameter('user_id', $user_id)
->getQuery();
$paginator = $this->paginate($questions, $currentPage);
Edit: Because of your latest comments, i have to mention, that this suggestion is assuming that your Question entity looks like this (according User's entity:
/**
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\User", inversedBy="question")
* #ORM\JoinColumn(name="user_id", referencedColumnName="id")
*/
private $user;
If not, add this, generate entities (php app/console doctrine:generate:entities AppBundle:Question) and update DB (php app/console doctrine:schema:update --force).
PS: Before generating entities, you'll have to remove old getters/setters.