home / skills / hackur / web-standards-playground-showcase / state-comparison

This skill helps you replace verbose state checks with clean helper methods for readable, type-safe state logic in your models.

npx playbooks add skill hackur/web-standards-playground-showcase --skill state-comparison

Review the files below or copy the command above to add this skill to your agents.

Files (1)
SKILL.md
8.7 KB
---
name: State Comparison Patterns
description: Model helper methods for clean state checking instead of verbose Spatie Model States comparisons (63% code reduction)
allowed-tools:
  - Read
  - Write
  - Edit
  - Grep
  - Glob
---

# State Comparison Patterns

Use clean helper methods instead of verbose Spatie Model States comparisons.

## When to Use

- Checking submission or card states in application code
- Writing conditional logic based on states
- Querying database for specific states
- Refactoring verbose state comparisons

## The Problem

### ❌ Verbose Spatie Pattern (Don't Use)

```php
// 63% more code than necessary!
(new ($card->card_state)($card))->equals(CardState::QUALITY_CHECK)
(new ($submission->state)($submission))->equals(SubmissionState::COMPLETED)
```

**Issues**:
- Verbose and hard to read
- Repeated boilerplate
- Error-prone (easy to make typos)
- Difficult to maintain

### ✅ Clean Helper Methods (Always Use)

```php
// DRY, SOLID, KISS principles
$card->isCardQualityCheck()
$submission->isCompleted()
```

**Benefits**:
- 63% code reduction
- Improved readability
- Type safety
- Single source of truth
- Zero duplication

## Model Helper Methods

### SubmissionTradingCard Model

**Location**: `app/Models/SubmissionTradingCard.php`

#### State Check Methods

```php
// Check specific state
$card->isCardState(CardState::ASSESSMENT)      // true/false
$card->hasCardStateIn([CardState::ASSESSMENT, CardState::IN_PROGRESS])  // true/false

// Convenience methods for each state
$card->isCardReceived()         // CardState::RECEIVED
$card->isCardAssessment()       // CardState::ASSESSMENT
$card->isCardInProgress()       // CardState::IN_PROGRESS
$card->isCardQualityCheck()     // CardState::QUALITY_CHECK
$card->isCardLabelSlab()        // CardState::LABEL_SLAB
$card->isCardCompleted()        // CardState::COMPLETED
$card->isCardCancelled()        // CardState::CANCELLED
```

#### Query Scopes

```php
// Filter by single state
SubmissionTradingCard::whereCardState(CardState::ASSESSMENT)->get();

// Filter by multiple states
SubmissionTradingCard::whereCardStateIn([
    CardState::ASSESSMENT,
    CardState::IN_PROGRESS,
])->get();
```

### Submission Model

**Location**: `app/Models/Submission.php`

#### State Check Methods

```php
// Check specific state
$submission->isSubmissionState(SubmissionState::COMPLETED)  // true/false
$submission->hasSubmissionStateIn([SubmissionState::COMPLETED, SubmissionState::SHIPPED])  // true/false

// Convenience methods for each state
$submission->isDraft()          // SubmissionState::DRAFT
$submission->isSubmitted()      // SubmissionState::SUBMITTED
$submission->isReceived()       // SubmissionState::RECEIVED
$submission->isAssessment()     // SubmissionState::ASSESSMENT
$submission->isCompleted()      // SubmissionState::COMPLETED
$submission->isShipped()        // SubmissionState::SHIPPED
$submission->isCancelled()      // SubmissionState::CANCELLED

// Terminal state check (completed, shipped, or cancelled)
$submission->isTerminalState()
```

#### Query Scopes

```php
// Filter by single state
Submission::whereSubmissionState(SubmissionState::COMPLETED)->get();

// Filter by multiple states
Submission::whereSubmissionStateIn([
    SubmissionState::COMPLETED,
    SubmissionState::SHIPPED,
    SubmissionState::CANCELLED,
])->get();
```

## Common Usage Patterns

### Single State Check

```php
// ❌ WRONG: Verbose pattern
if ((new ($card->card_state)($card))->equals(CardState::QUALITY_CHECK)) {
    // ...
}

// ✅ CORRECT: Clean helper
if ($card->isCardQualityCheck()) {
    // ...
}
```

### Multiple States Check

```php
// ❌ WRONG: Repeated verbose checks
if ((new ($submission->state)($submission))->equals(SubmissionState::COMPLETED) ||
    (new ($submission->state)($submission))->equals(SubmissionState::SHIPPED) ||
    (new ($submission->state)($submission))->equals(SubmissionState::CANCELLED)) {
    // ...
}

// ✅ CORRECT: Array syntax
if ($submission->hasSubmissionStateIn([
    SubmissionState::COMPLETED,
    SubmissionState::SHIPPED,
    SubmissionState::CANCELLED,
])) {
    // ...
}

// ✅ EVEN BETTER: Terminal state helper
if ($submission->isTerminalState()) {
    // ...
}
```

### Database Queries

```php
// ❌ WRONG: Manual state filtering
$cards = SubmissionTradingCard::all()->filter(function ($card) {
    return (new ($card->card_state)($card))->equals(CardState::ASSESSMENT);
});

// ✅ CORRECT: Query scope
$cards = SubmissionTradingCard::whereCardState(CardState::ASSESSMENT)->get();

// ✅ CORRECT: Multiple states
$cards = SubmissionTradingCard::whereCardStateIn([
    CardState::ASSESSMENT,
    CardState::IN_PROGRESS,
])->get();
```

### Conditional Logic

```php
// ❌ WRONG: Nested ternaries with verbose checks
$status = (new ($card->card_state)($card))->equals(CardState::COMPLETED)
    ? 'done'
    : ((new ($card->card_state)($card))->equals(CardState::QUALITY_CHECK) ? 'reviewing' : 'processing');

// ✅ CORRECT: Match expression with helpers
$status = match (true) {
    $card->isCardCompleted() => 'done',
    $card->isCardQualityCheck() => 'reviewing',
    default => 'processing',
};
```

### View Logic

```php
<!-- ❌ WRONG: Verbose Blade syntax -->
@if((new ($submission->state)($submission))->equals(SubmissionState::COMPLETED))
    <span class="badge-success">Completed</span>
@endif

<!-- ✅ CORRECT: Clean helper -->
@if($submission->isCompleted())
    <span class="badge-success">Completed</span>
@endif
```

## Implementation

### Helper Method Pattern

**Generic Check**:

```php
public function isCardState(string $state): bool
{
    return $this->card_state === $state;
}

public function hasCardStateIn(array $states): bool
{
    return in_array($this->card_state, $states, true);
}
```

**Convenience Methods**:

```php
public function isCardQualityCheck(): bool
{
    return $this->isCardState(CardState::QUALITY_CHECK);
}
```

**Query Scopes**:

```php
public function scopeWhereCardState($query, string $state)
{
    return $query->where('card_state', $state);
}

public function scopeWhereCardStateIn($query, array $states)
{
    return $query->whereIn('card_state', $states);
}
```

## Refactoring Guide

### Step 1: Find Verbose Patterns

```bash
# Search for verbose Spatie comparisons
./vendor/bin/sail grep -r "new (\$.*->.*state)" app/
./vendor/bin/sail grep -r "->equals(.*State::" app/
```

### Step 2: Replace with Helpers

**Before**:

```php
if ((new ($card->card_state)($card))->equals(CardState::QUALITY_CHECK)) {
    // Process quality check
}
```

**After**:

```php
if ($card->isCardQualityCheck()) {
    // Process quality check
}
```

### Step 3: Test

```bash
# Run tests to verify behavior unchanged
./scripts/dev.sh test
```

## Benefits

### Code Reduction

**Before** (100 lines):

```php
if ((new ($card->card_state)($card))->equals(CardState::QUALITY_CHECK)) {
    // Line 1
}

if ((new ($submission->state)($submission))->equals(SubmissionState::COMPLETED)) {
    // Line 2
}

// ... 98 more lines with verbose patterns
```

**After** (37 lines):

```php
if ($card->isCardQualityCheck()) {
    // Line 1
}

if ($submission->isCompleted()) {
    // Line 2
}

// ... 35 more lines with clean helpers
```

**Savings**: 63% code reduction

### Readability

**Verbose**: `(new ($card->card_state)($card))->equals(CardState::QUALITY_CHECK)`

**Clean**: `$card->isCardQualityCheck()`

**Developer Experience**: 10x more readable, self-documenting

### Type Safety

Helper methods provide IDE autocomplete:

```php
$card->is  // ← IDE suggests:
           // isCardReceived()
           // isCardAssessment()
           // isCardInProgress()
           // isCardQualityCheck()
           // ... etc.
```

### Single Source of Truth

Change state comparison logic in ONE place:

```php
// If state comparison logic changes, update helper only
public function isCardQualityCheck(): bool
{
    // Could add additional conditions here
    return $this->isCardState(CardState::QUALITY_CHECK)
        && $this->quality_check_passed === null;
}
```

All usages automatically updated!

## Common Pitfalls

### ❌ WRONG: Still using verbose pattern

```php
if ((new ($card->card_state)($card))->equals(CardState::QUALITY_CHECK)) {
    // This defeats the purpose of helper methods!
}
```

### ❌ WRONG: Manual state string comparison

```php
if ($submission->state === 'completed') {
    // Don't use magic strings!
}
```

### ❌ WRONG: Inconsistent naming

```php
// Don't create your own naming convention
if ($card->isQC()) {  // Abbreviation unclear
    // Use isCardQualityCheck() instead
}
```

## Documentation Links

- **Model Helpers Guide**: `docs/features/state-machine/MODEL-HELPERS.md`
- **State Machine Guide**: `docs/features/state-machine/`
- **Spatie Model States**: https://github.com/spatie/laravel-model-states
- **Service Architecture**: `docs/SERVICE-ARCHITECTURE.md`

Overview

This skill provides a set of model helper patterns to replace verbose Spatie Model States comparisons with concise, testable methods and query scopes. It reduces boilerplate, improves readability, and centralizes state logic so changes are made in one place. The result is clearer conditional logic, safer refactors, and about 63% less code for common state checks.

How this skill works

Add generic state-check methods, convenience boolean helpers for each state, and query scopes on models. Helpers like isCardQualityCheck() or isCompleted() call a single isState(state) method, while scopeWhereState and scopeWhereStateIn allow efficient database filtering. Use hasStateIn([...]) for multiple-state checks and specialized helpers like isTerminalState() where appropriate.

When to use it

  • Replace repeated Spatie Model States equals(...) expressions in business logic
  • Write conditional view or controller logic that depends on model state
  • Filter queries by one or more states in repositories or controllers
  • Refactor legacy code to remove magic strings and reduce typos
  • Provide IDE-friendly, discoverable helpers for developer experience

Best practices

  • Implement a single generic check (isXState) and build convenience methods on top
  • Add scopeWhereState and scopeWhereStateIn for database-level filtering
  • Name helpers explicitly (isCardQualityCheck) — avoid ambiguous abbreviations
  • Use hasStateIn([...]) or a terminal-state helper instead of multiple OR checks
  • Write tests that assert helper behavior so refactors remain safe

Example use cases

  • Replace (new ($card->card_state)($card))->equals(...) with $card->isCardQualityCheck() in controllers
  • Use Submission::whereSubmissionStateIn([...])->get() to load submissions in multiple states efficiently
  • Use $submission->isTerminalState() to guard side effects like notifications
  • Render badges in views with @if($submission->isCompleted()) instead of verbose state builds
  • Refactor nested ternaries to a match expression driven by helper methods

FAQ

Will helpers stay accurate if comparison logic changes?

Yes. Centralizing comparisons in helper methods provides a single source of truth so you update logic in one place and all callers inherit the change.

Do query scopes handle multiple states efficiently?

Yes. scopeWhereStateIn uses whereIn so the database does the filtering and you avoid loading all models then filtering in PHP.