draw/entity-migrator
Laravel package for migrating and transforming entities between data sources. Helps map fields, move records safely, and run repeatable migration workflows with configurable steps for imports, upgrades, and refactors.
Install Dependencies:
composer require draw/entity-migrator symfony/messenger doctrine/dbal spatie/laravel-messenger
spatie/laravel-messenger to bridge Symfony Messenger with Laravel Queues.Configure Doctrine DBAL: Register DBAL as a Laravel service provider:
// config/app.php
'providers' => [
Doctrine\DBAL\Bridge\Doctrine\DBALServiceProvider::class,
],
Configure DBAL in config/dbal.php to match your Laravel database connection.
First Migration Example: Create a migration class targeting an Eloquent model:
namespace App\Migrations;
use Draw\EntityMigrator\Migration;
use App\Models\User;
class AddUserVerificationFields extends Migration
{
public function up(): void
{
$this->addField(User::class, 'email_verified_at', 'timestamp', ['nullable' => true]);
$this->updateData(User::class, function ($entity) {
// Custom data transformation logic
$entity->email_verified_at = now();
});
}
public function down(): void
{
$this->removeField(User::class, 'email_verified_at');
}
}
Run via Artisan: Create a custom Artisan command:
php artisan make:command EntityMigrateCommand
Update the command to use the migrator:
use Draw\EntityMigrator\Migrator;
use Doctrine\DBAL\Connection;
class EntityMigrateCommand extends Command
{
protected $signature = 'entity:migrate {migration?}';
protected $description = 'Run entity migrations';
public function handle()
{
$migrator = new Migrator(
new Connection($this->app['db']->connection()->getDoctrineConnection()),
new \Spatie\Messenger\MessageBus()
);
$migration = $this->argument('migration') ? new $this->argument('migration') : null;
$migrator->migrate($migration);
}
}
Execute:
php artisan entity:migrate App\Migrations\AddUserVerificationFields
Where to Look First:
src/Migrations/: Store all custom migration classes.config/entity_migrator.php: Configure global settings (e.g., lock timeout, batch sizes).app/Console/Kernel.php: Register the custom command in $commands.Combine Laravel’s Schema migrations with EntityMigrator for complex data changes:
// Schema migration (simple)
Schema::table('users', function (Blueprint $table) {
$table->string('new_column')->nullable();
});
// Data migration (complex)
$migrator = new Migrator($dbalConnection, $messageBus);
$migrator->migrate(new class extends Migration {
public function up(): void {
$this->updateData(User::class, function ($user) {
$user->new_column = strtolower($user->email);
});
}
});
Use Symfony Messenger to process large datasets in batches:
// Define a message handler
class ProcessUserBatchHandler
{
public function __invoke(ProcessUserBatch $message)
{
$users = User::where('id', '>=', $message->offset)
->limit($message->batchSize)
->get();
foreach ($users as $user) {
$user->update(['status' => 'processed']);
}
}
}
// Dispatch batches via migration
public function up(): void
{
$batchSize = 1000;
$total = User::count();
for ($offset = 0; $offset < $total; $offset += $batchSize) {
$this->dispatch(new ProcessUserBatch($offset, $batchSize));
}
}
Leverage Symfony Workflow for multi-step migrations with approval gates:
// Define workflow transitions
$workflow = new Workflow(
new PlaceTransition('validate'),
new PlaceTransition('migrate'),
new PlaceTransition('notify')
);
// Integrate with Laravel events
event(new MigrationWorkflowTransitioned('migrate', [
'users_processed' => 5000,
'status' => 'in_progress'
]));
Prevent race conditions during migrations:
use Symfony\Component\Lock\LockFactory;
// In your migration class
public function up(): void
{
$lockFactory = new LockFactory();
$lock = $lockFactory->createLock('user_migration_lock', 3600); // 1-hour lock
if (!$lock->acquire()) {
throw new \RuntimeException('Migration already in progress');
}
try {
// Migration logic here
} finally {
$lock->release();
}
}
Use draw/tester to mock data transformations:
public function testMigration()
{
$migrator = new Migrator($this->createMockConnection(), $this->createMockBus());
$migration = new AddUserVerificationFields();
$this->assertNull($migration->down()); // Test rollback
// Mock data for testing
$tester = new \Draw\Tester\MigrationTester($migrator);
$tester->assertMigration($migration, [
'users' => [
['id' => 1, 'email_verified_at' => null],
['id' => 2, 'email_verified_at' => null],
],
'expected' => [
['id' => 1, 'email_verified_at' => now()],
['id' => 2, 'email_verified_at' => now()],
],
]);
}
Offload migration steps to Laravel’s queue system:
// Dispatch a migration step as a job
$this->dispatch(new MigrateUsersJob());
// Job implementation
class MigrateUsersJob implements ShouldQueue
{
public function handle()
{
$migrator = app(Migrator::class);
$migrator->migrate(new class extends Migration {
public function up(): void {
$this->updateData(User::class, function ($user) {
$user->status = 'migrated';
});
}
});
}
}
Doctrine vs. Laravel Database Abstraction:
EntityMigrator uses Doctrine DBAL, which may not align with Laravel’s Schema builder.Schema for schema changes to avoid conflicts.// Avoid mixing:
// Schema::table('users', ...); // Laravel
// $this->addField(User::class, ...); // DBAL (EntityMigrator)
Locking Overhead:
Lock component can cause timeouts or blocking in high-concurrency environments.SELECT ... FOR UPDATE) for critical sections.Rollback Limitations:
down() method or use Laravel’s migration rollbacks for schema changes.public function down(): void
{
// Fallback to Laravel's rollback for schema
Schema::table('users', function (Blueprint $table) {
$table->dropColumn('email_verified_at');
});
// Manual data rollback (if needed)
User::whereNotNull('email_verified_at')->update(['email_verified_at' => null]);
}
Symfony Messenger Complexity:
Transaction Isolation:
DB::transaction(function () {
// Process 100 records at a time
for ($i = 0; $i < 100; $i++) {
DB::transaction(function () use ($i) {
$user = User::
How can I help you explore Laravel packages today?