Is it possible to copy data from old table into new table instead of rename? We are planning a major database schema upgrade and would like to preserve current data tables, so the migration down() can be as simple as dropping newly created tables.
we realize this breaks backward compatibility as migrate:rollback doesn't really rollback any new data into previous state; but enabling such thing will be very costly due to the scale of schema update, we are content with a simple 1-way migration, as long as it preserves old tables.
Can this be done within Laravel's migration and schema alone?
Thanks to suggestion from #TonyArra and #Fractaliste, we now do something like following, this allow us to test run migration and rollback without worrying about data lost.
use Illuminate\Database\Migrations\Migration;
use MyNewModel;
class DataConvert extends Migration {
/**
* Run the migrations.
*
* #return void
*/
public function up()
{
foreach(MyOldModel::all() as $item)
{
MyNewModel::create(array(...));
}
}
/**
* Reverse the migrations.
*
* #return void
*/
public function down()
{
MyNewModel::truncate();
}
}
As far as I know, there's no copy function in Laravel to do this, but it's fairly easy with models. For example, if you wanted to move data from the table users to newusers, you could add the following to the NewusersTableSeeder run function:
$users = User::all()->toArray();
foreach ($users as $user) {
$user['newField'] = "data";
Newuser::create($user);
}
(recommend that this be done in the seeder, as you should already have Eloquent::unguard(); in DatabaseSeeder.php)
Into the down() function and just before dropping your tables, I think you can perform an export of your data.
The basic use of migration is to create/drop tables. But nothing prevents you to make a more complex one. And the artisan tool provides access to any Laravel's functionality (except network one's like Input or Cookies I think)
Not sure it helps but i wrote a small class that helps copying data between two databases with totally different structure according to rules you provide on a xml file, see https://github.com/Binternet/redb
So maybe you can fire this up when you finish the last migration
Related
I'm developing a web using Laravel 9. I used the command sail php make:migration add_bought_to_products_table to add a boolean column called "bought" in a products table. When trying to modify the value using Eloquent helpers (Product::where('id', $product->id)->update(array('bought'=>true)) the value is not updated in the database. When looking at it, I se that the new field "bought" created by the migration is marked as Read-only: No corresponding table column.
The migration code is the following:
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*
* #return void
*/
public function up()
{
Schema::table('products', function (Blueprint $table) {
$table->boolean('bought');
});
}
/**
* Reverse the migrations.
*
* #return void
*/
public function down()
{
Schema::table('products', function (Blueprint $table) {
$table->dropColumn('bought');
});
}
};
Here a screenshot of the database:
I have already tried to clean the cache and rebuild many times the database doing a rollback and migrating again. The curious thing is that I previously added the field "visibility" which works perfectly with the exact same code and steps as the field that is giving the problem.
How can I solve it?
After breaking my head a lot, I solved it by simply doing a clean restart of the docker containers. It seems that the issue had nothing to do with Laravel but with Docker!
For anyone experiencing similar issues: make sure to end Docker and all containers and do a clean restart.
I simply restarted my database tool and it was gone.
I have an old migration where I used the foreignIdFor() method to create a column.
I later decided to delete the model which was referenced and now, when I do a migrate:refresh locally, that migration triggers an error saying that the model doesn't exist, of course.
So, should one never delete models referenced with foreignIdFor() and use unsignedBigInteger()instead ?
EDIT:
I know foreignIdFor and unsignedBigInteger will create the same column. When I say I deleted the model, I mean the model class, not a model.
My migration file :
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
use App\Models\Event;
use App\Models\OutlookCategory;
class CreateEventOutlookCategoryTable extends Migration
{
/**
* Run the migrations.
*
* #return void
*/
public function up()
{
Schema::create('event_outlook_category', function (Blueprint $table) {
$table->id();
$table->foreignIdFor(Event::class);
$table->foreignIdFor(OutlookCategory::class);
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* #return void
*/
public function down()
{
Schema::dropIfExists('event_outlook_category');
}
}
The foreignIdFor method adds a {column}_id UNSIGNED BIGINT equivalent column for a given model class, so using unsignedBigInteger() doesn't make any difference.
Resources:
https://laravel.com/docs/9.x/migrations#column-method-foreignIdFor
https://laravel.com/docs/9.x/migrations#column-method-unsignedBigInteger
What you could/needed to do is to add cascade delete, and make sure that whenever associated model is deleted, all related models are deleted too.
You could do it like this:
table->foreignId('column_name_id')
->constrained()
->onDelete('cascade');
Resources:
https://laravel.com/docs/9.x/migrations#foreign-key-constraints
Answer to the edit:
If you delete Model that was used in foreignIdFor, that method will not be able to understand what you are referencing, and therefore it will fail. So, the answer to your question is YES and NO.
Let me elaborate. If your migrations will be run just once, on the production environment, for example, then you will be able to delete the model you've been referencing in the previous migrations and just create a new migration that will cleanup those columns.
In all other cases, when your migrations will be run for a few times (locally when you use migrate:fresh), you need to have Model that was referenced in them in your code base in order for it to work properly.
If you want to avoid these kind of problems that you are experiencing right now, just use unsignedBigInteger and pass to it string that is the name of the column, and you don't have to worry about deleting the model. However, you will still need to pay attention not to delete the column that is referenced there, because you will get another error for missing column.
Is there a way how I can change the migrations order without remaking them all? Because now I have a problem with my foreign keys -_- (working with laravel)
Roll back all the migrations (or start with a fresh database);
Change the dates that form the first part of the migration filenames so they're in the order you want (eg. for 2014_06_24_134109_update_database.php, the date & time is 2014-06-24, 13:41:09);
Run the migrations again.
With respect to your comment about foreign keys... I'm not sure that the problem is with Laravel. More likely, it's just MySQL.
I avoid foreign keys because once you get a moderately complicated set of relations, you start to run into problems with database consistency like you're seeing - it's hard for the server to figure out what order to create the tables & relationships in, and it starts to cause difficulties with things like dump files (for backups).
You have to create a custom command that executes
php artisan migrate:refresh --path=/database/migrations/name_migration.php repeately with the migrations's name in the order you want.
Like this:
Create Command class with: php artisan make:command NameClass
Go to app/Console/Commands/ and find the class file NameClass.php
In the NameClass.php you have two attributes $signature (the name of the command) and $description (Information about what your command does).
Set the name and the description of your command.Ex: protected $signature='namecommand'; protected $descripton = 'This method migrate tables in order'
Inside the NameClass.php you have a method called handle(), here you have to declare the code you want to be executed when you write the command.
Register your command. Go to app/Console/Kernel.php and add your class to the list of Command Classes.
protected $commands = [
Commands\NameClass::class,
];
Write the command in the terminal. php artisan namecommand
Example:
php artisan make:command MigrateInOrder
app/Console/Commands/MigrateInOrder.php
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
class MigrateInOrder extends Command
{
/**
* The name and signature of the console command.
*
* #var string
*/
protected $signature = 'migrate_in_order';
/**
* The console command description.
*
* #var string
*/
protected $description = 'Execute the migrations in the order specified in the file app/Console/Comands/MigrateInOrder.php \n Drop all the table in db before execute the command.';
/**
* Create a new command instance.
*
* #return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* #return mixed
*/
public function handle()
{
/** Specify the names of the migrations files in the order you want to
* loaded
* $migrations =[
* 'xxxx_xx_xx_000000_create_nameTable_table.php',
* ];
*/
$migrations = [
'2020_04_18_005024_create_users_types.php',
'2014_10_12_000000_create_users_table.php',
'2014_10_12_100000_create_password_resets_table.php',
'2019_08_19_000000_create_failed_jobs_table.php'
];
foreach($migrations as $migration)
{
$basePath = 'database/migrations/';
$migrationName = trim($migration);
$path = $basePath.$migrationName;
$this->call('migrate:refresh', [
'--path' => $path ,
]);
}
}
}
Go to app/Console/Kernel.php and register your command
protected $commands = [
Commands\MigrateInOrder::class,
];
Excute the command
php artisan migrate_in_order
Taking inspiration from PhpMyAdmin, I put all foreign keys definitions in a specific far future file, eg : 2999_12_31_235959_foreign_key.php
<?php
use App\Models\Post;
use App\Models\Tag;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class ForeignKeys extends Migration
{
/**
* Run the migrations.
*
* #return void
*/
public function up()
{
// Post_tag
Schema::table(Post::NOM, function (Blueprint $table) {
$table->foreign('id_post')
->references('id_post')
->on(Post::NOM);
$table->foreign('id_tag')
->references('id_tag')
->on(Tag::NOM);
});
}
}
The only con I see is not having foreign keys definition in migration.
For the pros :
Keeping database relations
Do not care of table creation order
The best and easiest thing would be to just rename the migration yyyy_mm_dd_hhmmss_migration_name. If your migration follows this sequence, Laravel will ensure to run the migration is sorted form of date,
Building on the answer of Galeokerdo which suggests creating a separate migration file for the foreign keys, and putting the date in the far future, I tried it and it worked great. But then I started thinking of the rollback. It turned out that Laravel takes the reverse order when rolling back migrations. That is, the latest file is rolled back first.
Since the rollback will not work with the foreign key constraints in place, I tried putting my foreign-key-removal code in the "down" method of the separate foreign key migration, having found out that the file will execute first before all other migration files. Like thus:
/**
* Reverse the migrations.
*
* #return void
*/
public function down()
{
Schema::table('tablename', function (Blueprint $table) {
$table->dropForeign('tablename_foreignkey_foreign');
});
}
"tablename_foreignkey_foreign" is the name of the foreign key constraint. By default, it is
"nameofthetable_foreignkeycolumn_foreign"
I just wanted to share this in case anybody is struggling with it the way I did.
You only need to change migrations order. if bands or stage tables are below users table, MySQL don't find the references. =)
I would like to rename a table in the database from topics to galleries and I have created a migration that will rename my table.
Schema::rename('foo', 'bar');
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class RenameTopicsTable extends Migration
{
/**
* Run the migrations.
*
* #return void
*/
public function up()
{
//
Schema::rename('topics', 'galleries');
}
/**
* Reverse the migrations.
*
* #return void
*/
public function down()
{
//
Schema::rename('galleries', 'topics');
}
}
However will the Topic Model and Topic Controller be automatically renamed? Or will I have to refactor my code? Does Laravel provide a way to do this easily?
In short my question is - How do you change your schema easily in laravel? (models/controllers/database/requests/transformers ect..)
To answer this question. Laravel does not provide a out-of-the-box way to rename your tables at the same time as your Models/Controllers.
You must manually refactor your code after you change your database schema.
An example of this is lets say i have a posts table and i want to rename it to blogs. Well my posts model and posts controller wont be helpful after i update the schema so i will need to change those over as well. Routes will need to be updated. If I am using views those will need to be updated. In my case i was using transformers and requests so those need to be manually updated.
If you can, try to avoid changing your schema :D
So I have database with table, where I need to edit one column, make it nullable, to be specific. How can I access it from php artisan tinker, or maybe somehow re-run migration on one table without losing data from it?
With tinker you cannot modify schema table. You need to create a migration like this:
<?php
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class AlterTableUsers extends Migration
{
/**
* Run the migrations.
*
* #return void
*/
public function up()
{
Schema::table('users', function ($table) {
$table->string('name', 50)->nullable()->default(null)->change();
});
}
}
In this case, we make name nullable with default value null.
More info: https://laravel.com/docs/master/migrations#modifying-columns
You can just run the suggested code:
Schema::table('users', function ($table) {
$table->string('name', 50)->nullable()->default(null)->change();
});
directly from tinker
If you want to re-run migration or add new migration that alters the existing table depends on whether you are coding on production mode or development mode. Generally on development mode re-running migration may be best option(due to dummy data)..On other hand; on production mode, if the data is important ..we generally add the new migration that updates the existing table (because data may be important to us..)