1

Looking for a smarter way to do this. I have 4 migrations files (out of MANY dozen) that are related. I also have a seeder for these. Doing php artisan migrate:fresh --seed takes a good 30-45 seconds each time (there are also a few dozen seeders) and I want to just drop my 4 tables and rebuild quickly while testing.

I want to define this programmatically instead of with a CLI, so I made an artisan command:

/* routes/console.php */
Artisan::command('refresh-my-tables', function() {
    $numbers = include __DIR__ . './../database/migrations/2023_05_03_175125_create_my_table_numbers_table.php';
    $files = include __DIR__ . './../database/migrations/2023_05_08_160034_create_my_table_files_table.php';
    $orders = include __DIR__ . './../database/migrations/2023_05_03_175124_create_my_table_orders_table.php';
    $parent = include __DIR__ . './../database/migrations/2023_05_03_171245_create_my_table_table.php';

    // These are ordered intentionally to deal with foreign key constraints
    $numbers->down();
    $files->down();
    $orders->down();
    $parent->down();
    $parent->up();
    $orders->up();
    $files->up();
    $numbers->up();

    (new Database\Seeders\MyTableSeeder)->run();
});

Just need a simple php artisan refresh-my-tables

This feels... wrong. Specifically the include [long absolute filename]. I had thought Laravel, in the past, had a way to hook into migrations by name (letting PSR-4 handle finding the classes), but now, php artisan make:migration builds a file that returns an anonymous Migration extended class.

All Google and SO searches just refer to artisan migrate without acknowledging more complex edge-cases that, I can imagine, are quite common for sufficiently large projects. Is this the best (most Laravel-flavored) way to do this?

Phil Tune
  • 3,154
  • 3
  • 24
  • 46
  • 1
    Interesting edge-case indeed, but I have to ask, do you _need_ to drop and re-migrate if you're just trying to re-seed your data? Could you simply do `Number::truncate()` (i.e. truncate the tables via their Model, or even `DB::table('numbers')->truncate()`)? This way, your `migrations` table also isn't affected (unless `migrations` isn't modified via `->down()` and `->up()`; can't say I've ever used em like this) – Tim Lewis May 24 '23 at 16:14
  • So, you aren't using [`use RefreshDatabase`](https://laravel.com/docs/10.x/database-testing#resetting-the-database-after-each-test) trait because you lose the seeded data? Using the latest Laravel 10, it will not refresh the database every time, but it will use transactions, so when the test is done, it is rolled back and you still have the right data. You can read how to seed the database automatically in that same link, so you do not need to manually run a seed inside a test every time, but you should only run needed seeders on the needed tests, not globally... – matiaslauriti May 24 '23 at 17:48
  • 1
    You know what @TimLewis, I think `truncate()` was exactly what I was looking for. At least for seeding purposes. If I am updating the migrations (this had been happening a lot, we're still figuring out functionality) I'd still want to call `up()` on my migrations. Seeing @DanielTharp's answer below, looks like I can still name my migration class, which solves testing specific migrations files as well. (At the moment, I'm ONLY testing my seeder, so `truncate()` should simplify things a lot. Thank you!) – Phil Tune May 24 '23 at 18:50
  • @matiaslauriti very interesting. That's good to know when we setup unit testing. We're only doing manual, front-end testing now. (I came in at the middle of a large project and the client hadn't implemented a testing suite.) Thanks! – Phil Tune May 24 '23 at 18:59

1 Answers1

1

The old way of doing things is still valid at the time of this writing and Laravel 10. You can refactor your anonymous classes to look like this instead:

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateUsersTable extends Migration
{
    public function up()
    {
        Schema::create('users', function (Blueprint $table) {
            ...
        }
    }
    public function down()
    {
        Schema::dropIfExists('users');
    }
}

Then your calls would just instantiate that class and run its methods.

EDIT: If you are coming to this from the internet and this isn't working, be sure to also add the path to these migrations to your composer.json so the autoload knows it's there. "autoload":{..."classmap": ["database/migrations/"]}

Daniel Tharp
  • 353
  • 1
  • 10
  • I had wondered if it was that simple. I hadn't tested just changing `return new class extends Migration` to `class MyMigration extends Migration`. I'm pretty certain I had seen this in old documentation, but they've trimmed a lot out and it's a shame some things are harder to find through Laravel docs alone. – Phil Tune May 24 '23 at 18:54
  • Might I edit your answer to include instructions to autoload these classes, as they're not PSR-4/0? This only works if I add `"autoload":{..."classmap": ["database/migrations/"]}` to composer.json. – Phil Tune May 30 '23 at 14:38
  • Didn't think about that, I'll make mention, thanks for coming back to improve the answer! – Daniel Tharp May 31 '23 at 15:01