How to pluck unique relationships from query builder? - laravel

I need to pluck unique set of relation which are present in query set of models. I have model User with table 'users' and model Role with table 'roles', User hasMany Role.
//User.php
public function roles()
{
return $this->belongsToMany(Role::class);
}
//Role.php
public function users()
{
return $this->belongsToMany(User::class);
}
I managed it using collections, but it needs to run such a big query, what slowing whole request
//Controller
...
$users = User::query()->someChainOfScopes();
$uniqueRoles = $users->get()->pluck('roles')->flatten(1)->unique('id')->values();
...
This code returns collection which I need, but I would like to implement it using query builder to pluck unique roles for speed improvement

There are a few different ways you could do this using Laravel,
Joins could be an option to join the 2 tables in a DB::table() query and then find the users using the parameters you require and return the role_id.
To speed up users
$user_ids = DB::table('users')->select('id')->someChainOfScopes()->get();
The chained scopes you will have to change to standard where functions etc.
Then use this array to query the role_user table direct
$role_ids = DB::table('role_user')
->select('role_id')
->whereIn('user_id', $user_ids)
->distinct()
->get();
The problem with collections and plucking from collections is it has to iterate over the whole array of objects just to pull a field out. When that is a large collection then iis very costly.
I've not tested but hopefully it will get you going in the right direction.

Related

Laravel eloquent for four tables

I'm new to Laravel. I am developing a project. and in this project I have 4 tables related to each other
-Users
-Orders
-OrderParcels
-Situations
When listing the parcels of an order, I want to get the information of that order only once, the user information of that order once again, and list the parcels as a table under it. so far everything ok. but I also want to display the status of the parcels listed in the table as names. I couldn't add the 4th table to the query. do you have a suggestion? I'm putting pictures that explain the structure below.
My current working code is
$orderParcels = Orders::whereId($id)
->with('parcels')
->with('users:id,name')
->first();
and my 'orders' model has method
public function parcels(){
return $this->hasMany(OrderParcels::class);
}
public function users(){
return $this->hasOne(User::class,'id','affixer_id');
}
Note[edit]: I already know how to connect like this
$orderParcels = DB::table('order_parcels as op')
->leftjoin('orders as o','op.orders_id','o.id')
->leftjoin('users as u','o.affixer_id','u.id')
->leftjoin('situations as s','op.status','s.id')
->select('op.*','o.*','u.name','s.situations_name')
->where('op.orders_id',$id)->get();
but this is not working for me, for each parcels record it returns me orders and user info. I want once orders info and once user info.
Laravel provides an elegant way to manage relations between models. In your situation, the first step is to create all relations described in your schema :
1. Model Order
class User extends Model {
public function parcels()
{
return $this->hasMany(OrderParcels::class);
}
public function users()
{
return $this->hasOne(User::class,'id','affixer_id');
}
}
2. Model Parcel
class Parcel extends Model {
public function situations()
{
return $this->hasOne(Situation::class, ...);
}
}
Then, you can retrieve all desired informations simply like this :
// Retrieve all users of an order
$users = $order->users; // You get a Collection of User instances
// Retrieve all parcels of an order
$parcels = $order->parcels; // You get a Collection of User instances
// Retrieve the situation for a parcel
$situations = $parcel->situations // You get Situation instance
How it works ?
When you add a relation on your model, you can retrieve the result of this relation by using the property with the same name of the method. Laravel will automatically provide you those properties ! (e.g: parcels() method in your Order Model will generate $order->parcels property.
To finish, in this situation where you have nested relations (as describe in your schema), you should use with() method of your model to eager load all the nested relation of order model like this :
$orders = Orders::with(['users', 'parcels', 'parcels.situations'])->find($id)
I encourage you to read those stubs of Laravel documentation :
Define model relations
Eager loading
Laravel Collection
Good luck !
Use join to make a perfect relations between tables.
$output = Orders::join('users', 'users.id', '=', 'orders.user_id')
->join('order_parcels', 'order_parcels.id', '=', 'orders.parcel_id')
->join('situations', 'situation.id', '=', 'order_parcels.situation_id')
->select([
'orders.id AS order_id',
'users.id AS user_id',
'order.parcels.id AS parcel_id',
'and so on'
])
->where('some row', '=', 'some row or variable')->get();

How can I return an array of Laravel Eloquent model objects from a query that requires left join?

There are two tables, "users" and "posts".
Table "posts" has "user_id" as a foreign key.
How to write this equivalent in Eloquent, in a way that returns an array called $posts which is not simple PHP array but the Post class model objects?
SELECT posts.*
FROM posts
LEFT JOIN users
ON posts.user_id = users.id
ORDER BY users.registered_at, posts.created_at;
In other words, I want to return an array of model objects and not just flat data that comes from raw SQL.
if you have setup proper relationships as suggested above, you can get your user model then get posts through that user.
Your user model should have a function like:
public function users(){
return $this->hasMany(Post::class);
}
You could probably do something like this:
$posts = Posts::load('users')
->orderBy('users.reregistered_at')
->orderBy('users.some_column')
->get();
This should return a collection of posts with their relations and orderBy what you need

create whare clause on model attribute

How to create where clause on laravel model attribute
I have the following relation between user and books. where user hasMany books and the book model belongs to single user
I want to select all books with pages > 100 that belongs to user_id = 2
I use laravel 5.2 with mysql and defined a model for User and another model for Book
When I want to get all books for specific user, I user
return User::find(2)->books;
and this works fine. But I want to get the books where pages > 100. I use:
return User::find(2)->books->where([['pages', '>', 100], ['chapters', '>', 3]]);
but doesn't work
User model:
class User extends Authenticatable
{
public function books()
{
return $this->hasMany('App\Book');
}
}
Book model
class Book extends Model
{
public function user()
{
return $this->belongsTo('App\User');
}
}
I expect to get all the books with user_id = 0 and pages > 100 and chapters > 3
You'll want to access the relationship method books(), not the property books. Some quotes from the documentation:
Querying Relations
Since all types of Eloquent relationships are defined via methods, you may call those methods to obtain an instance of the relationship without actually executing the relationship queries. In addition, all types of Eloquent relationships also serve as query builders, allowing you to continue to chain constraints onto the relationship query before finally executing the SQL against your database.
Relationship Methods Vs. Dynamic Properties
If you do not need to add additional constraints to an Eloquent relationship query, you may access the relationship as if it were a property.
Example solution:
return User::find(2)
->books()
->where([['pages', '>', 100], ['chapters', '>', 3]])
->get();
Note that we're using books() to access the HasMany relationship, and using the where() query builder method on it to create a constraint. Then, in order to complete and execute the query, we call get() at the end. If you don't, you'll just return the daisy chained query builder.
Your previous code might not error, because the value returned from User::find(2)->books will be a Collection object, which actually has a where() method. It just likely didn't have any matches because of the array format you passed.
To add where clouses on related models, we can use whereHas() function.
Since you want Books, Start from Book model.
$books = Book::where([
['pages', '>', 100],
['chapters', '>', 3],
])
->whereHas('user', function($query) {
$query->where('id', 2);
})
->get();

Complicated Eloquent relationship using `Model::query`

I have a complicated relationship I'm trying to establish between two models.
The goal is to use $supplier->supply_orders to access the orders where the user supplies an item.
This throws: LogicException: Relationship method must return an object of type Illuminate\Database\Eloquent\Relations\Relation.
With the code I've got I can use $supplier->supply_orders()->get(), however, when I try to use it as a relationship it throws. Since this is a relationship I should be able to wrap it in a relationship, but how would I go about doing that?
Supplier Model:
class Supplier extends Model {
public function supply_orders() {
return Order::query()
->select('order.*')
->join('item_order', 'order.id', '=', 'item_order.order_id')
->join('item', 'item_order.item_id', '=', 'item.id')
->where('item.supplier_id', '=', $this->id);
}
}
~~~ A whole lot of back info that I don't think you need but might ~~~
sql tables:
supplier
- id
items:
- id
- supplier_id
item_order:
- id
- order_id
- item_id
orders:
- id
The other Eloquent Models:
class Item extends Model {
public function orders() {
return $this->belongsToMany('Order');
}
}
class Order extends Model {}
Example of how this should work:
$supplier = factory(Supplier::class)->create();
$item = factory(Item::class)->create([
'supplier_id' => $supplier->id,
]);
$order = factory(Order::class)->create();
$order->items()->attach($item);
$orders = $supplier->supply_orders // Throws LogicException
This throws: LogicException: Relationship method must return an object of type Illuminate\Database\Eloquent\Relations\Relation
Sounds like a hasManyThrough with a many to many relationship. Laravel has no inbuilt support for this but you can always go ahead and write your own relationship like this: https://laravel.io/forum/03-04-2014-hasmanythrough-with-many-to-many
If you dont want relationships you can always do something like:
Order::whereHas('items.supplier', function($query) use($supplier) {
$query->where('id', $supplier->id);
});
For this to work, you need to have a relationship function items in your Order model and a relationship function supplier in your item model
I believe the reason it throws a relationship error is that you haven't created an Eloquent relation for
$supplier->supply_orders.
Instead, Laravel looks at your supply_orders() as a method in the class, and thus can't figure out which table to use as the pivot. To get the base relationship to work within Eloquent, you'd need to create a new pivot table for the relationship between suppliers and orders something like:
suppliers
-id
orders
-id
order_supplier
-id
-order_id
-supplier_id
From here, Laravel will accept a simple many to many relationship between the two (this would not cause a failure):
Supplier Class:
/**
* Get all orders associated with this supplier via order_supplier table
*
* #return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
public function orders(){
return $this->belongsToMany("\App\Order");
}
Now that the relationship is solid both between the suppliers and orders, as well as the orders and items, you can eager load the relationship in all directions. Where it gets complicated for your particular need with the current DB setup is that you have a 3rd parameter from the items table that is not a direct pivot. Without having to re-structure the DB, I think the easiest would be to load your suppliers and the relationships like normal:
$suppliers = Supplier::with('orders', function($query) {
$query->with('items');
});
From here you've got all the relationships loaded and can draw down the ones with the right item->ids in a follow-up to the $suppliers collection. There are quite a few ways to skin the cat (even including all in one query) now that you have the Eloquent relationship... but I tend to keep it a little more simple by breaking it into a few readable bits.
Hope this helps.

Laravel: return multiple relationships

I have the following table:
The table is called user_eggs and it stores the user eggs.
eggs are items with additional data (hatch_time)
As you can see, user 2 has 2 eggs, which are items 46 and 47.
My items table stores the item general information such as name, image, description, etc...
How I can return the user eggs using $user->eggs() including the item data in my items table of the egg item_id?
I tried:
User Model:
/**
* Get the eggs
*/
public function eggs()
{
return $this->belongsToMany(Egg::Class, 'user_eggs','user_id','item_id')
->withPivot('id','hatch_time');
}
but $user->eggs() returns an empty array.
Any ideas?
A simple approach will be:
in your UserEgg model define:
/**
* Get the user associated with egg.
*/
public function _user()
{
return $this->belongsTo('App\User','user_id');
}
/**
* Get the item associated with egg.
*/
public function item()
{
return $this->belongsTo('App\Item','item_id');
}
then in your controller:
use the model to extract everything like this:
$userEggs = UserEgg::where('user_id',2)->get();
foreach($userEggs as $userEgg){
$associateduser = $userEgg->_user;
$associatedItem = $userEgg->item;
}
Short answer
If you loop through the user's eggs:
foreach($user->eggs as $egg){
$item = Item::find($egg->pivot->item_id);
}
If you want to query:
$user->eggs()->wherePivot('item_id', 1)->get();
Long answer
From the Laravel Documentation
Retrieving Intermediate Table Columns
As you have already learned, working with many-to-many relations requires the presence of an intermediate table. Eloquent provides some very helpful ways of interacting with this table. For example, let's assume our User object has many Role objects that it is related to. After accessing this relationship, we may access the intermediate table using the pivot attribute on the models:
$user = App\User::find(1);
foreach ($user->roles as $role) {
echo $role->pivot->created_at;
}
Notice that each Role model we retrieve is automatically assigned a pivot attribute. This attribute contains a model representing the intermediate table, and may be used like any other Eloquent model.
By default, only the model keys will be present on the pivot object. If your pivot table contains extra attributes, you must specify them when defining the relationship:
return $this->belongsToMany('App\Role')->withPivot('column1', 'column2');
If you want your pivot table to have automatically maintained created_at and updated_at timestamps, use the withTimestamps method on the relationship definition:
return $this->belongsToMany('App\Role')->withTimestamps();
Filtering Relationships Via Intermediate Table Columns
You can also filter the results returned by belongsToMany using the wherePivot and wherePivotIn methods when defining the relationship:
return $this->belongsToMany('App\Role')->wherePivot('approved', 1);
return $this->belongsToMany('App\Role')->wherePivotIn('priority', [1, 2]);

Resources