I have three tables in my database: Users, Books, and Reading. The structure of Reading is:
id, user_id, book_id
Now, I need to access to all the books that the user is reading.
First, I would like to know which kind of relationship is, one to many, many to many, Has Many Through, etc. And what do I have to specify in each model? I mean, in the users model hasMany('Reading') or whatever.
I want to access with something like this: Auth::user()->reading()->books()->get() or something similar.
The kind of relationship where one User can be reading several books and one Book can be read by many users is Many-to-Many relationship.
So, to define such a relation in Laravel you should use belongsToMany method:
class User extends Eloquent
{
function books()
{
return $this->belongsToMany("Book", "Reading", "user_id", "book_id");
}
}
class Book extends Eloquent
{
function users()
{
return $this->belongsToMany("User", "Reading", "book_id", "user_id");
}
}
$books = Auth::user()->books;
Notice, you don't need to call method ->books() but use a property ->books, otherwise you'd have to call it like Auth::user()->books()->get().
Related
In my app, you can create lists of roles that are attached to contacts. So you can assign the contact "Bob" the roles of "Gardener" and "Pet Sitter". Then you can create the list "People" and add "Gardener (Bob)" and "Pet Sitter (Bob)" to it.
I have the following tables:
contacts
id
name
roles
id
name
contact_role (pivot)
id
contact_id
role_id
lists
id
name
contact_role_list (pivot)
id
contact_role_id
list_id
Everything was working smoothly until the second pivot table linked to the first pivot table. My pivot tables are (currently) not having any models so I'm not sure if there is a built-in feature to tackle this in Laravel or if I need to think differently.
I currently have this in my List model:
public function list_roles(): BelongsToMany
{
return $this->belongsToMany(XYZ::class, 'contact_role_list', 'list_id', 'contact_role_id');
}
Is this even close? What do I put where it says XYZ::class?
Ok, so the below is doing what I want, but is there an even better way to do it? The key to solving my problem was to create a Model for ContactRole and changing extends Model to extends Pivot.
I placed this in my List Model:
public function list_roles(): BelongsToMany
{
return $this->belongsToMany(ContactRole::class, 'contact_role_list', 'list_id', 'contact_role_id');
}
And this in my ContactRole Model:
public function contact(): BelongsTo
{
return $this->belongsTo(Contact::class);
}
Now I could reach the contact data by using something like this: List::first()->contact_roles->first()->contact
Any way to use with, pivot or similar to tidy this up even more? Thanks!
I like to approach these issues in terms of Models rather than pivots. I think many new Developers in Laravel get over obsessed with what's going on in the Database which is fine, but theres a lot of Magic going on so you can write very simple code that does a lot of Heavy lifting, so that being said if I fully understand your problem
You have a Contacts Model
This model can have many roles
so in your contacts Model you need a role relationship
public function roles()
{
return $this->belongsToMany(Roles::class);
}
next of course you have a role Model (pun intended)
your each role can have many list
public function lists()
{
return $this->hasMany(List::class)
}
then the idea is now that you have roles on contacts and lists on roles you should be able to have many lists through contact
public function lists()
{
return $this->hasManyThrough(List::class, Role::class);
}
I've done similar things before and for your description it seems like that's the approach you might need to take.
I have a problem with Laravel relationships.
I need to make relationship based on this table:
issuer and friend need to be united. Relationship will return all rows where user id in issuer or in friend. At the moment code looks like this:
return DB::table('contacts')->select()->where('friend', $this->id)->orWhere('issuer', $this->id)->where('status', 'approved');
Previously I used that method, but there are no relationship, 'cause attach() is undefined.
private function contactsIssued() {
return $this->belongsToMany(User::class, 'contacts','issuer', 'friend');
}
private function contactsFriended() {
return $this->belongsToMany(User::class, 'contacts','friend', 'issuer');
}
public function contacts() {
return $this->contactsIssued()->union($this->contactsFriended()->toBase());
}
So, I need to make one relationship that has two foreign columns.
Sorry, my English can be broken, 'cause it's not my native language.
That's look like a one to many relationship but you are using belongsToMany which is many to many relationship and required a pivot table.
if you have user model that has many contact model then in your contact model you should use the one to many relationship by using belongsTo.
but if you have 3 table you didn't say that in you question then please edit you question and provide your models and the relations between them.
Laravel 7.0
I am trying to set up a system where users can belong to
different models, like Courses or Realms. I call this a
'membership'.
A user who is a member in a Realm or Course always has a
Role within that Realm, like 'amdin', 'editor', or 'guest'
I have models for User, Role, Memberable and the Course/Realm stuff.
Like this:
Role ⇦1:N⇦ Memberable ⇨N:1⇨ User
⇩
poly 1:1
⇩
Realm/Course/YouNameIt
At the moment I am using a wild mix of relations to get
more or less what I want. But my approach seems rather
gross to me, or even wrong.
For example, to get a list of models that I am a member of, I write:
User::find(2)->memberships()->get()
But this gives me a Collection of Memberables, not Realms or
Courses, which kind of sucks. Can this be remedied?
To get my first Realm/Course, I have to write:
User::find(2)->memberships()->first()->memberable // YUCK!
Here are my models:
class User {
public function memberships()
{
return $this->hasMany(Memberable::class)->with('memberable');
}
public function roles()
{
return $this->belongsToMany(Role::class, 'memberables')
->withPivot(['memberable_id', 'memberable_type']);
}
}
class Memberable extends Model
{
public function role()
{
return $this->hasOne(Role::class, 'id', 'role_id');
}
public function memberable()
{
return $this->morphTo('memberable');
}
}
trait HasMembers
{
public function members()
{
return $this->morphToMany(User::class, 'memberable', 'memberables')
->withPivot('role_id')
->join('roles', 'roles.id', 'memberables.role_id')
->addSelect(['role' => Role::select('name')
->whereColumn('role_id', 'roles.id')]);
}
}
class Realm extends Model
{
use HasMembers;
}
class Course extends Model
{
use HasMembers;
}
I know that questions like "is this good" or "is this bad" are difficult to answer, so I'll ask:
Is this so wrong that I should worry?
How could I improve this design?
I'll probably never have more Models that Realms and Courses that can have members. Is it smarter to ditch the whole memberable-polymorphism in favour of two separate tables? It IS nice to keep the flexibility the polymorphic approach offers.
You could just set up more relationships
User belongsToMany Roles
Roles belongsToMany Users
User morphedByMany Realm using Memberable
User morphedByMany Courses using Memberable
User morphedByMany YouNameIt using Memberable
Realm morphToMany User using Memberable
Courses morphToMany User using Memberable
YouNameIt morphToMany User using Memberable
I'd make Memberable a morph pivot.
class Memberable extends MorphPivot { ... }
With this, accessing the relationships isn't hard.
$user->roles // Get an Eloquent Collection made up from $user's roles.
$role->users // Get an Eloquent Collection made up from users that have $role.
$user->realms // Get an Eloquent Collection made up from $user's realms.
$user->courses // Get an Eloquent Collection made up from $user's courses.
$user->youNameIt // Get an Eloquent Collection made up from $user's youNameIt.
$realm->users // Get an Eloquent Collection made up from users associated with $realm.
$course->users // Get an Eloquent Collection made up from users associated with $course.
$youNameIt->users // Get an Eloquent Collection made up from users associated with $youNameIt.
And maybe make User <---> Roles relationship use a separate pivot. If not, then make it a morph relationship as well (User morphedByMany Role using Memberable, Role morphToMany User using Memberable)
The thing is, if you use a central table to morph everything up, it will grow a lot. You're essentially making it do the job of 3-4 pivots (in this example). If your data grows a lot, it might become a problem.
Also, different relationships might need different data from the pivot table but since you're only using a single table, that would complicate things unless you opt to use a json field.
I have a couple of models that I have included pivot tables for to avoid a polymorphic relation.
role table
id
name
description
restriction table
id
rule
status
restriction_role table
id
restriction_id
role_id
Reason for this setup is that both Roles and Restrictions can actually belong to multiple other models. In this case, a Role can only have 1 Restriction. I would normally define this as
class Role extends Eloquent {
public function restrictions()
{
return $this->hasOne('Restriction');
}
}
This obviously does not work because Laravel is unaware of the pivot table connecting this relation. I could easily accomplish this by using a many-to-many relationship instead, but this is not exactly how my model works. Not seeing anything in the documentation for defining this. Any thoughts?
I've solved this by using the belongsToMany relation and defining a custom accessor for it, like so:
public function foo()
{
return $this->belongsToMany(App\Bar::class);
}
public function getFooAttribute()
{
return $this->foo()->first();
}
As #deczo stated in the comments, belongsToMany() is about all that will work here. I recommend returning the first result using the first() method if you require only one result but cannot use a hasOne() relationship.
I've been working with PHP for years but have never really ventured out of procedural programming except when working with things like IPB and Magento. I'm trying to advance to the next level and get a better understanding of application structures, OOP, and some common PHP frameworks. That being said, I apologize if my question sounds immature or technically incorrect, I'm new to all of this.
Anyway, I was thinking about the structure of a simple forum. Forgetting about categories, tags, users, roles, advanced editors/bbcode, etc for now and just focusing on the topics and posts...
Because a topic is essentially a series of linked posts ordered by their created_at column, is there a necessity for an actual topics table or could one not simply have a parent column in the posts table? Topics would be identified as posts with a parent equal to their own id, null or 0; something that would otherwise be unused.
If that were the db schema, how would it be laid out in the code, and if relevant, Laravel? Could you still create a Topic model? What would be the pros and cons to having two models working from a single table?
Lastly, how would you approach it if you were creating it? Would you use two tables? A pivot table? Something else? Please explain why you would implement it that way.
For the database design, self referencing tables are a valid design pattern and useful in cases of nested hierarchies such as Categories that can contain sub-categories that can also contain sub-categories ect ect... In this case sub-categories are categories that have a parent but there is no other distinction between them.
It's up to you to decide if a Topic and Post is an identical entity with a parent-child relationship. Personally the way I define the objects I don't feel they are.
The topic-post relationship you're describing is probably more of a One to Many relationship with the topic being the owner or maybe even a Many to Many relationship. This depends on the answer to, "can your topic have many posts? Can your posts be part of many topics?"
If you answered yes and no, then it is a One to Many with topics being the parent aka owner in the relationship.
If you answered yes and yes, then you have a Many to Many relationship. In SQL these are represented by a table with two columns that reference id's from two tables.
If you answered no and yes, then you have a One to Many with posts being the parent aka owner in the relationship.
In laravel, depending on the relationship, your models would include a method that looks like this:
One to Many:
class Topic extends Eloquent
{
public function posts()
{
return $this->hasMany('Post');
}
}
Laravel One-to-Many Relationships
Many to Many:
In laravel the term "pivot table" refers to the table with references to the other objects.
class Post extends Eloquent
{
public function topics()
{
return $this->belongsToMany('Topic');
}
}
class Topic extends Eloquent
{
public function posts()
{
return $this->belongsToMany('Post');
}
}
Laravel Many-to-Many
Self referencing model:
For a self referencing parent child relationship like I explained before you could create something like this, as you can see it's just a one-to-many and the many-to-one in the same model.
class Category extends Eloquent
{
public function parent()
{
return $this->belongsTo('Category', 'parent_id');
}
public function children()
{
return $this->hasMany('Category', 'parent_id');
}
}
There is also the Polymorphic Relation.
This is useful when you have the same entity with just a different type. For example in this table you can have an insurance policy for an employee and a manager. The personType column in the insurancePolicies table defines who the insurance policy belongs to.
Image from codecommit.com
Our laravel models in this case would look like:
class InsurancePolicy extends Eloquent
{
public function insurable()
{
return $this->morphTo();
}
}
class Manager extends Eloquent
{
public function insurance()
{
return $this->morphMany('InsurancePolicy', 'person');
}
}
class Employee extends Eloquent
{
public function insurance()
{
return $this->morphMany('InsurancePolicy', 'person');
}
}
Most everything of what I've explained can also be found in the laravel docs