Laravel migration foreign keys - php

I'm trying to build some relationships in Laravel, i'm a little confused about relationships and migrations. Here is a simple example of what i'm doing:
Users -> has_many -> Cats
So my Users migration file has a relationship to Cats like so:
$table->foreign('cats_id')->references('id')->on('cats')
But when I run my migration, I get:
Error: relation cats does not exist...
Do I need to build the Cats table before the Users table?
Do I also need to specify the foreign relation between the two, or if the models contain "hasMany" and "belongsTo" wouldn't Laravel build those relationships automatically on migration?
Do I actually need migrations?

You can't reference a table that not exists. It has nothing to do with Laravel or Eloquent, it's (My)SQL thing.
First create the parent table users, then the child table cats referencing the first:
$table->foreign('user_id')->references('id')->on('users')
this is how User hasMany Cat would look like. cats table has foreign key referencing users table, not the other way around like you tried.

You need to set the foreign key to the table where the 'many' are.
$table->foreign('id')->references('cats_id')->on('Users')
You need to make sure that
Table 'Users' exists before you create table Cats (Or any other table that is referenced)
Column 'id' exists before you create the foreign key. (Or any other column that is referenced)
A quite bulletproof solution for me is to setup the tables with a first migration eg
public function up()
{
Schema::create('users', function(Blueprint $table)
{
$table->increments('id');
$table->timestamps();
$table->string('cats_id');
});
//and
Schema::create('cats', function(Blueprint $table)
{
$table->increments('id');
$table->timestamps();
$table->string('cat_name');
});
}
in one migration file and then I create another migration file that runs in the end and creates all the foreign keys for all migrations that were running before:
public function up()
{
Schema::table('cats', function(Blueprint $table)
{
$table->foreign('id')->references('cats_id')->on('users');
});
}
You can also choose what should happen to your cats table on update or delete of a user by adding eg
->onUpdate('CASCADE')->onDelete('CASCADE');
to the $table->... line

You will have to run the migration for cats table and create that table before you can associate it with users table.
Foreign key relation will be helpful when you are required to do cascade delete or update. Also an insert like the following will be easier for you with the cats relationship set.
$user = User::find(1);
$cats = array(
new Cat(array('name' => 'Kitty')),
new Cat(array('name' => 'Lily')),
);
$user->cats()->save($cats);

When specifying a relationship on User Model the Cat model also needs to exist.
In migration
Users
$table->foreign('cats_id')->references('id')->on('cats');
Cats
$table->foreign('user_id')->references('id')->on('users');
Now you force integrity on database level.
Migrate
run the migration using php artisan migrate
Next step is to add the integrity on you Model
Model
User.php
public function cats()
{
return $this->hasMany('Cats');
}
Cat.php
public function user()
{
return $this->belongsTo('User');
}

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 ;)

Add cascade/delete functionality to existing migration

I have 2 tables in my database. One for Courses and one for Course Chapters.
The migration for the courses looks like this:
Schema::create('courses', function (Blueprint $table) {
$table->bigIncrements('id');
$table->timestamps();
});
The migration for the chapters looks like this:
Schema::create('course_chapters', function (Blueprint $table) {
$table->bigIncrements('id');
$table->unsignedInteger('course_id');
$table->timestamps();
});
I want the course and the chapter to cascade down, so when i delete a course, the chapter also will be deleted.
Some examples i saw make use of deleting the foreign key but I never signed my column as a foreign key.
For example, normally, I could:
$table->dropForeign('course_id');
$table->foreign('course_id')
->references('id')->on('courses')
->onDelete('cascade');
How can i accomplish this in a (preferably) new migration and on what table should i add the foreign key?
This as it is should go on your course_chapters table:
$table->foreign('course_id')->references('id')->on('courses')->onDelete('cascade');
You don't need to add $table->dropForeign('course_id'); because that will drop the foreign key from the column.
NOTE: and this:
$table->unsignedInteger('course_id');
Should be this:
$table->unsignedBigInteger('course_id');
Because it will throw an error of using different data types.

Laravel Migration to add foreign key not working

I am back adding foreign keys to some tables but this one is not working.
Schema::table('users', function(Blueprint $table){
$table->integer('account_type')->unsigned()->change();
$table->foreign('account_type')
->references('id')
->on('account_types');
});
It throws these 3 errors in terminal:
[Illuminate\Database\QueryException] SQLSTATE[HY000]: General error: 1215 Cannot add foreign key constraint (SQL: alter table `users` add constraint `users_account_type_foreign` foreign key (`account_type`) references `account_types` (`id`))
[Doctrine\DBAL\Driver\PDOException]SQLSTATE[HY000]: General error: 1215 Cannot add foreign key constraint
[PDOException] SQLSTATE[HY000]: General error: 1215 Cannot add foreign key constraint
Any idea what I need to do to fix this? I wan't to be able to click on the account_type field in my users table and be taken to the related account type in the account_types table whilst using SequelPro.
If we assume that you've already created account_types and users, then this is what you could do.
Run this command to create a new migration.
php artisan make:migration add_account_type_to_users
Then add this to your up function, what this will do is update the already existing users table, with adding a column account_type and then adding the foreign key to the account_types table.
public function up()
{
Schema::table('users', function(Blueprint $table) {
$table->integer('account_type')->unsigned();
$table->foreign('account_type')->references('id')->on('account_types');
});
}
Then add this to your down function, to allow the migration to run smoothly incase of future changes and/or a refresh.
public function down()
{
Schema::table('users', function(Blueprint $table) {
$table->dropColumn('account_type');
});
}
I am assuming your issue is with the dating of the migration files - I would advice you to go over them and make sure they get executed in the right order.
I suggest that you create a migration specifically to add the foreign key constraint. Migrations should be created in such a way that each change to your database is a NEW migration. That way if something goes wrong, you can roll it back. "Back adding" as you said you were doing is likely the root of the problem. Like some users have suggested, the order in which your migrations are created is critical to how the database is built. And you must have created both participating tables in order to create a foreign key constaint on them.
Your table 'account_type' need to be created before 'users' table.
Basically on Laravel, users table is the first created, to resolve it you can put all schema on One migration file. or you need to change the date in the name of the users file, because laravel execute migrations in name order.
I make my migration like this :
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class DatabaseContent extends Migration
{
/**
* Run the migrations.
*
* #return void
*/
public function up()
{
$connection = config('database.default');
Schema::connection($connection)->create('account_type', function
(Blueprint $table) {
// Own Method
}
Schema::connection($connection)->create('users', function
(Blueprint $table) {
$table->increments('id');
$table->string('name');
$table->timestamps();
});

Reciproc relationship migration order in Laravel

I am trying to create a relationship in Laravel Migration:
User > has many > Images
User > has one > Image (image_id) <-- Avatar
Image > belongsTo > User (user_id)
If I will create the migration with references to mark the relationships, the migration will fail because image needs User, and User needs image.
How should I order migrations to prevent this from happening?
(https://laravel.com/docs/5.4/migrations)
You can create users and images tables in any order and add foreign key constrains in the second migration after creating the table. Or even in a separate migration file.
To add constrains use table() method instead of create():
Schema::table('images', function (Blueprint $table) {
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
});

Laravel Many to Many Pivot on existing Pivot

I have a situation in Laravel 5.1 where I would like to add a many-to-many relationship to an existing relationship. According to the diagram below, I already have all the items in green working.
The issue is that since there isn't a primary key on the issue_person table, I don't know how to add a many-to-many relationship to Users. Does anyone know how I would go about accomplishing this?
So it appears that a simple answer to this is to write a migration that adds a primary key to the original issue_person pivot table, and then set up a many-to-many relationship between issue_person and user using the position_user table.
My migration looks like this:
public function up()
{
Schema::table('issue_person', function (Blueprint $table) {
$table->increments('id');
});
}
public function down()
{
Schema::table('issue_person', function (Blueprint $table) {
$table->dropColumn('id');
});
}

Categories