How a foreign key refer multiple primary keys - php

I am working on a system in Laravel in which user can post, answer and vote on post or answer.
One way to do that is making separate vote tables for that but i wanted to do that with one table as votes.
I want that content_id in votes table should refer to two primary keys as posts.id and post-answers.id
If that solution is not possible then suggest an alternate solution for that.
Thanks in advance.
I tried to make this migration but to no avail the table is created successfully but foreign key just pointing only one primary key.
public function up()
{
Schema::create('contentvotes',function(Blueprint $table){
$table->increments('id');
$table->enum('content_type',['post','post_answer']);
$table->integer('user_id')->unsigned();
$table->foreign('user_id')->references('id')->on('users');
$table->integer('content_id')->unsigned();
$table->foreign('content_id')->references('id')->on('posts');
$table->foreign('content_id')->references('id')->on('postanswers');
$table->boolean('status');
$table->timestamps();
});
}

for me, i will do like this. not the best practices but it is the possible way
public class Model {
...
...
protected $appends = ['post', 'post_answer']
public class getPostAttribute()
{
return Post::find($this->attribute('content_id'))
}
public class getPostAnswerAttribute()
{
return PostAnswer::find($this->attribute('content_id'))
}
}

One foreign key cannot reference to primary keys from multiple tables.
For example, you may have Post A and Post Answer B with same ID, how the database knows which ID it is for.
I noticed that you have a content_type column and I believe you have realized this issue. You content_type and content_id is actually a composite foreign key. Again it can reference to one table.
Solution: you need introduce polymorphism. You can merge the post and postanswer to one table and add a type column.
To minimize the effort for data migration, your primary key can be Id and type. Otherwise, ID is a better primary key.

Related

How to use two foreignId in Laravel

I have two tables like:
User:
Schema::create('users', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('loginid')->unique();
$table->string('password');
$table->rememberToken();
$table->timestamps();
});
IP:
Schema::create('i_p_s', function (Blueprint $table) {
$table->id();
$table->string('address')->unique();
$table->foreignId('user_id')->nullable();
$table->string('hostname');
$table->string('description')->nullable();
$table->timestamps();
$table->index('user_id');
});
IP Model:
public function User() {
return $this->belongsTo(User::class);
}
User Model:
public function IPs() {
return $this->hasMany(IP::class);
}
The user_id column means this IP is using by which user.
And now I want to add a new column last_modified which means who is the last editor of this row.
So I think the last_modified should be $table->foreignId('user_id')->nullable(); too.
But how to define the relationship in IP model?
Additionally, I call the user_id like this now.
$ips = IP::with('user')->get();
#foreach ($ips as $ip)
{{ $ip->user }}
#endforeach
So how can I call the last_modified after the definition?
Thanks a lot
As shown in the docs (https://laravel.com/docs/7.x/migrations#foreign-key-constraints),
$table->foreignId('user_id')->nullable();
is just a shortcut of the "old" way
Schema::table('i_p_s', function (Blueprint $table) {
$table->unsignedBigInteger('user_id');
$table->foreign('user_id')->references('id')->on('users');
});
The problem with your code would be, that you need also the constrained()-method. It will dissolve a given column name like user_id into like "Laravel, please use the column id of the table users here".
I'm not sure if the nullable()-method will be useable for this shortcut.
In the same way, your relations will be dissolved within your models. If you're not adding additional values to the belongsTo() and haveMany()-methods, Laravel will try to find its way through your databse by assuming standard naming conventions for primary keys and table names (if the table names are not set within your model).
primary keys are assumed as id. This is also the reason why $table->ip() works.
table names are assumed as the plural of the model name. That means also, you have to make sure to set the name of your i_p_s table within your IP-model as it does not follow the convention. Event better would be to think about an adaption to the convention and call your table ips.
foreign keys should be (to be able to dissolve things that way) named by the singular table name, underscore, primary key. in other words user_id.
So, your assumption should be right apart from the fact that you cannot add a second column called user_id. For your second foreign key, your code should look like the "normal/ traditional" way of doing this.
Schema::table('i_p_s', function (Blueprint $table) {
$table->unsignedBigInteger('last_modified')->nullable();
$table->foreign('last_modified')->references('id')->on('users');
});
I'm pretty sure that this will work, although I didn't tested this yet. Unfortunately, I'm not sure if you can provide the column name and table also within the constrained method. If so, that would be pretty handy. Give it a try, otherwise use the traditional way.
The relation within the model should then look like this:
public function hasChanged() {
$this->hasMany(IP::class, 'last_modified', 'id');
}
last_modified is the foreign key within the i_p_s-table while id is the local column of your owning User-model.
Bringing this into reverse for the IP-model:
public function wasChangedBy() {
$this->belongsTo(User::class, 'last_modified', 'id');
}
In both cases you can dispense on setting the 'id' column as primary key of your users-table as it is standard.
The relations are the same as in your example because of the construction/ architecture. In 99% this is always a one-to-many relation.
Last but not least, it was a bit strange to see this construction of the same foreign key two times referencing in the same table. But I found this post, which says it is eventually totally normal to do so.
Mysql: using two foreign keys to the same table
The only other way I could think of would be to have an intermediate table between i_p_s and users but this would lead to a loop in your database between these two tables which also is a thing you should avoid. So I would say this is ok.
Hope this helps ;)

Create or Update with Laravel

Im making a site where users can post and read each others posts.
I want to make it possible for all users to mark the posts they have read, thus showing only posts which are not read. It should also be possible to undo this. So I have a model and table for both Users and Posts.
The way I thought of doing it was creating a PostsRead table which would contain the unique id for the user and for the post. Then I could fetch all the posts and remove the ones from the PostsRead table. So the question(s) is:
1) Is this a good (best practice-ish) way to do it?
2) How? I guess I need to check if there is a record in the PostsRead-table containing the user_id/post_id pair. If it is not, insert a new row. And if it is, I guess I need to update the current row. It should maybe have a boolean field telling if the post is read or not. How would the code for this be?
When I go through it in my head, this approach seems....weird. Am I on the wrong track here? I hope the question was understandable.
Yes, it's the best practice to do it this way.
Create a table called 'user_read' with 'user_id' and 'post_id' (both as primary key)
Schema::create('user_read', function (Blueprint $table) {
$table->integer('user_id')->unsigned()->index();
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
$table->integer('post_id')->unsigned()->index();
$table->foreign('post_id')->references('id')->on('posts')->onDelete('cascade');
$table->timestamps();
$table->primary(['user_id', 'post_id']);
});
To set it up you should have this relation in your model.
User.php
public function reads()
{
return $this->belongsToMany(Post::class, 'user_read', 'user_id', 'post_id')
->withTimestamps();
}
To store a new post that the user read you can just do
//1 and 3 are the posts ids
$user->reads()->sync([1, 3]);

Multiple "statuses" for many-2-many table relationship in a Laravel Data Model? Best approach?

I am new to Laravel (only been coding a few months). I've created a DB model that connects two tables "Players" and "Teams" via a pivot table.
class Player extends Model
{
public function teams()
{
# With timetsamps() will ensure the pivot table has its created_at/updated_at fields automatically maintained
return $this->belongsToMany('p4\Team')->withTimestamps();
}
}
class Team extends Model
{
public function players()
{
return $this->belongsToMany('p4\Player')->withTimestamps();
}
}
Players can be members of (many) teams, "own" teams, be "recruited" to teams, and be "rejected" from teams. Both Teams and Players can initiate requests for each of these statuses, allowing the other party to confirm/reject. In each case, they should be linked and can only occupy one of the four statuses. What is the best way to link these two tables such that the relationship between any two instances can be given 4 "statuses"?
I need to give the users (whom control teams and players in this open management/recruitment environment), the ability to request/approve classification in each of the "statuses".
It strikes me that the cleanest way to do this would be to use a single pivot table that "assigns" these given statuses to each linked ID pair. I, however, have only seen simpler examples of this concept executed and am as a result unsure as to what the best way to do that is. I would appreciate some guidance here.
Schema::create('player_team', function (Blueprint $table) {
$table->increments('id');
$table->timestamps();
# `book_id` and `tag_id` will be foreign keys, so they have to be unsigned
# Note how the field names here correspond to the tables they will connect...
# `book_id` will reference the `books table` and `tag_id` will reference the `tags` table.
$table->integer('player_id')->unsigned();
$table->integer('team_id')->unsigned();
# Make foreign keys
$table->foreign('player_id')->references('id')->on('players');
$table->foreign('team_id')->references('id')->on('teams');
});
*Again, I'm pretty fresh. Apologies if this has an obvious solution I'm just missing.
If I understood you correctly, you can add status column to a pivot table:
$table->tinyInteger('status')->unsigned();
Don't forget to add withPivot() to relations:
return $this->belongsToMany('p4\Player')->withPivot('status')->withTimestamps();
You can access this column with pivot and set or unset it by adding an array to attach() and detach() methods:
$team->players()->attach($playerId, ['status' => 3]);
Read more about it in docs:
https://laravel.com/docs/5.3/eloquent-relationships#many-to-many
https://laravel.com/docs/5.3/eloquent-relationships#inserting-and-updating-related-models

Laravel - How to use a foreign key to a table with an identifying foreign key?

So I have three tables: users, candidates and logs. Now candidates does not have its own id, it has an identifying foreign key user_id. Looking much like this:
-users-
id
name
etc.
-candidates-
user_id
type
etc.
Now I want to have a table logs that has a one-to-many relation with candidates, being that a candidate can have zero or more logs. So I want to have a foreign key to candidates, something like candidate_id. However, putting it like this and Laravel won't automagically understand the relation. What should I name the foreign key from logs to candidates to make Laravel understand the relationship?
I think the simplest thing would be to give candidates its own incremental id and then have two relationship tables, candidate_user and candidate_log. It would be set up according to Laravel docs
Your candidates table must have a primary key.
There's no need to have an incremental ID, but you shouldn't leave the table without a primary key.
Once you define a primary key, you can use it as your reference.
Laravel will try to guess your foreign_key name:
https://laravel.com/docs/5.2/eloquent-relationships#one-to-many
Remember, Eloquent will automatically determine the proper foreign key column on the Comment model. By convention, Eloquent will take the "snake case" name of the owning model and suffix it with _id. So, for this example, Eloquent will assume the foreign key on the Comment model is post_id.
If your primary key is not the "id" column, you should use the protected $primaryKey and the public $incrementing properties to describe your primary key in your Eloquent Model.

two foreign keys, how to map with laravel eloquent

I have two tables in MySQL, where the first one is called users and the second one is called games. The table structure is as follows.
users
id (primary)
email
password
real_name
games
id (Primary)
user_one_id (foreign)
user_one_score
user_two_id (foreign)
user_two_score
My games table is holding two foreign relations to two users.
My question is how do I make the model relations for this table structure?? - According to the laravel documentation, I should make a function inside the model and bind it with its relations
for instance
public function users()
{
$this->belongsTo('game');
}
however I can't seem to find anything in the documentation telling me how to deal with two foreign keys. like in my table structure above.
I hope you can help me along the way here.
Thank you
A migration:
$table->integer('player1')->unsigned();
$table->foreign('player1')->references('id')->on('users')->onDelete('cascade');
$table->integer('player2')->unsigned();
$table->foreign('player2')->references('id')->on('users')->onDelete('cascade');
And a Model:
public function player1()
{
$this->belongsTo('Game', 'player1');
}
public function player2()
{
$this->belongsTo('Game', 'player2');
}
EDIT
changed 'game' to 'Game' as user deczo suggested.
Unfortunately the way you have this setup is not likely to work in the current context. You may have more luck with the belongsTo method, but again that only supports one relationship.
You could implement a user1() belongsTo, a user2() belongsTo and finally just declare a non eloquent function to return both (something like $users = array($this->user1(), $this->user2())

Categories