I'm working on a project where I need to create three Many-to-Many relationships between 4 models. Here is how it goes:
FAQ Categories can have many FAQ Subcategories and vice versa.
FAQ Groups can have many FAQ Subcategories and vice versa.
FAQs can have many FAQ groups and vice versa.
To all the database experts out there, how should I design this database schema in Laravel? Should I have three different pivot tables? Should I use polymorphic relationships?
I've used polymorphic relationships before, but I'm struggling with implementing it in this scenario.
I would do something like this:
FAQ Categories Table
Schema::create('faq_categories', function (Blueprint $table) {
$table->increments('id');
$table->string('name');
});
Schema::table('faq_categories', function (Blueprint $table) {
$table->unsignedInteger('parent_id')->nullable();
$table->foreign('parent_id')->references('id')->on('faq_categories')->onDelete('cascade');
});
FAQ Groups Table
Schema::create('faq_groups', function (Blueprint $table) {
$table->increments('id');
$table->string('name');
});
FAQs Table
Schema::create('faqs', function (Blueprint $table) {
$table->increments('id');
$table->string('name');
});
As you can see I wouldn't create a FAQ Sub Categories table, because it's cleaner to have a category table referencing itself to a parent category (also important to make that foreign key nullable to be able to create a top level category).
Now to setup the relationships between the tables, we can do this:
FAQ Categories - FAQ Groups (Many to Many)
Schema::create('faq_category_faq_group', function (Blueprint $table) {
$table->increments('id');
$table->unsignedInteger('faq_category_id');
$table->foreign('faq_category_id')->refrences('id')->on('faq_categories')->onDelete('cascade');
$table->unsignedInteger('faq_group_id');
$table->foreign('faq_group_id')->refrences('id')->on('faq_groups')->onDelete('cascade');
});
FAQs - FAQ Groups (Many to Many)
Schema::create('faq_faq_group', function (Blueprint $table) {
$table->increments('id');
$table->unsignedInteger('faq_id');
$table->foreign('faq_id')->refrences('id')->on('faqs')->onDelete('cascade');
$table->unsignedInteger('faq_group_id');
$table->foreign('faq_group_id')->refrences('id')->on('faq_groups')->onDelete('cascade');
});
Should I use polymorphic relationships?
I don't think polymorphic relationship's would make any sense in this scenario. I would stick with standard many to many.
In your model classes you should setup all the relationships like referenced in the docs.
You can do this:
FaqCategory Model
class FaqCategory extends Model
{
/**
* Get the category's parent category.
*/
public function parent()
{
return $this->belongsTo('App\FaqCategory');
}
/**
* Get the category's sub categories.
*/
public function sub_categories()
{
return $this->hasMany('App\FaqCategory', 'parent_id');
}
/**
* Get the category's faq groups.
*/
public function faq_groups()
{
return $this->belongsToMany('App\FaqGroup');
}
}
FaqGroup Model
class FaqGroup extends Model
{
/**
* Get the group's faq categories.
*/
public function faq_categories()
{
return $this->belongsToMany('App\FaqCategory');
}
/**
* Get the group's faqs.
*/
public function faqs()
{
return $this->belongsToMany('App\Faq');
}
}
Faq Model
class Faq extends Model
{
/**
* Get the faq's faq groups.
*/
public function faq_groups()
{
return $this->belongsToMany('App\FaqGroup');
}
}
One concern is whether FAQ Categories and Subcategories should be different tables. Maybe should be the same table in order to give you the option to have more levels in the future.
i.e.
faq_categories(id, parent_id, name etc.)
Then, in my opinion, you need just different pivot tables. I can not see any reason to use polymorphic relationships.
Related
I am new to Laravel and I am trying to create an application for our customers to confirm ownership of their products.
I created the following tables and models:
users table
Schema::create('users', function (Blueprint $table) {
$table->bigIncrements('id');
$table->string('name');
$table->string('email')->unique();
$table->timestamp('email_verified_at')->nullable();
$table->string('password');
$table->rememberToken();
$table->timestamps();
});
products table
Schema::create('products', function (Blueprint $table) {
$table->bigIncrements('id');
$table->string('name');
$table->integer('etc');
$table->timestamps();
});
user_product table
Schema::create('user_product', function (Blueprint $table) {
$table->bigIncrements('id');
$table->unsignedBigInteger('user_id')->index();
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
$table->unsignedBigInteger('product_id')->index();
$table->foreign('product_id')->references('id')->on('products')->onDelete('cascade');
$table->timestamps();
});
user model
public function products()
{
return $this->belongsToMany('App\Product', 'user_product')->withTimestamps();
}
product model
public function users()
{
return $this->belongsToMany('App\User');
}
So here’s where I’m stuck…
I want to have a product displayed together with the user registration form. Upon registration, the user has to confirm ownership of the product. So I need to associate the product displayed with the user account that is about to be created. Not sure how to do that though…
I am using ManyToMany because I want to have all the products in the database, even if they belong to a user or not. After the association between the user and the product, you cannot access that product without being logged in.
I hope it makes a bit of sense, still working on the logistics behind this :)
Any idea of how to reach this scenario?
Any help would be much appreciated :)
If the product can only belong to a single user and a user can have many products you should use One To Many, and create a foreing key in the product table by calling user and would use the create () or save () function.
Using softdelete in the User model you do not risk deleting the user and their products.
But if you want a Many to Many relationship anyway use the attach () function as follows:
$user->produtcs()->attach($productId);
or else
$product->users()->attach($userId)
But both models already have to be in the database, both User and Product.
If you want to know which User owns the product you can add a user_owner_id field in the user_product table and add it in the attach like this:
$user->produtcs()->attach($productId, ['user_owner_id' => $userId]);
I have an interface which displays a list of communities on the platform. Communities have members and in turn members/profiles can befriend one another. On the listing page each community card needs to display the number of members (in the community) and the number of friends (friends of logged in profile) from those members.
Here's an illustration of how a community card looks like
I'm getting the communities with the members first:
$communities = $loggedInProfile->communities->load('members')->take(15);
And then iterating over the communities and then the members to find out which ones are friends with the logged in user.
foreach ($communities as $key => $community) {
$friends = [];
foreach ($community->members as $member) {
if ($loggedInProfile->isFriendWith($member)) {
array_push($friends, $member);
}
}
$community->members_who_are_friends = $friends;
}
My issue is that this is very taxing in terms of the number of queries when the associations get large. Is there a better way of retrieving these relationships without having to use nested for loops? I'm also indexing all data with Elasticsearch. Would a retrieval of this sort be better with Elasticsearch? Also would this be a good use case for hasThrough?
Update
The members relationship:
public function members()
{
return $this->belongsToMany('App\Profile', 'community_members', 'community_id', 'profile_id')->withTimestamps();
}
The isFriendWith relationship:
public function isFriendWith(Model $recipient)
{
return $this->findFriendship($recipient)->where('status', Status::ACCEPTED)->exists();
}
The check is done on a table called friendships. The status column (which can be either 0 or 1) is checked to see if friends or not.
The findFriendship check:
private function findFriendship(Model $recipient)
{
return Friendship::betweenModels($this, $recipient);
}
Database structure:
-Profiles migration
Schema::create('profiles', function (Blueprint $table) {
$table->increments('id');
$table->unsignedInteger('user_id');
$table->foreign('user_id')->references('id')->on('users');
});
-Communities migration (the foreign key is the owner of the community)
Schema::create('communities', function (Blueprint $table) {
$table->increments('id');
$table->unsignedInteger('profile_id');
$table->foreign('profile_id')->references('id')->on('profiles');
$table->string('slug')->unique();
});
-Community_members migration
Schema::create('community_members', function (Blueprint $table) {
$table->primary(['profile_id', 'community_id']);
$table->unsignedInteger('profile_id');
$table->foreign('profile_id')->references('id')->on('profiles');
$table->unsignedInteger('community_id');
$table->foreign('community_id')->references('id')->on('communities');
$table->timestamps();
});
-Friendships migration
Schema::create('friendships'), function (Blueprint $table) {
$table->increments('id');
$table->morphs('sender');
$table->morphs('recipient');
$table->tinyInteger('status')->default(0);
$table->timestamps();
});
In your line:
$communities = $loggedInProfile->communities->load('members')->take(15);
load() is used to perform Lazy Eager loading, i.e. you load the members after the communities have been retrieved, resulting in a different query for every community. You could extract the whole data with a single query using with(). Also, take(15) is performed on the resulting collection and not on the query. Try this:
$communities = $loggedInProfile->communities()->with('members')->take(15)->get();
In the Models of a many to many relationship I have accidentally identified the foreign key names in reverse. This is done in both related Models so the relationship works. It's in production.
In Articles:
public function categories()
{
return $this->belongsToMany(ArticleCategory::class, 'article_category_article', 'article_category_id', 'article_id');
}
and in ArticleCategory:
public function articles()
{
return $this->belongsToMany(Article::class, 'article_category_article', 'article_id', 'article_category_id');
}
As you can see, both foreign keys are reversed.
It doesn't bother me because it works throughout the project. In the article_category_article table both values are recorded in the 'wrong' column.
But what if I'd like to swap it anyway. The Models are easy, but what about the pivot table? I have tried with a laravel migration:
public function up()
{
Schema::table('article_category_article', function (Blueprint $table) {
$table->renameColumn('article_id', 'temporarily');
$table->renameColumn('article_category_id', 'article_id');
$table->renameColumn('temporarily', 'article_category_id');
});
}
without success, it predictably runs into the error There is no column with name 'temporarily' on table 'article_category_article'
Splitting it up in 2 migration files ran into the same error.
I have the tendency to let it be. The question is: can it be done? I presume swapping the columns inside MySQL (without migrations), re-index the tables and adapt the Models is a possibility. Any ideas? I can test it out on a local server.
Two separate queries work for me:
public function up()
{
Schema::table('article_category_article', function (Blueprint $table) {
$table->renameColumn('article_id', 'temporarily');
$table->renameColumn('article_category_id', 'article_id');
});
Schema::table('article_category_article', function (Blueprint $table) {
$table->renameColumn('temporarily', 'article_category_id');
});
}
I need to add three tables to MySql but I don't know how to organize them.
My tables are :
apartment
floor
surface
And my relations :
apartment can have many floor
apartment can have many surfaces
floor can have many apartment
floor can have many surfaces
surfaces can have one floor (inside apartment's floors)
surfaces can have one apartment
Surfaces are enter after apartment, so I can't use surfaces floor as my apartment floor.
At the moment I have apartment table and floor table with a pivot table (many to many).
In Mysql, what's the best way to handle this ? And after inside laravel ?
Thank for yourhelp !
Edit : my work on laravel so far (only floor table and apartment table)
In Apartment.php :
public function floors(){
return $this->belongsToMany('App\Models\Copro\Floor');
}
In Floor.php :
public function apartments()
{
return $this->belongsToMany('App\Models\Copro\Apartment');
}
Edit 2 : my migration so far :
Schema::create('surfaces', function (Blueprint $table) {
$table->increments('id');
$table->integer('apartment_id')->unsigned()->index();
$table->foreign('apartment_id')->references('id')->on('apartments')->onDelete('cascade');
$table->integer('floor_id')->unsigned()->index();
$table->foreign('floor_id')->references('id')->on('floors')->onDelete('cascade');
$table->decimal('surface',65, 3);
});
Schema::create('apartments', function (Blueprint $table) {
$table->increments('id');
$table->string('nom');
});
Schema::create('floors', function(Blueprint $table)
{
$table->increments('id');
$table->string('nom');
});
Schema::create('apartment_floor', function(Blueprint $table){
$table->increments('id');
$table->integer('apartment_id')->unsigned()->index();
$table->integer('floor_id')->unsigned()->index();
$table->foreign('apartment_id')->references('id')->on('apartments')->onDelete('cascade');
$table->foreign('floor_id')->references('id')->on('floors')->onDelete('cascade');
});
I can think of below kind of relationships in Laravel, I have not created the sample code although but it should help.
Apartment model:
public function floors() {
return $this->belongsTo('App\Models\Copro\Floor', 'floor_id', 'id');
} /* Many apartments belong to single floor. Single floor can have many apartments.*/
public function surfaces() {
return $this->hasMany('App\Models\Copro\Surface', 'surface_id', 'id');
} /* Single apartment has many surfaces */
Floor model:
public function apartments() {
return $this->hasMany('App\Models\Copro\Apartment', 'apartment_id', 'id');
} /* A floor has many apartments */
public function surfaces() {
return $this->hasMany('App\Models\Copro\Surface', 'surface_id', 'id');
} /* A floor has many surfaces */
Surface model:
public function floor() {
return $this->belongsTo('App\Models\Copro\Floor', 'floor_id', 'id');
} /* Surfaces belong to single floor */
public function apartment() {
return $this->hasMany('App\Models\Copro\Apartment', 'apartment_id', 'id');
} /* Surfaces belong to single apartment */
However, it will bring more clarity to the answers if you can provide table schema or migration schema on the same question. Also, you can mention which kinds of SQL queries you are expecting and want to execute. Thanks. Hope this helps.
EDIT:
I am not having PHP installed right now but below schema can be useful to us to at least give newer and better thoughts.
Floor:
id
flr_number
Apartment:
id
apt_number
flr_id
Surface:
id
apt_id
surface
Floor will have many apartments through surfaces. Therefore we can introduce below method in Floor model.
public function surfaces() {
return $this->hasManyThrough(
'App\Models\Copro\Surface',
'App\Models\Copro\Apartment',
'flr_id', /* Foreign key of Apartments table */
'apt_id', /* Foreign key of Surfaces table */
'id', /* Local key of Floors table */
'id' /* Local key of Apartments table */
);
}
I am trying to save data with eloquent relationship.
I have following three tables: User Table, Category Table and Post Table.
Post Table
Schema::create('posts', function (Blueprint $table) {
$table->increments('id');
$table->integer('category_id')->unsigned();
$table->integer('user_id')->unsigned();
$table->string('heading');
$table->timestamps();
$table->foreign('category_id')->references('id')->on('categories');
$table->foreign('user_id')->references('id')->on('users');
});
Relations:
Category:
public function posts() {
return $this->hasMany('App\Post');
}
Post:
public function user() {
return $this->belongsTo('App\User');
}
public function category() {
return $this->belongsTo('App\Category');
}
User:
public function posts($category) {
return $this->hasMany('App\Post');
}
My Problem is that, how can I save post just by passing the Heading in create function. I want to use the relationship. As an example I want to use this kind of code:
$data = ['heading' => $heading];
$user->posts()->category()->create($data);
Is this possible to do this kind of stuff ?
Or any another simple way to achieve this.
EDIT
I need to create post by using this kind of relationship.
As per the process:
user will fill up the form from which I will get the data along with
the category id.
Now I need to create data for that user related with the given category id.
It's because after you call posts() method you won't get to the model's relation (only the query builder) so you will not access category() relation method. It's because posts are one-to-many relation and you don';t know exacly which record you refer to create data.
EDIT
If you want to create new post entry the the best way to sole this is:
$data = ['heading' => $heading, 'category_id' => $putHereCategoryId];
$user->posts()->create($data);
You'll need to obtain somehow the id of the desire category for the new post's entry.