aaix/laravel-patches
Laravel Patches adds a simple, command-based patching system for Laravel. Create one-off Artisan commands for data fixes and deployments, run them manually, and track executions in the database to prevent reruns. Delete patches when done.
Installation
composer require aaix/laravel-patches
php artisan vendor:publish --provider="Aaix\LaravelPatches\LaravelPatchesServiceProvider" --tag="config"
config/patches.php) and migration (database/migrations/..._create_patches_table.php).Run Migration
php artisan migrate
patches table to track patch metadata.First Patch
php artisan patch:create my_first_patch --description="Fixes critical bug in user auth"
database/patches/my_first_patch.php with a skeleton:
<?php
return function () {
// Your patch logic here
};
Apply the Patch
php artisan patch:apply my_first_patch
php artisan patch:create hotfix_user_permissions --description="Grant admin access to inactive users"
database/patches/hotfix_user_permissions.php:
return function () {
DB::table('users')->where('is_admin', false)->update(['is_admin' => true]);
};
php artisan patch:apply hotfix_user_permissions
php artisan patch:apply hotfix_user_permissions --env=production
Creation
patch:create for ad-hoc fixes or feature toggles.php artisan patch:create add_missing_index --description="Add index to orders table for performance"
Application
php artisan patch:apply add_missing_index
php artisan patch:apply --all --env=staging
php artisan patch:apply --force
Rollback
php artisan patch:create revert_missing_index --description="Revert index addition"
DB::transaction(function () {
// Patch logic
});
Artisan Commands
// app/Console/Commands/CustomPatchCommand.php
use Aaix\LaravelPatches\Commands\PatchApplyCommand;
class CustomPatchCommand extends PatchApplyCommand {
protected $signature = 'patch:custom {name}';
// Override logic
}
Event Listeners
// app/Providers/EventServiceProvider.php
protected $listen = [
\Aaix\LaravelPatches\Events\PatchApplied::class => [
\App\Listeners\LogPatchApplication::class,
],
];
Database Seeding
// database/patches/seed_initial_data.php
return function () {
factory(App\User::class, 10)->create();
};
php artisan db:seed.Environment-Specific Patches
database/patches/{env}/ (e.g., database/patches/production/) and use the --env flag:
php artisan patch:apply --env=production
Patch Dependencies
// database/patches/fix_order_total.php
return [
'run' => function () {
// Fix logic
},
'depends_on' => ['add_missing_index'], // Requires this patch first
];
Patch Testing
patch:test (if the package adds this in future) or manually:
// tests/Feature/PatchesTest.php
public function test_my_patch() {
$this->artisan('patch:apply', ['name' => 'my_first_patch'])
->expectsOutput('Patch applied successfully');
$this->assertDatabaseHas('users', ['is_admin' => true]);
}
Patch Documentation
// routes/web.php
Route::get('/patches', function () {
return \Aaix\LaravelPatches\Models\Patch::latest()->get();
});
Idempotency
DELETE queries) will cause issues.INSERT IGNORE or check existence first:
if (!DB::table('patches_metadata')->where('key', 'exists')->exists()) {
DB::table('patches_metadata')->insert(['key' => 'exists', 'value' => 1]);
}
Database Locks
DB::connection()->disableEvents() to reduce overhead.Patch Discovery
database/patches/ by default. Custom paths require config:
// config/patches.php
'patch_paths' => [
database_path('patches'),
database_path('patches/custom'),
],
Environment Mismatches
--env explicitly and restrict production access:
php artisan patch:apply --env=staging
Patch Conflicts
DB::transaction(function () {
// Critical patch logic
});
Failed Patches
patches table for status = 'failed'.storage/logs/laravel.log) for exceptions.Missing Patches
Permission Issues
bootstrap/cache/) and database/ are writable:
chmod -R 775 storage bootstrap/cache database
Naming Conventions
fix_user_auth_bug) to avoid issues with PHP constants.Patch Metadata
patches table to track additional metadata:
// Add columns via migration
Schema::table('patches', function (Blueprint $table) {
$table->string('author')->nullable();
$table->timestamp('scheduled_at')->nullable();
});
Automated Patch Application
task('deploy:patch', function () {
run('php artisan patch:apply --env=production --all');
});
Patch Validation
// app/Console/Kernel.php
protected function schedule(PatchSchedule $schedule) {
$schedule->command('patch:validate')->everyMinute();
}
Backup Before Patching
php artisan backup:run --only-db
php artisan patch:apply my_patch
Patch Testing in CI
# .github/workflows/tests.yml
jobs:
test:
runs-on: ubuntu-latest
How can I help you explore Laravel packages today?