addGlobalScope withCount relationship's relationship - laravel

I have a 3 models: Phone, Product and Store.
A phone has many Product which belongs to a Store.
I am trying to add a golbal scope so that every time I load a phone, products and stores counts are loaded automatically.
products_count works fine, however stores_count is a bit tricky since store is not a Phone's relationship but a Product's.
I have tried the following but it gives me an error "Method getRelated does not exist.", I assume because stores() now returns a collection.
Any ideas as to how I could add the stores_count?
public static function boot(){
parent::boot();
static::addGlobalScope('products', function ($builder){
$builder->withCount('products');
$builder->withCount('stores'); <----- gives error
});
}
public function products(){
return $this->hasMany(Product::class);
}
public function stores(){
$store_ids = $this->products()->get(['store_id'])->unique();
return Store::find($store_ids);
}
Update after #Sandeesh answer.
I tried to use hasManyThrough but it returns an empty collection which is wrong.
When I dd($phone->products); I can see 7 products which have 3 different stores.
public function stores(){
return $this->hasManyThrough(Store::class, Product::class,
'store_id', 'id');
}
Database schema
Phone
-id
Product
-id
-phone_id
-product_id
-store_id
Store
-id
Update 2
So I managed to get the produced query from the stores() method above.
select `phones`.*,
(select count(*) from `products` where `phones`.`id` = `products`.`phone_id`) as `products_count`,
(select count(*) from `stores` inner join `products` on `products`.`id` = `stores`.`id` where `phones`.`id` = `products`.`store_id`) as `stores_count`
from `phones` where `slug` = ? limit 1
The problem is in the third line. The query is messed up, not sure what is wrong with the relationship though.

You can using hasManyThrough
https://laravel.com/docs/5.4/eloquent-relationships#has-many-through
public function stores()
{
return $this->hasManyThrough(Store::class, Product::class);
}
Edit
This should give you what you need. But eager loading is always better
protected $appends = [
'productCount',
'storeCount'
];
public function getProductCountAttribute()
{
return $this->products()->count();
}
public function getStoreCountAttribute()
{
return Store::whereIn('id', $this->products()->pluck('store_id')->toArray())->count();
}

Related

Get only one column from relation

I have found this: Get Specific Columns Using “With()” Function in Laravel Eloquent
but nothing from there did not help.
I have users table, columns: id , name , supplier_id. Table suppliers with columns: id, name.
When I call relation from Model or use eager constraints, relation is empty. When I comment(remove) constraint select(['id']) - results are present, but with all users fields.
$query = Supplier::with(['test_staff_id_only' => function ($query) {
//$query->where('id',8); // works only for testing https://laravel.com/docs/6.x/eloquent-relationships#constraining-eager-loads
// option 1
$query->select(['id']); // not working , no results in // "test_staff_id_only": []
// option 2
//$query->raw('select id from users'); // results with all fields from users table
}])->first();
return $query;
In Supplier model:
public function test_staff_id_only(){
return $this->hasMany(User::class,'supplier_id','id')
//option 3 - if enabled, no results in this relation
->select(['id']);// also tried: ->selectRaw('users.id as uid from users') and ->select('users.id')
}
How can I select only id from users?
in you relation remove select(['id'])
public function test_staff_id_only(){
return $this->hasMany(User::class,'supplier_id','id');
}
now in your code:
$query = Supplier::with(['test_staff_id_only:id,supplier_id'])->first();
There's a pretty simple answer actually. Define your relationship as:
public function users(){
return $this->hasMany(User::class, 'supplier_id', 'id');
}
Now, if you call Supplier::with('users')->get(), you'll get a list of all suppliers with their users, which is close, but a bit bloated. To limit the columns returned in the relationship, use the : modifier:
$suppliersWithUserIds = Supplier::with('users:id')->get();
Now, you will have a list of Supplier models, and each $supplier->users value will only contain the ID.

Laravel: How to get data from 3 tables with relationship

I have 3 Tables:
Customers
id
name
Sales
customer_id
sale_date
Contacts
customer_id
contact_date
There aren't any update operations in the contacts table. Each process opens a new record in the contacts table. So, a user can have more than one records in the contacts table.
Here are my relations in models:
Customer
public function contacts()
{
return $this->hasMany(Contact::class);
}
public function sales()
{
return $this->hasMany(Sale::class);
}
Contact
public function customer()
{
return $this->belongsTo('App\Customer', 'customer_id');
}
Sale
public function customer()
{
return $this->belongsTo('App\Customer');
}
I would like to have the latest record of the contacts table and make it join with the other related tables.
Here is the query which I have tried:
$record = Contact::groupBy('customer_id')
->select(DB::raw('max(id)'));
$result = Customer::query();
$result->where('is_active', 'YES');
$result->with('sales');
$result->whereHas('contacts', function ($q) use($record){
return $q->whereIn('id', $record)->where('result', 'UNCALLED');
});
return $result->get();
In the blade file, I get some result in foreach loops. However, I am unable to get the related data from the sales and contacts table.
#foreach($result as $item)
#foreach($item->sales as $sale) // Has no output and gives error: Invalid argument supplied for foreach()
#foreach($item->contacts as $contact) // Has no output and gives error: Invalid argument supplied for foreach()
Can anyone help me how to display the sale and contact date? Or any idea for how to improve this code quality?
If you want the latest record of the contacts you can declare another relationship on the Customer model, e.g.:
public function latest_contact()
{
return $this->hasOne(Contact::class)->latest('contact_date');
}
BTW you can always declare one or more hasOne additional relationship if you have a hasMany in place the foreign key used is the same.
In this way you can retrieve latest_contact eager loaded with your Customer model:
$customer = Customer::with('latest_contact')->find($id);
Or use this relationship in your queries, something like that:
$customers = Customer::where('is_active', 'YES')
->with('sales')
->with('contacts')
->whereHas('last_contact', function ($q){
return $q->where('result', 'UNCALLED');
})->get();
Or that:
$customers = Customer::where('is_active', 'YES')
->with('sales')
->with('contacts')
->with('last_contact', function ($q){
return $q->where('result', 'UNCALLED');
})->get();
If you want you can declare last_contact with the additional where:
public function latest_contact()
{
return $this->hasOne(Contact::class)
->where('result', 'UNCALLED')
->latest('contact_date');
}
This way all other queries should be easier.
I hope this can help you.
I'm not sure, but can you try to do the following:
return Customer::where('is_active', 'YES')
->with([
'sale',
'contact' => function ($query) use($record) {
return $query->whereIn('id', $record)->where('result', 'UNCALLED');
}
])->get();

Laravel 5.5: How to get top selling items of a given shop?

My tables are like:
shops
[id]
inventories
[id, shop_id]
orders
[id, shop_id]
order_item
[order_id, inventory_id, quantity]
Models:
//Shop
class Shop extends Model
{
public function inventories()
{
return $this->hasMany(Inventory::class);
}
public function orders()
{
return $this->hasMany(Order::class);
}
}
//Inventory
class Inventory extends Model
{
public function shop()
{
return $this->belongsTo(Shop::class);
}
public function orders()
{
return $this->belongsToMany(Order::class, 'order_items')
->withPivot('quantity');
}
}
//Order
class Order extends Model
{
public function shop()
{
return $this->belongsTo(Shop::class);
}
public function inventories()
{
return $this->belongsToMany(Inventory::class, 'order_items')
->withPivot('quantity');
}
}
Now I want 5 top selling inventories of a given shop, What will be the best possible way to do that?
I'm on Laravel 5.5
select s.id,sum(oi.quantity) as total from munna.shops as s
join munna.inventories as iv on s.id=iv.shop_id
join munna.orders as o on iv.shop_id=o.shop_id
join munna.order_items as oi on o.id=oi.order_id
group by s.id
order by total desc limit 5
First, by looking at your tables on order_item, the order_id and inventory_id will bellong to the same shop for sure? I guess yes because if not you would have 2 different shops with same top order. I dont know why you are doing it like this but it's a bit confusing can't figure out why but I would try this:
public function topOrders()
{
$items = DB::table('shops')
->join('orders', 'shops.id', '=', 'orders.shop_id')
->join('inventories', 'shops.id', '=', 'inventories.shop_id')
->join('order_items', 'orders.id', '=', 'order_items.order_id')
->orderBy('quantity', 'desc')
->take(5)
->get();
return $items;
}
What I wrote should select everything from all 3 rows, if you want to select only the items or whatever you want to select you can specify it adding a select clause
Though this was my own question I found the solution on my own and I want to share the solution with the community. I wanted to solve it using Eloquent because I need the model on the view and didn't want to query the model again.
Inventory::where('shop_id', \Auth::user()->shop_id)
->select(
'inventories.*',
\DB::raw('SUM(order_items.quantity) as quantity')
)
->join('order_items', 'inventories.id', 'order_items.inventory_id')
->groupBy('inventory_id')
->get();
I hope this'll help someone with similar issue. Thanks

Laravel oneToMany accessor usage in eloquent and datatables

On my User model I have the following:
public function isOnline()
{
return $this->hasMany('App\Accounting', 'userid')->select('rtype')->latest('ts');
}
The accounting table has activity records and I'd like this to return the latest value for field 'rtype' for a userid when used.
In my controller I am doing the following:
$builder = App\User::query()
->select(...fields I want...)
->with('isOnline')
->ofType($realm);
return $datatables->eloquent($builder)
->addColumn('info', function ($user) {
return $user->isOnline;
}
})
However I don't get the value of 'rtype' for the users in the table and no errors.
It looks like you're not defining your relationship correctly. Your isOnline method creates a HasMany relation but runs the select method and then the latest method on it, which will end up returning a Builder object.
The correct approach is to only return the HasMany object from your method and it will be treated as a relation.
public function accounts()
{
return $this->hasMany('App\Accounting', 'userid');
}
Then if you want an isOnline helper method in your App\User class you can add one like this:
public function isOnline()
{
// This gives you a collection of \App\Accounting objects
$usersAccounts = $this->accounts;
// Do something with the user's accounts, e.g. grab the last "account"
$lastAccount = $usersAccounts->last();
if ($lastAccount) {
// If we found an account, return the rtype column
return $lastAccount->rtype;
}
// Return something else
return false;
}
Then in your controller you can eager load the relationship:
$users = User::with('accounts')->get(['field_one', 'field_two]);
Then you can do whatever you want with each App\User object, such as calling the isOnline method.
Edit
After some further digging, it seems to be the select on your relationship that is causing the problem. I did a similar thing in one of my own projects and found that no results were returned for my relation. Adding latest seemed to work alright though.
So you should remove the select part at very least in your relation definition. When you only want to retrieve certain fields when eager loading your relation you should be able to specify them when using with like this:
// Should bring back Accounting instances ONLY with rtype field present
User::with('accounts:rtype');
This is the case for Laravel 5.5 at least, I am not sure about previous versions. See here for more information, under the heading labelled Eager Loading Specific Columns
Thanks Jonathon
USER MODEL
public function accounting()
{
return $this->hasMany('App\Accounting', 'userid', 'userid');
}
public function isOnline()
{
$rtype = $this->accounting()
->latest('ts')
->limit(1)
->pluck('rtype')
->first();
if ($rtype == 'Alive') {
return true;
}
return false;
}
CONTROLLER
$builder = App\User::with('accounting:rtype')->ofType($filterRealm);
return $datatables->eloquent($builder)
->addColumn('info', function (App\User $user) {
/*
THIS HAS BEEN SUCCINCTLY TRIMMED TO BE AS RELEVANT AS POSSIBLE.
ARRAY IS USED AS OTHER VALUES ARE ADDED, JUST NOT SHOWN HERE
*/
$info[];
if ($user->isOnline()) {
$info[] = 'Online';
} else {
$info[] = 'Offline';
}
return implode(' ', $info);
})->make();

laravel eloquent left join not outputting

I'm trying to achieve this
SELECT *
FROM pending
LEFT JOIN users ON pending.user_id = users.id
WHERE pending.school_id = '1'
Which produces two results of the two tables combined with the users details output.
Pending Model
public function users(){
return $this->belongsTo('User');
}
public function school(){
return $this->belongsTo('School');
}
User model
public function pending(){
return $this->hasMany('Pending','user_id');
}
School Model
public function pending(){
return $this->hasMany('Pending','school_id');
}
Controller
$pending_user = Pending::with('users')->where('school_id', '=', '1')->get();
This retrieves an array of the two records but a null result in the user array.
"users":null
Any help appreciated.
I have decided to go with this
$pending_user = Pending::select('first_name','last_name','user_id','how')->where('school_id', '=', '1')->leftjoin('users','pending.user_id','=','users.id')->get();

Resources