1
votes

I have products which have also comments. This comments can be voted and comments can also have child comments. The comments get loaded via eager loading $with which is defined in the product model and the child comments get as well loaded via eager loading which is also defined in the comments model. The child comments can also be voted (but don't have any child comments).

Product.php (Model)

namespace App;

class Product extends Model
{
    /**
     * @Protected_variables
     */

    protected $with = [
        'comments',
        'user'
    ];

    /**
     * @Relationships
     */

    public function user()
    {
        return $this->belongsTo('App\User');
    }

    public function comments()
    {
        return $this->morphMany('App\Comment', 'commentable');
    }
}

Comment.php (Model)

namespace App;

class Comment extends Model
{
    /**
     * @Protected_variables
     */
     
    protected $with = [
        'children',
        'user'
    ];

    public function user()
    {
        return $this->belongsTo('App\User');
    }

    public function children()
    {
        return $this->hasMany('App\ChildComment');
    }
        
    public function likes()
    {
        return $this->belongsToMany('App\User', 'comments_likes', 'comment_id', 'user_id')->withTimestamps();
    }
}

I use route model binding to receive my product in the ProductController. Here is an example of the route Route::get('/product/{product}', ['as' => 'product.show', 'uses' => 'ProductController@show']); and the function show:

ProductController@show:

public function show(Product $product, Request $request)
{
    if(request()->wantsJson()){
        return response()->json([
            'product' => $product
        ]);
    }

    return view('pages.productDetails')->with([
            'product' => $product
        ]);
}

In the show function I can now access the comments of the product, as well as the child comments of the comments and all the other relations which get loaded via the $with attribute in the models.

Now comes questions. As I already have loaded the relationship, how can I either 1. sort them now or 2. pass the sort arguments to the model to get a sorted relationship back?

When I write dd($product->comments->sortByDesc('created_at')->toArray()); I get my product with the comments which are sorted by created_at. That's what I want. But I cannot assign the sorted collection to the product collection like this $product->comments = $product->comments->sortByDesc('created_at'); because $product->comments is @property-read.

I also don't want to do another query and pass this $product->comments()>orderBy('created_at', 'desc')->get(); to my response. Because then the eager loading in the model redundant.

Is there a way to 1. either sort the relationship collection or 2. pass the sort arguments to the model to get a sorted relationship back?

I actually want to stick to my route binding model. I know I could pass the sorting arguments and product id as arguments and then execute it via get. But is there a solution to do that within the model eager loading?

Also, please note I want to sort my comments also by likes and the count of child comments they have. I don't want to sort them only by date, so I need to pass the sort argument to the model when choosing a solution for number 2.

Kind regards!

1

1 Answers

0
votes

You can set a default order for a relation or make an entire new relation for it.

public function comments()
{
    return $this->morphMany('App\Comment', 'commentable')->orderBy('created_at', 'desc');
}

Edit: if you decide to go with a default order and need to remove this another query you can use reorder() see https://laravel.com/docs/7.x/queries#ordering-grouping-limit-and-offset