Laravel Many To Many Relationship between Three Models - laravel

I want to create a relationship between the three models. My models are
Users
-- Database Structure
id
name
---
Books
-- Database Structure
id
name
BookShelf
-- Database Structure
id
name
1: User has many books and book belongs to many users
2: User's book belongs to one BookShelf and user's BookShelf has many books.
How can I define the relationship between these three models? I am building an application something like Goodreads.

You have a many to many relationship between your User and Book, as well as a many to many between your Book and BookShelf.
This type of relationship is managed by a pivot table behind the scenes, so you will require two pivot tables.
Pivot between User and Book
Create your migration
php artisan make:migtration create_book_user_table
Define your relationship
public function up()
{
Schema::create('book_user', function (Blueprint $table) {
$table->id();
$table->timestamps();
$table->foreignId('user_id')->constrained();
$table->foreignId('book_id')->constrained();
});
}
Pivot between Book and BookShelf
Create your migration
php artisan make:migtration create_book_book_shelf_table
Define your relationship
public function up()
{
Schema::create('book_user', function (Blueprint $table) {
$table->id();
$table->timestamps();
$table->foreignId('book_id')->constrained();
$table->foreignId('book_shelf_id')->constrained();
});
}
With the pivot tables create you can add the relationships to your models.
User
public function books()
{
return $this->belongsToMany(Book::class);
}
Book
public function users()
{
return $this->belongsToMany(User::class);
}
public function bookshelves()
{
return $this->belongsToMany(BookShelf::class);
}
BookShelf
public function books()
{
return $this->belongsToMany(Book::class);
}
Now you have your relationships, you can access them on your User. As an example:
Route::get('/', function () {
$user = User::where('id', 1)->with(['books', 'books.bookshelves'])->first();
return view('user', compact('user'));
});
user.blade.php
<h3>{{ $user->name }}</h3>
<h4>Books</h4>
#foreach ($user->books as $book)
<h5>{{ $book->name }}</h5>
<p>Can be found on #choice('shelf|shelves', $book->bookShelves()->count())</p>
#foreach ($book->bookShelves as $shelf)
<li>{{ $shelf->name }}</li>
#endforeach
#endforeach
The above would iterate over each Book for the $user and then iterate over each of the BookShelves that Book is related to.
Update
If a Book can only belong to one Bookshelf, you need to alter your books table.
public function up()
{
Schema::create('books', function (Blueprint $table) {
$table->id();
$table->timestamps();
$table->string('name');
$table->foreignId('bookshelf_id')->constrained('book_shelves');
});
}
You will also need to alter the relationship on your Book model:
public function bookshelf()
{
return $this->belongsTo(BookShelf::class);
}
This way a Book can now only belong to one Bookshelf.
If a User can only have one Bookshelf, again you need to alter your book_shelves table:
public function up()
{
Schema::create('book_shelves', function (Blueprint $table) {
$table->id();
$table->timestamps();
$table->string('name');
$table->foreignId('user_id')->constrained();
});
}
Then add the relationship to your User.
public function bookshelf()
{
return $this->hasOne(BookShelf::class);
}
You should then be able to access the Bookshelf for a User directly.
$user->bookshelf->books

1: User has many books and book belongs to many users
User has a Many To Many relationship with Book
// User model
public function books()
{
return $this->belongsToMany(Book::class, 'book_user');
}
Book has a Many To Many relationship with User
// Book model
public function users()
{
return $this->belongsToMany(User::class, 'book_user');
}
2: User's book belongs to many BookShelf and BookShelf has many books.
Book has a Many To Many relationship with Bookshelf
// Book model
public function bookshelves()
{
return $this->belongsToMany(Bookshelf::class, 'book_bookshelf');
}
Bookshelf has a Many To Many relationship with Book
// Bookshelf model
public function books()
{
return $this->belongsToMany(Book::class, 'book_bookshelf');
}
You'd need two pivot tables.
book_user
column
migration
id
$table->id()
book_id
$table->foreignId('book_id')->constrained('books')
user_id
$table->foreignId('user_id')->constrained('users')
book_bookshelf
column
migration
id
$table->id()
book_id
$table->foreignId('book_id')->constrained('books')
bookshelf_id
$table->foreignId('bookshelf_id')->constrained('bookshelves')

Let's start from first task:
1: User has many books and book belongs to many users
In this case it is many-to-many relationships. To implement this setup you need users table like this:
Users: id, name, someotherdata
then you need books table:
books: id, name, bookdata
and then you need the third table to connect them many to many:
users_books: id, book_id, user_id
In this case when you need all books which belongs to one user you can select from users_books where user_id=someuserid and join it with books table and you will receive all books which belongs to some user.
Visa versa you can receive all users who posses some particular book.
2: User's book belongs to one BookShelf and user's BookShelf has many books.
For this you would have to create table bookshelfs: id, name
And add foreign key to books table as bookshelf_id
Then your books table will look like this:
books: id, name, bookdata, bookshelf_id.
So when you need to get all books from shelf just filter select from books where bookshelf_id = someid

Related

Laravel How to handle One To Many relationship using Pivot Tables

I have a new Laravel application that I'm hooking up to a preexisting database. The relationships in this database are pretty much all pivot tables.
Similar to this:
Schema::create('customers', function (Blueprint $table) {
$table->id();
$table->string('name');
});
Schema::create('vehicles', function (Blueprint $table) {
$table->id();
$table->string('name');
});
Schema::create('customer_vehicle', function (Blueprint $table) {
$table->id();
$table->int('customer_id');
$table->int('vehicle_id');
});
I've configured models/pivot tables and set up a Many To Many relationship for the customers and vehicles.
Similar to this:
class Customer extends Model
{
public function vehicles()
{
return $this->belongsToMany(
Vehicle::class,
CustomerVehiclePivot::class,
'customer_id',
'vehicle_id'
);
}
}
class Vehicle extends Model
{
public function customers()
{
return $this->belongsToMany(
Customer::class,
CustomerVehiclePivot::class,
'vehicle_id',
'customer_id'
);
}
}
So far this is working, but it doesn't feel quite right. $customer->vehicles() returns the expected results, but a vehicle should only belong to one customer, and the way I'm doing that at the moment is by doing $vehicle->customers()->first().
The actual relationship should be a One To Many. A customer can have many vehicles, but a vehicle should only belong to one customer.
Is there a way to configure the relationship as a One To Many when using a pivot table in order to be able to fetch a vehicle's customer with $vehicle->customer?
Based on #chuck's suggestion, I now have the following for my Vehicle customer method.
class Vehicle extends Model
{
public function customer()
{
return $this->hasOneThrough(
Customer::class,
CustomerVehiclePivot::class,
'vehicle_id',
'id',
'id',
'customer_id'
);
}
}
I can now perform the following and get the expected results.
$vehicle->customer; // Returns the vehicle's customer
$customer->vehicles; // Returns the customer's vehicles
I'm now trying to figure out how to use factories with this configuration.
I thought I could do Vehicle::factory()->for(Customer::factory())->create() but I get the error...
Call to undefined method Illuminate\Database\Eloquent\Relations\HasOneThrough::getOwnerKeyName()
So I'm not quite sure how you can create Vehicles for users.
I was successfully able to create users with attached vehicles by using hasAttached.
Customer::factory()
->hasAttached(
Vehicle::factory()->count(3)
)
->create()
I was able to figure out how to use factories to create vehicles for users.
Customer::factory()->create()->vehicles()->attach(Vehicle::factory()->count(3)->create());
Yes, you can use the hasOneThrough() relationship in Laravel to set up a one-to-many relationship between customers and vehicles using a pivot table. Here is an example of how you can set this up:
class Customer extends Model
{
public function vehicles()
{
return $this->hasMany(Vehicle::class);
}
}
class Vehicle extends Model
{
public function customer()
{
return $this->hasOneThrough(
Customer::class,
CustomerVehiclePivot::class,
'vehicle_id', // Foreign key on the pivot table
'id', // Local key on the customers table
'id', // Local key on the vehicles table
'customer_id' // Foreign key on the pivot table
);
}
}

laravel hasMany polymorphic relationship in where clause

I am trying to select the picture with the most likes within a specific category from my database. I have a database table storing the pictures and a table storing the likes. The pictures table is related to likeable table through a hasMany polymorphic relationship.
Like model:
public function likeable()
{
return $this->morphTo();
}
Picture model:
public function likes()
{
return $this->morphMany('App\Like', 'likeable');
}
Home Controller:
Picture::where('picture_type', 'food')->orderBy(likes(), 'desc')->first();
Likeable Migration:
public function up()
{
Schema::create('likeable', function (Blueprint $table) {
$table->increments('id');
$table->integer('user_id');
$table->integer('likeable_id');
$table->string('likeable_type');
$table->timestamps();
});
}
So far, the above line of code, in my home controller returns an error. From what I understand, the likes method has to be called on a specific instance. However, I do not know how to implement this all the while keeping my where clause.
Thank you for your help.
I assume you are wanting to order pictures by the most likes.
In which case, you want something like this:
Picture::withCount('likes')->where('picture_type', 'food')->orderBy('likes_count', 'desc')->first();

laravel eloquent relationships between 3 model

actually i have two kind of users which has two different table (user and seller table).
i have comment table with this fields:
Schema::create('comments', function (Blueprint $table) {
$table->increments('id');
$table->integer('user_id')->unsigned()->nullable();
$table->foreign('user_id')->references('id')->on('users');
$table->integer('parent_id')->unsigned()->default(0);
$table->text('comment_text');
$table->integer('commentable_id')->unsigned();
$table->string('commentable_type');
$table->timestamps();
});
how can I add seller_id to this table? if seller wants to response to a user comment.
same issue for message table.
Actually the good practice is you must add a role field in the user table that determines the user is a user or seller. But if you want to keep your table like that you don't need to add seller_id, just use one to many polymorphic relations. Change your comments table schema like this :
Schema::create('comments', function (Blueprint $table) {
$table->increments('id');
$table->integer('parent_id')->unsigned()->default(0);
$table->text('comment_text');
$table->integer('commentable_id')->unsigned();
$table->string('commentable_type');
$table->timestamps();
});
Then in the user and seller model, you must add the relationship method like this :
public function comments()
{
return $this->morphMany('App\Comment', 'commentable');
}
And in the comment model like this :
public function commentable()
{
return $this->morphTo();
}
Then you can get the seller comment like this :
$seller = App\Seller::find(1);
$seller->comments;
And to save the comment from the seller you can use this :
$seller = App\Seller::find(1);
$comment = $seller->comments()->create([
'comment_text' => 'A new comment.',
// Add other field
]);

Laravel belongsTo relationship returns null

I'm building a rudimentary CRM app using Laravel 6.0. Users can freely create accounts, but to get any functionality out of the app, they need to set up a SubscriptionAccount (or join an existing one), which will then allow them to create/manage customer Accounts, add Users, etc. (each is a one to many).
The User model's relationship to SubscriptionAccount model is giving me issues. For example:
$user = User::find(1);
$user->subscription()->create(['name' => 'Test Subscription']);
$user = $user->fresh();
dd($user->subscription); // returns null
I suspected it had to do with the belongsTo relationship in the User model, but the odd thing is that it actually creates and persists a new SubscriptionAccount while using that relationship (second line above), though if you access users relationship from the new SubscriptionAccount it also returns null.
Here are the models:
// User.php
class User
{
public function subscription()
{
return $this->belongsTo(SubscriptionAccount::class, 'subscription_account_id');
}
}
// SubscriptionAccount.php
class SubscriptionAccount extends Model
{
public function users()
{
return $this->hasMany(User::class, 'subscription_account_id');
}
}
The only thing out of the ordinary is shortening the name of the relationship to subscription from SubscriptionAccount, but that should have been taken care of by specifying the foreign key in both relationships. Here's the migrations:
Schema::create('subscription_accounts', function (Blueprint $table) {
$table->bigIncrements('id');
$table->uuid('uuid')->unique();
$table->string('name');
$table->timestamps();
});
Schema::create('users', function (Blueprint $table) {
$table->bigIncrements('id');
$table->uuid('uuid')->unique();
$table->bigInteger('subscription_account_id')->unsigned()->index()->nullable();
$table->string('name');
...
$table->timestamps();
$table->foreign('subscription_account_id')
->references('id')
->on('subscription_accounts');
});
If I create the user from a SubscriptionAccount (i.e. $subscriptionAccount->users()->create([...]); it sets the correct subscription_account_id on the users table, but doesn't work vice versa.
This is a known issue (feature?) with the belongsTo relationship:
https://github.com/laravel/framework/issues/29978
To work around it you can associate the models manually:
$user = User::find(1);
$sub = Subscription::create(['name' => 'Test Subscription']);
$user->subscription()->associate($sub);
$user->save();
So instead of using belongsTo because a subscription account does not belongs to one user, it can belong to many, you might want to use the hasOne relationship instead:
public function subscription()
{
return $this->hasOne(SubscriptionAccount::class, 'id', 'subscription_account_id');
}
It will belongTo one User if you had a user_id within the subscription_accounts table.
Let me know if it makes sense and if it works :)

Laravel Eloquent Relationship Many to Many Naming Convention

I am creating a purchased table in my application. So I have 2 tables : User and Product. Its a many to many relationship.
I know we have to create a new table for this. The naming convention for table is plural as users and products.
How would we call this purchase table? user_product or users_products?
Also I think I would need a model for this correct? If I do need a model should the naming convention for this model be User_Product?
From the documentation:
As mentioned previously, to determine the table name of the relationship's joining table, Eloquent will join the two related model names in alphabetical order. However, you are free to override this convention. You may do so by passing a second argument to the belongsToMany method
In your case, Laravel assumes that your joining table would be named product_user. No extra model is needed:
User.php
class User extends Model
{
//...
public function products()
{
return $this->belongsToMany(Product::class);
}
//...
}
Product.php
class Product extends Model
{
//...
public function users()
{
return $this->belongsToMany(User::class);
}
//...
}
And your schemas would look like so:
users table migration
Schema::create('users', function (Blueprint $table) {
$table->increments('id');
//...
});
products table migration
Schema::create('products', function (Blueprint $table) {
$table->increments('id');
//...
});
product_user table migration
Schema::create('product_user', function (Blueprint $table) {
$table->integer('product_id');
$table->integer('user_id');
//...
});
About the naming Convention, thats just something that will make your code more readable i think, so you can name it as you like(in case you are new and learning , my opinion is that its better to avoid being stuck in conventions at first , im still learning my self)
anyway a pivot model is not required, unless you simply need some custom behaviour
I think this would help you
class User extends Model
{
/**
* The products that belong to the shop.
*/
public function products()
{
return $this->belongsToMany('App\Products');
}
}
you can do this : $user->products or to query $product->users, or both.
Now, with such declaration of relationships Laravel “assumes” that pivot table name obeys the rules and is user_product. But, if it’s actually different (for example, it’s plural), you can provide it as a second parameter:
return $this->belongsToMany('App\Products', 'products_users');
If you want to know how to manage these you can find more in here

Resources