Laravel: how to get average on nested hasMany relationships (hasManyThrough) - php

I have three tables:
products: id|name|description|slug|category_id|...
reviews: id|product_id|review_text|name|email|...
review_rows id|review_id|criteria|rating
the review table stores the review text, writer of the review and has a foreign product_id key. The review_rows table stores the ratings for different criteria like:
----------------------------------------
| id | criteria | rating | review_id |
----------------------------------------
| 1 | price | 9 | 12 |
----------------------------------------
| 2 | service | 8 | 12 |
----------------------------------------
| 3 | price | 6 | 54 |
----------------------------------------
| 4 | service | 10 | 54 |
----------------------------------------
review rows are linked to the review table with the review_id foreign key. I've set up my model relationships like this:
Product -> hasMany -> Review
Review -> belongsTo -> Product
Review -> hasMany -> ReviewRow
ReviewRow -> belongsTo -> Review
Now I would like to display the average rating for a product on my category and product pages. How can I achieve this?
I need to sum and average all the reviewRows per review and then sum and average all of those for each review to end up with the overall rating for that product. Is this possible via Eloquent or do I need a different solution or a different database design/structure?
Thanks in advance!

You need something like this http://softonsofa.com/tweaking-eloquent-relations-how-to-get-hasmany-relation-count-efficiently/ only slightly adjusted to match your needs:
public function reviewRows()
{
return $this->hasManyThrough('ReviewRow', 'Review');
}
public function avgRating()
{
return $this->reviewRows()
->selectRaw('avg(rating) as aggregate, product_id')
->groupBy('product_id');
}
public function getAvgRatingAttribute()
{
if ( ! array_key_exists('avgRating', $this->relations)) {
$this->load('avgRating');
}
$relation = $this->getRelation('avgRating')->first();
return ($relation) ? $relation->aggregate : null;
}
Then as simple as this:
// eager loading
$products = Product::with('avgRating')->get();
$products->first()->avgRating; // '82.200' | null
// lazy loading via dynamic property
$product = Product::first()
$product->avgRating; // '82.200' | null

Maybe you can try with Eloquent relationships and a little help from php function array_reduce
//model/Reviews.php
public function sum() {
return array_reduce($this->hasMany('ReviewRows')->lists('rating'), "sumItems");
}
public function sumItems ($carry, $item) {
$carry += $item;
return $carry;
}
Or with Eloquent RAW querys like:
//model/Reviews.php
public function avg() {
$result = $this->hasMany('ReviewRows')
->select(DB::raw('avg(rating) average'))
->first();
return $result->average;
}

Simple and easy solution. Add this into product model
protected $appends = ["avg_rating"];
public function reviewRows()
{
return $this->hasManyThrough('App\ReviewRow','App\Review','product_id','review_id');
}
public function getAvgRatingAttribute()
{
return round($this->reviewRows->average('rating'),2);
}

see https://github.com/faustbrian/laravel-commentable
public function comments(): MorphMany
{
return $this->morphMany($this->commentableModel(), 'commentable');
}
public function avgRating()
{
return $this->comments()->avg("rating");
}
$products = \App\Models\Products::with(
[
"comments" => function ($q) {
$q->with(["children" => function ($qch) {
$qch->take(2);
}
])->withCount("children")->where("parent_id", '=', null);
},]
)->take(5)->get();
foreach ($products as &$product) {
$product["avgRating"] = $product->avgRating();
}
dd($products);

use withAvg() as mentioned in laravel official documentation here

Related

Eloquent Count nested relationships

| Data | DataChildren | Category
----------------------------------
| id | id | id
| name | data_id | name
| | category_id |
| | name |
Data Model:
public function data_children() {
return $this->hasMany(DataChildren::class, 'data_id', 'id');
}
DataChildren Model:
public function category() {
return $this->belongsTo(Category::class, 'category_id', 'id');
}
I want to get count of Category based on Data id through DataChildren.
I just want to take the Category records from Data so the result should be like this
name from category | Count of category for Data
-------------------------------------------------
Unpublished | 1
Published | 3
I've tried using this but return null
Data::withCount(['category'=> function($query){return $query->groupBy('category_id');}])->find(1);
you need to used many to many relationship
in Category Model:
public function datas()
{
return $this->belongsToMany(Data::class, 'data_childerens', 'category_id', 'data_id');
}
Then run this Query withCount :
Category::withCount('datas')->get();
Set Data Model:
public function categories()
{
return $this->belongsToMany(Category::class, 'data_childerens', 'data_id', 'data_id');
}
Then run this Query With and withCount :
Data::with('categories')->withCount('datas')->get();

Laravel Eloquent returns an empty collection on belongsToMany relationship

UPDATE: The issue mentioned here has caused by the use of MariaDB instead of MySQL by XAMPP. I have followed the answer here to switch it to MySQL and it works like a charm.
This is regarding an e-commerce platform.
I have 2 data tables with 1 joining table for a many-to-many join. The idea is for Products to have many Special Offers running at any given time.
Tables
products
+-------+-------------------------------+
| id | name |
+-------+-------------------------------+
| 10001 | Apple iPhone 11 |
| 10002 | Samsung Galaxy S11 |
+-------+-------------------------------+
special_offers
+----+-------------------------------+
| id | name |
+----+-------------------------------+
| 1 | Awesome Offer |
| 2 | Year End Offer |
+----+-------------------------------+
product_special_offer
+------------+------------------+----------+
| product_id | special_offer_id | discount |
+------------+------------------+----------+
| 10001 | 1 | 10.0 |
| 10002 | 2 | 12.5 |
+------------+------------------+----------+
Models
Since the requirement is for a many-to-many relationship, I am using belongToMany method in my models.
Product
class Product extends Model
{
public function specialOffers()
{
return $this->belongsToMany(SpecialOffer::class)->withPivot('discount');
}
}
SpecialOffer
class SpecialOffer extends Model
{
public function products()
{
return $this->belongsToMany(Product::class)->withPivot('discount');
}
}
Controller
The following is the controller snippet.
ProductController
class ProductController extends Controller
{
public function index()
{
$product = Product::find(10001);
dd($product->specialOffers);
}
}
Results
The following is what Laravel returns.
Collection {#610 ▼
#items: []
}
The query it runs is mentioned below.
select `special_offers`.*, `product_special_offer`.`product_id` as `pivot_product_id`, `product_special_offer`.`special_offer_id` as `pivot_special_offer_id`, `product_special_offer`.`discount` as `pivot_discount` from `special_offers` inner join `product_special_offer` on `special_offers`.`id` = `product_special_offer`.`special_offer_id` where `product_special_offer`.`product_id` = 10001
This could work
class SpecialOffer extends Model
{
public function products()
{
return $this->belongsToMany(Product::class, 'product_special_offer','special_offer_id','product_id');
}
}
Make a third model to the connection table, and add the two relation. And it's will work.
class ProductSpecialOffer extends Model
{
public function products() {
return $this->belongsTo(Product::class);
}
public function specialOffers() {
return $this->belongsTo(SpecialOffer::class);
}
}

Laravel Display table product if not exist on quantity table

In PHP-MySQL I can create this query...
$sql = "SELECT * FROM product";
$result = $conn->query($,con, $sql);
while($row =mysql_fetch_array($result)){
$sqlquantity = "SELECT * FROM quantity where branchid='4' and productid='$row['productid']'";
$resultquantity = $conn->query($,con, $sqlquantity);
if (mysqli_num_rows($resultquantity) == 0) {
echo $row['productname']. "not available in branch"
}
else {
echo $row['productname']. "available in branch"
}
}
But how can I do this using Laravel?
I have 3 tables
+-----------------+ +-----------------+ +----------------+
| product table | | quantity table | | branch table |
+-----------------+ +-----------------+ +----------------+
| id | | productid | | id |
| productname | | branchid | | branchname |
+-----------------+ | quantity | +----------------+
+-----------------+
My problem is that I am trying to create a model, view and controller where I can display all the products that is not available yet on each branch base on the quantity table. Can anyone help?
Product model
public function quantity()
{
return $this->hasMany('App\Quantity', 'productid', 'id');
}
Quantity model
public function product()
{
return $this->belongsTo('App\Product', 'productid', 'id');
}
Branch Model
public function products()
{
return $this->hasMany('App\Quantity', 'branchid', 'id');
}
What I am trying to create is that if I view the branch I can add those product quantity table if the product does not exist.
You could also try this one...
Please check many-to-many relationship at the Official Docs for better explanation. .
You don't need to create a Quantity model because it serves as a pivot or joining table (not an entity) between Product and Branch model. Since you have custom pivot table name, which is quantity you need to pass it to the 2nd argument or else Eloquent will automatically create a table name for you which is branch_product (alphabetical). 3rd and 4th arguments are foreign keys of current model and the joining model respectively.
Product model
public function branches()
{
return $this->belongsToMany('App\Branch', 'quantity', 'productid', 'branchid')
->withPivot('quantity'); //additional pivot column
}
Branch model
public function products()
{
return $this->belongsToMany('App\Product', 'quantity', 'branchid', 'productid')
->withPivot('quantity');
}
Product Controller
$products = Product::all();
if($products){
$x = 0;
foreach ($products as $prod) {
$products[$x] = $prod->branches()
// ->wherePivot('productid', '=', $prod->id)
->wherePivot('branchid', '=', 4)
->wherePivot('quantity', '=', 0)
->get();
$x++;
}
}
return response(['products' => $products],200);
//get all the products in branch 4 with quantity = 0
Then you could do the conditional if to determine if it is available or not.
You could use whereHas() method like :
Product::whereHas( 'Quantity', function($sQuery){
$sQuery->where('branchid', 4);
})->get();

Laravel - model modifications

I need to refactor project and I have problem. Below is old, working model, where 'active' column is in "people" table. I need to move 'active' column into "people_translations" table.
Do you have any Idea to modify scopeActive method?
Thanks a lot!
Old working model:
class BaseModel extends Eloquent
{
public function scopeActive($query)
{
return $query->where($this->table . '.active', '=', 1);
}
}
class People extends BaseModel
{
protected $table = 'peoples';
protected $translationModel = 'PeopleTranslation';
}
class PeopleTranslation extends Eloquent
{
public $timestamps = false;
protected $table = 'peoples_translations';
}
Old tables structure:
Table: peoples
id | type | date | active
-------------------------
7 | .... | ... | 1
Table: peoples_translations
id | people_id | language_id | name
-----------------------------------
1 | 7 | 1 | Ann
Old query:
$peoples = \People::active()->get();
New tables structure:
Table: peoples
id | type | date
----------------
7 | .... | ...
Table: peoples_translations
id | people_id | language_id | name | active
--------------------------------------------
1 | 7 | 1 | Ann | 1
Create a relation for translations inside People Model
public function translations()
{
return $this->hasMany('PeopleTranslation', 'people_id');
}
Create active scope in People model
public function scopeActive($query)
{
return $query->whereHas('translations', function($query) {
$query->where('active', 1);
});
}
It will make subquery for this table and as a result it will get where (count of translations with active = 1) > 0.
If you have one-to-one relation - look for hasOne relation method instead of hasMany.

Insert relational models laravel eloquent

I am new to eloquent for laravel 4 and i can't quite figure out on how to make and save related models.
Table Messages: id | user | message | parent
For example:
class Message extends Eloquent {
public function user(){
return $this->belongs_to('User', 'id');
}
public function child(){
return $this->hasMany('Message', 'parent');
}
public function parent(){
return $this->belongs_to('Message', 'id')
}
}
So if you have a conversation you have a message, this message may or may not have a child or a parent and always has a user which made the message. So if i want to save a conversation this way, how would i do this?
For example, i have to following conversation:
John (1) says: Hi all!
Mark (3) responds: Hey there!
Hans (4) responds: Hi john
Peter(2) responds: Goodmorning.
Now i am john and i would like to save this conversation as below:
id | user | message | parent
========================================
1 | 1 | Hi, All! | NULL
2 | 3 | Hey there! | 1
3 | 4 | Hi john | 1
4 | 2 | Goodmorning. | 1
I can save them all separately but i figure there has to be a better way than below:
$parent = NULL;
foreach($messages as $message){
$model = new Message;
$model->user = $message['user'];
$model->message = $message['value'];
if(!is_null($parent)){
$model->parent = $parent;
}
$model->save();
if(is_null($parent)){
$parent = $model->id;
}
}
The initial problem I saw was that you are saying that the message's parent is always itself, you need to specify an additional unsigned integer to relate on. Here's one example:
public function parent(){
return $this->belongs_to('Message', 'parent_id')
}
You need to use that same 'parent_id' for the children as well:
public function children(){
return $this->hasMany('Message', 'parent_id');
}

Categories