How are you using Laravel migrations in Acorn?

Don’t sleep on Acorn’s support for running Laravel migrations in WordPress, it’s a great tool to use :sparkles:

Also, using LLMs to help generate them makes creating these a fairly quick process. If you’re using a CLI like Claude Code, have it run wp commands, scan plugin files, and run wp db queries to help with making migrations. A couple recent migrations I’ve done:

Rename blocks

<?php

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

return new class extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        // Update all post_content that contains oldnamespace/ block references
        $blockMappings = [
            'oldnamespace/block-name' => 'newnamespace/block-name',
            'oldnamespace/block-name-2' => 'newnamespace/block-name-2',
        ];

        foreach ($blockMappings as $oldBlock => $newBlock) {
            // Convert block namespace: oldnamespace/block-name -> newnamespace/block-name
            $oldBlockSlug = str_replace('/', '-', $oldBlock);
            $newBlockSlug = str_replace('/', '-', $newBlock);

            // Update HTML comment format
            DB::table('posts')
                ->where('post_content', 'like', "%<!-- wp:$oldBlock %")
                ->update([
                    'post_content' => DB::raw("REPLACE(post_content, '<!-- wp:$oldBlock', '<!-- wp:$newBlock')"),
                    'post_modified' => now(),
                    'post_modified_gmt' => now(),
                ]);

            // Update closing comments
            DB::table('posts')
                ->where('post_content', 'like', "%/wp:$oldBlock -->%")
                ->update([
                    'post_content' => DB::raw("REPLACE(post_content, '/wp:$oldBlock -->', '/wp:$newBlock -->')"),
                    'post_modified' => now(),
                    'post_modified_gmt' => now(),
                ]);

        }

        // Update CSS classes: wp-block-oldnamespace- -> wp-block-newnamespace-
        DB::table('posts')
            ->where('post_content', 'like', '%wp-block-oldnamespace-%')
            ->update([
                'post_content' => DB::raw("REPLACE(post_content, 'wp-block-oldnamespace-', 'wp-block-newnamespace-')"),
                'post_modified' => now(),
                'post_modified_gmt' => now(),
            ]);

        // Log the migration
        \Log::info('Migrated Gutenberg blocks from oldnamespace/ to newnamespace/ namespace', [
            'blocks_updated' => array_keys($blockMappings),
            'migration_time' => now()
        ]);
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        // Reverse the migration - change newnamespace/ back to oldnamespace/
        $blockMappings = [
            'newnamespace/block-name' => 'oldnamespace/block-name',
            'newnamespace/block-name-2' => 'oldnamespace/block-name-2',
        ];

        foreach ($blockMappings as $newBlock => $oldBlock) {
            DB::table('posts')
                ->where('post_content', 'like', '%"blockName":"' . $newBlock . '"%')
                ->orWhere('post_content', 'like', "%<!-- wp:$newBlock %")
                ->update([
                    'post_content' => DB::raw("REPLACE(post_content, '\"blockName\":\"$newBlock\"', '\"blockName\":\"$oldBlock\"')"),
                    'post_modified' => now(),
                    'post_modified_gmt' => now(),
                ]);

            // Also update HTML comment format
            DB::table('posts')
                ->where('post_content', 'like', "%<!-- wp:$newBlock %")
                ->update([
                    'post_content' => DB::raw("REPLACE(post_content, '<!-- wp:$newBlock', '<!-- wp:$oldBlock')"),
                    'post_modified' => now(),
                    'post_modified_gmt' => now(),
                ]);

            // Update closing comments
            DB::table('posts')
                ->where('post_content', 'like', "%/wp:$newBlock -->%")
                ->update([
                    'post_content' => DB::raw("REPLACE(post_content, '/wp:$newBlock -->', '/wp:$oldBlock -->')"),
                    'post_modified' => now(),
                    'post_modified_gmt' => now(),
                ]);
        }

        \Log::info('Rolled back Gutenberg blocks from newnamespace/ to oldnamespace/ namespace');
    }
};

Delete redirects from Redirection plugin that have had 0 hits

I absolutely loathe this plugin, but a site I work on has a lot of legacy redirects using it still. I’m going to end up creating a migration to move things to Safe Redirect Manager, but in the meantime…

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Facades\DB;

return new class extends Migration
{
    /**
     * Run the migrations.
     */
    public function up(): void
    {
        // Get count of redirects with 0 hits before deletion for logging
        $countBeforeDeletion = DB::table('wp_redirection_items')->where('last_count', 0)->count();

        // Log the operation
        if ($countBeforeDeletion > 0) {
            \Log::info("Deleting {$countBeforeDeletion} redirects with 0 hits from wp_redirection_items table");

            // Delete redirects with 0 hits
            $deletedCount = DB::table('wp_redirection_items')->where('last_count', 0)->delete();

            \Log::info("Successfully deleted {$deletedCount} redirects with 0 hits");
        } else {
            \Log::info("No redirects with 0 hits found to delete");
        }
    }

    /**
     * Reverse the migrations.
     */
    public function down(): void
    {
        // This migration cannot be reversed as we're deleting data
        // The redirects would need to be restored from a backup
        \Log::warning("Migration 'delete_redirects_with_no_hits' cannot be reversed - data has been permanently deleted");
    }
};
5 Likes

Thank you for these examples @ben, I really appreciate them: they are inspiring and put us on the right track, talking about best practices in using Radicle.

1 Like