Weave Code
Code Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Model Flags Laravel Package

spatie/laravel-model-flags

Add lightweight flags to Eloquent models without extra columns. Set and check flags, then query with handy flagged/notFlagged scopes. Ideal for idempotent, restartable jobs and commands (e.g., send a mail only once per user).

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup

  1. Installation:

    composer require spatie/laravel-model-flags
    

    Publish the config (optional, default settings work out-of-the-box):

    php artisan vendor:publish --provider="Spatie\LaravelModelFlags\LaravelModelFlagsServiceProvider"
    
  2. Usage on a Model: Use the HasFlags trait in your Eloquent model:

    use Spatie\LaravelModelFlags\HasFlags;
    
    class User extends Model
    {
        use HasFlags;
    }
    
  3. First Use Case: Check or set a flag on a model instance:

    $user = User::find(1);
    $user->hasFlag('received_email'); // false
    $user->flag('received_email');
    $user->hasFlag('received_email'); // true
    
  4. Querying Flags: Fetch models with/without a flag:

    User::flagged('received_email')->get(); // Users with the flag
    User::notFlagged('received_email')->get(); // Users without the flag
    

Implementation Patterns

Core Workflows

  1. Idempotent Operations: Use flags to track progress in long-running processes (e.g., batch emails, migrations, or API calls):

    foreach ($users as $user) {
        if (!$user->hasFlag('processed_payment')) {
            $user->processPayment();
            $user->flag('processed_payment');
        }
    }
    
  2. Feature Rollouts: Gradually enable features for users:

    if ($user->hasFlag('new_ui_enabled')) {
        return view('new-ui');
    }
    
  3. Audit Trails: Track state changes without migrations:

    $order->flag('shipped');
    $order->flag('refund_processed');
    

Integration Tips

  1. Custom Flag Storage: Override getFlagsAttribute() or setFlagsAttribute() to use a custom column or storage mechanism:

    protected function getFlagsAttribute()
    {
        return $this->flags ?? json_decode($this->custom_flags_column, true);
    }
    
  2. Scopes for Complex Queries: Combine with other scopes:

    User::active()->flagged('premium')->get();
    
  3. Flag Groups: Use prefixes or namespacing for related flags (e.g., email_*, payment_*):

    $user->flag('email_welcome_sent');
    $user->flag('email_invoice_sent');
    
  4. Artisan Commands: Leverage flags to resume interrupted commands:

    $users = User::notFlagged('backup_processed')->get();
    foreach ($users as $user) {
        $user->backup();
        $user->flag('backup_processed');
    }
    
  5. Events/Observers: Trigger actions when flags are set/removed:

    class UserObserver {
        public function saving(User $user) {
            if ($user->isDirty('flags') && $user->hasFlag('admin')) {
                event(new AdminFlagUpdated($user));
            }
        }
    }
    
  6. API Responses: Include flags in API responses for client-side state management:

    return UserResource::make($user)->additional([
        'flags' => $user->flags,
    ]);
    

Gotchas and Tips

Pitfalls

  1. Flag Collisions: Avoid using generic flag names (e.g., active, deleted) that conflict with existing model attributes or Laravel conventions. Prefix flags (e.g., email_verified).

  2. Serialization Issues: Flags are stored as JSON in a flags column. Ensure your database supports JSON (MySQL 5.7+, PostgreSQL). For older databases, override getFlagsAttribute() to use a text column with manual serialization.

  3. Race Conditions: Concurrent processes may overwrite flags. Use transactions or optimistic locking:

    DB::transaction(function () use ($user) {
        if (!$user->hasFlag('processed')) {
            $user->process();
            $user->flag('processed');
        }
    });
    
  4. Memory Usage: Loading models with many flags can bloat memory. Use with() to eager-load flags only when needed:

    User::with('flags')->find($id); // Avoid if flags aren't required.
    
  5. Testing Quirks: Reset flags in tests to avoid test pollution:

    public function tearDown(): void {
        $this->user->flags = [];
        $this->user->save();
    }
    

Debugging

  1. Flag Not Saving: Verify the flags column exists and is JSON-compatible. Check for typos in flag names (case-sensitive).

  2. Query Performance: Scopes (flagged, notFlagged) use JSON_CONTAINS (MySQL) or jsonb operators (PostgreSQL). Add indexes to the flags column:

    -- MySQL 5.7+
    ALTER TABLE users ADD FULLTEXT(flags);
    
    -- PostgreSQL
    CREATE INDEX users_flags_idx ON users USING gin(flags);
    
  3. Flag Data Corruption: If flags become malformed (e.g., invalid JSON), manually repair the column:

    $user->flags = json_decode($user->flags, true) ?: [];
    $user->save();
    

Extension Points

  1. Custom Flag Validation: Override flag() to validate flags before setting:

    public function flag(string $flag): void
    {
        if (!preg_match('/^[a-z_]+$/', $flag)) {
            throw new \InvalidArgumentException("Flag must be lowercase alphanumeric.");
        }
        parent::flag($flag);
    }
    
  2. Flag Expiry: Add TTL to flags using a flagged_at column:

    public function flag(string $flag, ?Carbon $expiresAt = null): void
    {
        $this->flags[$flag] = $expiresAt?->timestamp;
        $this->save();
    }
    
    public function hasFlag(string $flag): bool
    {
        return $this->flags[$flag] ?? false &&
               (!isset($this->flags[$flag]) || now()->timestamp() < $this->flags[$flag]);
    }
    
  3. Flag Events: Dispatch events when flags change:

    protected function setFlagsAttribute($value)
    {
        $oldFlags = $this->flags;
        $this->flags = $value;
        $this->save();
        $this->dispatchFlagEvents($oldFlags);
    }
    
    private function dispatchFlagEvents(array $oldFlags)
    {
        foreach (array_diff($this->flags, $oldFlags) as $flag) {
            event(new FlagAdded($this, $flag));
        }
        foreach (array_diff($oldFlags, $this->flags) as $flag) {
            event(new FlagRemoved($this, $flag));
        }
    }
    
  4. Bulk Flag Operations: Add static methods for batch updates:

    class User extends Model {
        public static function flagMany(array $ids, string $flag): void
        {
            self::whereIn('id', $ids)->get()->each(fn ($user) => $user->flag($flag));
        }
    }
    
  5. Flag Migration Helper: Create a migration helper to add the flags column:

    use Spatie\LaravelModelFlags\Migrations\FlagsMigration;
    
    Schema::table('users', function (Blueprint $table) {
        FlagsMigration::jsonColumn($table, 'flags');
    });
    
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
davejamesmiller/laravel-breadcrumbs
artisanry/parsedown
christhompsontldr/phpsdk
enqueue/dsn
bunny/bunny
enqueue/test
enqueue/null
enqueue/amqp-tools
milesj/emojibase
bower-asset/punycode
bower-asset/inputmask
bower-asset/jquery
bower-asset/yii2-pjax
laravel/nova
spatie/laravel-mailcoach
spatie/laravel-superseeder
laravel/liferaft
nst/json-test-suite
danielmiessler/sec-lists
jackalope/jackalope-transport