Using Laravel, I'm having some trouble accessing my other tables, which are all many to many.
So basically, I start out with the user id and want to list the customers that user has.
public function customers()
{
return $this->belongsToMany('Customer', 'user_to_customer');
}
This works, assume my user id is 1:
User::find(1)->customers;
However now I want to say, for each of these customers, list their products. However this needs to be within the same result.
I guess I would need something within the Customer model, such as:
public function products()
{
return $this->belongsToMany('Product', 'user_to_customer');
}
I can't seem to work out how to access this within the same query? Something like:
User::find(1)->customers->products;
Not sure.. any suggestions?
You can look into eager loading to accomplish this behavior. Given the following model relationships:
class User extends Eloquent {
public function customers()
{
return $this->has_many( 'Customer' );
}
}
class Customer extends Eloquent {
public function products()
{
return $this->has_many( 'Product' );
}
}
class Product extends Eloquent {}
The following query will return all products belonging to customers belonging to a specific (in this case, first) user:
User::with(array('customers', 'customers.products'))->first();
Related
Laravel version:7.0
reviews table (Model - Review) has id, product_type, product_id, rating columns.
product_type can be service, plugin, module and each value has own model App\Service, App\Plugin, App\Module. I could put model names directly in product_type but I prefer to use those values.
Here is Review model relationship.
public function plugin()
{
return $this->belongsTo(Plugin::class, "product_id")->withDefault();
}
public function module()
{
return $this->belongsTo(Module::class, "product_id")->withDefault();
}
public function service()
{
return $this->belongsTo(Service::class, "product_id")->withDefault();
}
public function getItem()
{
if($this->product_type=='module')
{
return $this->module;
}elseif($this->product_type=='service')
{
return $this->service;
}else {
return $this->plugin;
}
}
Now I want to get them with eager loading in Review Model as following:
$reviews = Review::with("getItem")->get();
Without Eager loading, I could use $review->getItem()->name // this returns name of product.
How can I get them with eager loading?
You could have implemented this easily as a polymorphic relationship. In your Reviews Model, you could do this:
Model Structure
App\Review.php
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Review extends Model
{
public function reviewable()
{
return $this->morphTo();
}
}
Then add reviews() method to your App\Service, App\Plugin and App\Module models
public function reviews()
{
return $this->morphMany('App\Review', 'reviewable');
}
Table Structure
You reviews table could look like this:
reviews
id - integer
body - text
reviewable_id - integer
reviewable_type - string
Note the reviewable_id and reviewable_type fields. The reviewable_id stores the id of the item reviewed and the reviewable_type stores the model related to the item.
Retrieving The Relationship
You may access the relationships via your models. For example, to access all of the reviews for a service, we can use the reviews dynamic property:
$service = App\Service::find(1);
foreach ($service->reviews as $review) {
//
}
You may also retrieve the owner of a polymorphic relation from the polymorphic model by accessing the name of the method that performs the call to morphTo. In your case, that is the reviewable method on the Review model. So, we will access that method as a dynamic property:
$review = App\Review::find(1);
$reviewable = $review->reviewable;
The reviewable will return the model on the Review model either Service, Plugin or Module
I have three tables, a user table, documents table, and a favourites table. The idea is a user can favourite a document, but I can't understand the best way to query this using Eloquent.
User.php
class User extends Authenticatable
{
public function documents()
{
return $this->hasMany('App\Document');
}
public function favourites()
{
return $this->hasMany('App\Favourite');
}
}
Document.php
class Document extends Model
{
public function user()
{
return $this->belongsTo('App\User');
}
}
Favourite.php
class Favourite extends Model
{
public function user()
{
return $this->belongsTo('App\User');
}
public function document()
{
return $this->belongsTo('App\Document');
}
}
The favourite table is a simple 2 column table with the user_id and the document_id linking each user to an article they have favourited.
Now I can use a method to get the users favourite articles like so:
App\User::with('favourites')->find(1);
The problem is this brings back the two id's from the favourites table when I want the data from the documents table such as the title and id of the document.
It looks like the "has-many-through" relationship is what I might need to achieve this query, but I'm unsure how to implement it in this use case or even if the "has-many-through" relationship is the correct way to do this?
As your relationship is being setup, you can have a user that has multiple documents, and he can also make multiple documents to be his favorites. So it will always return an array. In order to load all the documents for the user that are his favorites, you can do that the same as you started:
$favoriteDocuments = App\User::with('favourites.document')->find($userId = 1)->get();
// this will contain all the favorite documents for the user, so you can the iterate over them:
foreach($favoriteDocuments as $favoriteDocument)
{
// $favoriteDocument->document; is the object you are looking for.
}
Has many through relationship is used in order to get item from a table to which you don't have access to directly. But to both of your tables you have direct connection to the user.
You are correct with has-many-through, so on User:
return $this->hasManyThrough('App\Document', 'App\Favourite');
And on Document:
return $this->hasManyThrough('App\User', 'App\Favourite');
Eloquent Docs
Your favorites table is a Pivot table. You don't need a favorites model.
User.php
public function favoriteDocuments()
{
return $this->BelongsToMany('App\Document', 'favorite_documents');
}
Now you can call $user->favoriteDocuments to get the users documents.
See the docs about many to many relationships. https://laravel.com/docs/5.7/eloquent-relationships#many-to-many.
I have a model called CallbackRequest the model has a relationship with Loan model and that is the only relationship for CallbackRequest model.
CallbackModel:
public function loan() {
return $this->belongsTo(Loan::class);
}
Now Loan model itself has a relationship with a third model called Applicant.
Loan Model:
public function applicant() {
return $this->belongsTo(Applicant::class);
}
My point:
When I load CallbackRequest I eagerload loan model with it, all fine! But now I am wondering if there is a way to eagerload applicant model when I do:
Right now I access it like:
$modelResults = PublicCallback::with('loan')->get();
I get all callbacks with loan eagerloaded, but my point is I want when I eagerload loans in this case I want applicant to be loaded also !
Is there any way how to do this, is it possible ?
You can do this with:
$modelResults = PublicCallback::with(['loan', 'loan.applicant'])->get();
Ref: https://laravel.com/docs/5.5/eloquent-relationships#eager-loading
Just for posterity, there's also another way of loading nested relationships that can be done against a returned model, provided you have set up the relationships correctly:
Posts model:
public function comments() {
return $this->hasMany('App\Comment', 'quote_id', 'id');
}
Comments model:
public function user() {
return $this->belongsTo('App\User');
}
Then you can actually infer the user via relationship to a comment by drawing the post but loading an array of relations, eg:
$post = \App\Post::find($post_id);
return $post->load(['comments','comments.user']);
I have four tables which is departments, users, items, items_inventories
The relationship is like this:
A user has a assigned department.
An item has a assigned department. item_inventories has many items.
Structure:
users
->id
->name
->password
->access_type (department_id)
departments
->id
->name
items
->id
->name
->department_id
items_inventories
->id
->item_id
->qty
My models:
class Item extends Model
{
public function department()
{
return $this->hasOne('App\Http\Models\Department', "id", "department_id");
}
}
class ItemsInventory extends Model
{
public function item()
{
return $this->hasOne('App\Http\Models\Item', "id", "item_id");
}
}
In my items_inventories how do I query all items that belongs to a specific department? Since items has already a relationship to departments, How do I query like: select all items in items_inventories where item department_id is equal to 3?
My goal is, I have a user who is logged in, and I can access the assigned department to him/her via access_type (department_id) when my page loads, I want to list only items in the items_inventories that is assigned to his/her department. I already checked: https://laravel.com/docs/5.4/eloquent-relationships#relationship-methods-vs-dynamic-properties but can't seem to find something that matches my requirement. Thanks
Your relationships are a bit confusing. The table structure says that items belong to departments and item inventories belong to items. I've based the relationship on your table structure and how you can achieve your desired result. You might want to check on your relationship once more to verify how exactly you want it to pan out. As for the current relationship, my models should give you an idea.
class User extends Model
{
public function department()
{
return $this->belongsTo('App\Http\Models\Department', 'access_type');
}
}
class Department extends Model
{
public function items()
{
return $this->hasMany('App\Http\Models\Item');
}
public function itemInventory()
{
return $this->hasManyThrough('App\Http\Models\ItemsInventory', 'App\Http\Models\Item');
}
}
class Item extends Model
{
public function department()
{
return $this->belongsTo('App\Http\Models\Department');
}
public function itemInventory()
{
return $this->hasMany('App\Http\Models\ItemsInventory');
}
}
class ItemsInventory extends Model
{
public function item()
{
return $this->belongsTo('App\Http\Models\Item');
}
}
Controller logic
$department_id = 3;
$itemInventory = ItemsInventory::whereHas('item', function ($query) use ($department_id) {
$query->where('department_id', $department_id);
})->get();
// With user:department relation and department:iteminventory 'hasManyThrough' relation.
$itemInventory = $user->department()->itemInventory;
I have a model Page and many models called SomethingSection - they're connected through a polymorphic m-m realtionship and the pivot has an additional column 'position'.
I need to write a relationship (or accessor maybe?) on the Page model that will return a collection of all connected Sections, regardless of their model (read: table).
My models:
class Page extends Model {
public function introSections()
{
return $this->morphedByMany(IntroSection::class, 'pagable');
}
public function anotherSections()
{
return $this->morphedByMany(AnotherSection::class, 'pagable');
}
}
class IntroSection extends Model {
public function pages()
{
return $this->morphToMany(Page::class, 'pagable');
}
}
class AnotherSection extends Model {
public function pages()
{
return $this->morphToMany(Page::class, 'pagable');
}
}
The pivot column looks like this:
pagables
-page_id
-pagable_id
-pagable_type
-position
I'm looking for a way to call a method/attribute on the Page model and get all the connected sections in a single collection, sorted too. What would be a good way to go about this?
I understand that the connected sections do not have the same interface, but in my case that's not a problem at all (in terms of what I will do with the data).
I also understand that relationships perform a separate query (for each relationship), so getting all of them with 1 query is impossible (also different interfaces would be a problem here). And for the same reason the sorting will need to be done on the collection level, not in query.
How could I make this as maintainable as possible and preferably with as small a performance hit as possible.
Thanks in advance.
You can use withPivot() method after your relationship to get the pivot columns with relation like this:
class Page extends Model {
public function introSections()
{
return $this->morphedByMany(\HIT\Models\Sections\IntroSection::class, 'pagable')
->withPivot(['position']);
}
public function anotherSections()
{
return $this->morphedByMany(AnotherSection::class, 'pagable');
}
}
class IntroSection extends Model {
public function pages()
{
return $this->morphToMany(Page::class, 'pagable')
->withPivot(['position']);
}
}
and you can use collection's sortBy to sort the collection by using sortBy() method like this:
$sorted_collection = IntroSection::pages->sortBy('pagables.position');
UPDATE:
You can use collection's combine() method to get all the relationships like this, add this method inside your Page Class:
public function getAllSections()
{
return $this->introSections->combine($this->anotherSections-toArray())
->sortBy('pagables.position'):
}
Hope this helps!