Skip to content

Latest commit

Β 

History

History
354 lines (271 loc) Β· 9.99 KB

File metadata and controls

354 lines (271 loc) Β· 9.99 KB

Laravel Seeder Guidelines for Claude

🚨 CRITICAL RULE #1: DatabaseSeeder.php Synchronization

⚠️ MANDATORY FOR ALL SEEDER OPERATIONS ⚠️

EVERY TIME you create, update, or delete a seeder file, you MUST update DatabaseSeeder.php:

Operation Required Action in DatabaseSeeder.php
✨ Create new seeder Add NewSeeder::class with description comment
πŸ—‘οΈ Delete seeder Remove DeletedSeeder::class from the list
πŸ“ Rename seeder Update class name from OldName::class to NewName::class
πŸ”„ Refactor seeder Verify the class is still properly included

Why This Rule Exists

  • ❌ Without this: Seeders won't run during deployment β†’ incomplete database β†’ production errors
  • βœ… With this: All seeders run properly β†’ complete database β†’ stable production

How to Verify

Always run after any seeder changes:

php scripts/verify-seeders.php

If you see errors, STOP and fix them before committing!


🎯 Smart Seeding Strategy

When creating or updating database seeders, ALWAYS follow these principles to prevent data loss and preserve user customizations:


1. βœ… Check Before Seeding

NEVER seed blindly. Always check if data already exists:

public function run(): void
{
    // ❌ BAD: Direct seeding without checking
    Setting::create(['key' => 'app_name', 'value' => 'MyApp']);

    // βœ… GOOD: Check first
    if (!Setting::where('key', 'app_name')->exists()) {
        Setting::create(['key' => 'app_name', 'value' => 'MyApp']);
    }
}

2. πŸ”„ Update Only Missing Keys

When database schema changes (new columns/keys added), ONLY add missing keys without overwriting existing values:

public function run(): void
{
    $defaultSettings = [
        'app_name' => 'MyApp',
        'app_logo' => '/logo.png',
        'new_feature_enabled' => true,  // New key added
    ];

    // ❌ BAD: Overwrite everything
    foreach ($defaultSettings as $key => $value) {
        Setting::updateOrCreate(['key' => $key], ['value' => $value]);
    }

    // βœ… GOOD: Add only missing keys
    foreach ($defaultSettings as $key => $value) {
        if (!Setting::where('key', $key)->exists()) {
            Setting::create(['key' => $key, 'value' => $value]);
        }
    }
}

3. πŸ›‘οΈ Protect User Data with Early Exit

If core settings exist, assume user has customized them and skip the entire seeder:

public function run(): void
{
    // Check for a core setting that indicates seeding was done
    if (Setting::where('key', 'app_name')->exists()) {
        $this->command->warn('⚠️  Settings already exist!');
        $this->command->info('   Skipping to preserve customizations.');
        return;  // Early exit
    }

    // Seed all default settings
    $this->seedDefaultSettings();
}

4. πŸ”§ Smart Merge Strategy

For complex data (JSON settings, arrays), merge instead of replace:

public function run(): void
{
    $existingSetting = WindowsUiSetting::where('key', 'menu_items')->first();

    if ($existingSetting) {
        $existingValue = json_decode($existingSetting->value, true);
        $newDefaults = ['item1' => 'value1', 'item2' => 'value2'];

        // ❌ BAD: Replace everything
        $existingSetting->value = json_encode($newDefaults);

        // βœ… GOOD: Merge, keeping existing values
        $merged = array_merge($newDefaults, $existingValue);
        $existingSetting->value = json_encode($merged);
        $existingSetting->save();
    } else {
        // Create new if doesn't exist
        WindowsUiSetting::create([
            'key' => 'menu_items',
            'value' => json_encode($newDefaults),
        ]);
    }
}

5. πŸ“Š Incremental Seeding Pattern

For settings with versions, use incremental seeding:

public function run(): void
{
    $currentVersion = Setting::where('key', 'seeder_version')->value('value') ?? 0;

    // Version 1: Initial settings
    if ($currentVersion < 1) {
        $this->seedVersion1();
        Setting::updateOrCreate(['key' => 'seeder_version'], ['value' => 1]);
    }

    // Version 2: Add new settings only
    if ($currentVersion < 2) {
        $this->seedVersion2NewKeysOnly();
        Setting::updateOrCreate(['key' => 'seeder_version'], ['value' => 2]);
    }
}

private function seedVersion1(): void
{
    // Initial complete seed
    Setting::create(['key' => 'app_name', 'value' => 'MyApp']);
    Setting::create(['key' => 'app_logo', 'value' => '/logo.png']);
}

private function seedVersion2NewKeysOnly(): void
{
    // Add ONLY new keys introduced in version 2
    if (!Setting::where('key', 'new_feature')->exists()) {
        Setting::create(['key' => 'new_feature', 'value' => true]);
    }
}

6. 🎯 WindowsUiSetting Specific Rules

For WindowsUiSetting model with set() method:

public function run(): void
{
    // Define all default settings
    $defaults = [
        'taskbar_position' => ['value' => 'top', 'type' => 'string'],
        'taskbar_height' => ['value' => 60, 'type' => 'integer'],
        'new_clock_format' => ['value' => '24h', 'type' => 'string'], // New!
    ];

    // Check if seeding was done before
    $coreKeysExist = WindowsUiSetting::where('key', 'taskbar_position')->exists();

    if ($coreKeysExist) {
        // Add ONLY missing keys
        foreach ($defaults as $key => $config) {
            if (!WindowsUiSetting::where('key', $key)->exists()) {
                WindowsUiSetting::set($key, $config['value'], $config['type']);
                $this->command->info("   βž• Added new setting: {$key}");
            }
        }
        $this->command->info('βœ… Updated with new settings only.');
    } else {
        // First time seeding - create all
        foreach ($defaults as $key => $config) {
            WindowsUiSetting::set($key, $config['value'], $config['type']);
        }
        $this->command->info('βœ… All settings seeded successfully!');
    }
}

7. 🚨 When to Skip vs Update

Scenario Action Reason
Core settings exist SKIP entire seeder Preserve all user customizations
New column added Add missing keys only Extend functionality without data loss
Bug fix in default value SKIP User may have corrected it already
Critical security update Warn + Manual Require admin intervention
Schema migration Migration file Not a seeder responsibility

8. πŸ“ Seeder Best Practices Checklist

Before committing any seeder:

  • Does it check if data exists before inserting?
  • Does it preserve user customizations?
  • Does it handle new keys gracefully?
  • Does it show clear console messages (warn/info)?
  • Does it have an early exit strategy?
  • Is it idempotent (safe to run multiple times)?
  • Does it log what was added vs skipped?

9. πŸ’‘ Example: Complete Smart Seeder

<?php

namespace Database\Seeders;

use App\Models\WindowsUiSetting;
use Illuminate\Database\Seeder;

class WindowsUiSeeder extends Seeder
{
    private array $defaultSettings = [
        // Core settings (check these for existence)
        'windows_taskbar_position' => ['value' => 'top', 'type' => 'string', 'core' => true],
        'windows_taskbar_height' => ['value' => 60, 'type' => 'integer', 'core' => true],

        // New settings (can be added incrementally)
        'millennium_clock_format' => ['value' => '24h', 'type' => 'string', 'core' => false],
        'millennium_clock_style' => ['value' => 'digital', 'type' => 'string', 'core' => false],
    ];

    public function run(): void
    {
        // Check if core settings exist
        $coreSettings = collect($this->defaultSettings)
            ->filter(fn($config) => $config['core'] ?? false)
            ->keys();

        $existingCoreSettings = WindowsUiSetting::whereIn('key', $coreSettings)->count();

        if ($existingCoreSettings > 0) {
            $this->updateMode();
        } else {
            $this->freshInstallMode();
        }
    }

    /**
     * Fresh install - seed everything
     */
    private function freshInstallMode(): void
    {
        $this->command->info('🌱 Fresh install: Seeding all Windows UI settings...');

        foreach ($this->defaultSettings as $key => $config) {
            WindowsUiSetting::set($key, $config['value'], $config['type']);
        }

        $count = count($this->defaultSettings);
        $this->command->info("βœ… Seeded {$count} settings successfully!");
    }

    /**
     * Update mode - add only missing keys
     */
    private function updateMode(): void
    {
        $this->command->warn('⚠️  Existing settings detected!');
        $this->command->info('   Running in update mode (add missing keys only)...');

        $added = 0;
        $skipped = 0;

        foreach ($this->defaultSettings as $key => $config) {
            if (!WindowsUiSetting::where('key', $key)->exists()) {
                WindowsUiSetting::set($key, $config['value'], $config['type']);
                $this->command->info("   βž• Added: {$key}");
                $added++;
            } else {
                $skipped++;
            }
        }

        if ($added > 0) {
            $this->command->info("βœ… Added {$added} new settings.");
        }

        if ($skipped > 0) {
            $this->command->info("   ⏭️  Skipped {$skipped} existing settings (preserved).");
        }
    }
}

πŸ” Security Reminder

NEVER automatically update:

  • User credentials
  • API keys/tokens
  • Security settings
  • Custom business logic values

These require manual review by the administrator.


πŸ“š Related Laravel Concepts

  • firstOrCreate() - Find or create (no update)
  • updateOrCreate() - Find and update or create (overwrites!)
  • firstOr() - With callback for custom logic
  • Database transactions for atomic operations
  • Schema migrations for structural changes

Last Updated: 2025-01-09 Author: Claude (AI Assistant) Version: 1.0