BF migration issues, R hybrid

This commit is contained in:
Fabian @ Blax Software 2026-05-19 14:20:20 +02:00
parent 3272d985ac
commit 5155815043
6 changed files with 165 additions and 77 deletions

View File

@ -2,6 +2,23 @@
return [
/*
|--------------------------------------------------------------------------
| Auto-load migrations
|--------------------------------------------------------------------------
|
| When true (the default) the package auto-loads its migrations from
| `vendor/blax-software/laravel-files/database/migrations` so a fresh
| `composer require` + `php artisan migrate` Just Works™. Set to false
| if you publish the migrations and want to manage them yourself the
| package will then defer entirely to your published copies.
|
| See: laravel-workkit/PRINCIPLES/laravel-composer-packages.md
|
*/
'run_migrations' => true,
/*
|--------------------------------------------------------------------------
| Models

View File

@ -0,0 +1,51 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
/**
* Creates the package's `files` table (canonical name overridable via
* `config('files.table_names.files')`).
*
* Idempotent on purpose see laravel-workkit/PRINCIPLES/laravel-composer-packages.md
* (section "Migrations: hybrid auto-load + publishable"). The whole point of
* the hybrid pattern is that a consumer can `composer require` + `php artisan
* migrate` and have it work, OR publish the migrations to customise them
* without the auto-loaded version re-creating the table underneath. Either
* path must succeed even if the other already ran.
*/
return new class extends Migration {
public function up(): void
{
$name = config('files.table_names.files', 'files');
if (Schema::hasTable($name)) {
return;
}
Schema::create($name, function (Blueprint $table) {
$table->uuid('id')->primary();
// UUID rather than `unsignedBigInteger` so the package works
// out-of-the-box with the UUID-PK conventions used across the
// Blax fleet (see PRINCIPLES section "UUIDs or ULIDs for
// everything"). Hosts on bigint user IDs can publish the
// migration and swap this column type before running it.
$table->uuid('user_id')->nullable()->index();
$table->string('name')->nullable();
$table->string('type')->nullable();
$table->string('extension')->nullable();
$table->unsignedBigInteger('size')->nullable();
$table->string('disk')->default(config('files.disk', 'local'));
$table->string('relativepath')->nullable();
$table->json('meta')->nullable();
$table->timestamp('last_accessed_at')->nullable();
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists(config('files.table_names.files', 'files'));
}
};

View File

@ -0,0 +1,53 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
/**
* Creates the `filables` pivot the polymorphic attachment table that
* links a {@see \Blax\Files\Models\File} to whatever host model owns it.
*
* Idempotent same rationale as create_blax_files_table. The hybrid
* auto-load + publishable pattern only works if re-running the migration
* (after publish, or after someone partially created the schema by hand)
* is a no-op rather than a crash.
*/
return new class extends Migration {
public function up(): void
{
$name = config('files.table_names.filables', 'filables');
if (Schema::hasTable($name)) {
return;
}
Schema::create($name, function (Blueprint $table) {
// UUID PK on the pivot so it pairs cleanly with the UUID-keyed
// {@see \Blax\Files\Models\Filable} model (which uses HasUuids).
$table->uuid('id')->primary();
$table->uuid('file_id');
// uuidMorphs for the same reason `user_id` is a UUID on the
// files table: Blax host apps are UUID-PK by convention, so a
// bigint morph FK wouldn't fit. Hosts that need bigint can
// publish + edit.
$table->uuidMorphs('filable');
$table->string('as')->nullable()->index();
$table->smallInteger('order')->nullable()->default(null);
$table->json('meta')->nullable();
$table->timestamps();
$table->foreign('file_id')
->references('id')
->on(config('files.table_names.files', 'files'))
->cascadeOnDelete();
$table->unique(['file_id', 'filable_type', 'filable_id', 'as'], 'filables_unique');
});
}
public function down(): void
{
Schema::dropIfExists(config('files.table_names.filables', 'filables'));
}
};

View File

@ -1,33 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::create(config('files.table_names.filables', 'filables'), function (Blueprint $table) {
$table->id();
$table->uuid('file_id');
$table->morphs('filable');
$table->string('as')->nullable()->index();
$table->smallInteger('order')->nullable()->default(null);
$table->json('meta')->nullable();
$table->timestamps();
$table->foreign('file_id')
->references('id')
->on(config('files.table_names.files', 'files'))
->cascadeOnDelete();
$table->unique(['file_id', 'filable_type', 'filable_id', 'as'], 'filables_unique');
});
}
public function down(): void
{
Schema::dropIfExists(config('files.table_names.filables', 'filables'));
}
};

View File

@ -1,30 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::create(config('files.table_names.files', 'files'), function (Blueprint $table) {
$table->uuid('id')->primary();
$table->unsignedBigInteger('user_id')->nullable()->index();
$table->string('name')->nullable();
$table->string('type')->nullable();
$table->string('extension')->nullable();
$table->unsignedBigInteger('size')->nullable();
$table->string('disk')->default(config('files.disk', 'local'));
$table->string('relativepath')->nullable();
$table->json('meta')->nullable();
$table->timestamp('last_accessed_at')->nullable();
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists(config('files.table_names.files', 'files'));
}
};

View File

@ -15,11 +15,40 @@ class FilesServiceProvider extends \Illuminate\Support\ServiceProvider
public function boot()
{
$this->offerPublishing();
$this->registerMigrations();
$this->registerModelBindings();
$this->registerRoutes();
$this->registerCommands();
}
/**
* Auto-load the package's migrations so fresh installs work without
* publishing. Disabled via `files.run_migrations = false` for projects
* that prefer to publish + manage migrations themselves.
*
* Follows the hybrid auto-load + publishable pattern documented in
* `laravel-workkit/PRINCIPLES/laravel-composer-packages.md`.
*/
protected function registerMigrations(): void
{
if (! config('files.run_migrations', true)) {
return;
}
$this->loadMigrationsFrom(__DIR__ . '/../database/migrations');
}
/**
* Set up publishing of config and migrations for `php artisan vendor:publish`.
*
* Migrations are published preserving the SOURCE filename the
* migrations table records filenames, so keeping them identical means
* any migration already executed via auto-load is recognised as run
* for the published copy too. Slap a fresh `date('Y_m_d_His')` on the
* publish destination and Laravel sees a brand-new migration and runs
* it again, causing the "table already exists" failures consumers used
* to hit before this rewrite.
*/
protected function offerPublishing()
{
if (! $this->app->runningInConsole()) {
@ -28,23 +57,24 @@ class FilesServiceProvider extends \Illuminate\Support\ServiceProvider
$this->publishes([
__DIR__ . '/../config/files.php' => $this->app->configPath('files.php'),
], 'files-config');
], ['files-config', 'config']);
$this->publishes([
__DIR__ . '/../database/migrations/create_blax_files_table.php.stub' => $this->getMigrationFileName('create_blax_files_table.php'),
__DIR__ . '/../database/migrations/create_blax_filables_table.php.stub' => $this->getMigrationFileName('create_blax_filables_table.php'),
], 'files-migrations');
}
$migrationsPath = __DIR__ . '/../database/migrations';
$publishMap = [];
foreach (glob($migrationsPath . '/*.php') as $sourcePath) {
$publishMap[$sourcePath] = $this->app->databasePath('migrations/' . basename($sourcePath));
}
protected function getMigrationFileName(string $migrationFileName): string
{
$timestamp = date('Y_m_d_His');
$filesystem = $this->app->make(\Illuminate\Filesystem\Filesystem::class);
$this->publishes($publishMap, ['files-migrations', 'migrations']);
return \Illuminate\Support\Collection::make([$this->app->databasePath() . DIRECTORY_SEPARATOR . 'migrations' . DIRECTORY_SEPARATOR])
->flatMap(fn($path) => $filesystem->glob($path . '*_' . $migrationFileName))
->push($this->app->databasePath() . "/migrations/{$timestamp}_{$migrationFileName}")
->first();
// Convenience tag: publish everything the package owns in one go.
$this->publishes(
array_merge(
[__DIR__ . '/../config/files.php' => $this->app->configPath('files.php')],
$publishMap,
),
'files',
);
}
protected function registerModelBindings(): void