PHP code example of roddy / stateforge

1. Go to this page and download the library: Download roddy/stateforge library. Choose the download type require.

2. Extract the ZIP file and open the index.php.

3. Add this code to the index.php.
    
        
<?php
require_once('vendor/autoload.php');

/* Start to develop here. Best regards https://php-download.com/ */

    

roddy / stateforge example snippets




namespace App\Stores;

use Roddy\StateForge\Stores\BaseStore;

class CounterStore extends BaseStore
{
    protected string $persistenceType = 'file';

    protected function initializeState(): array
    {
        return [
            'count' => 0,
            'created_at' => now()->toISOString(),

            'increment' => function (int $by = 1) {
                $this->setState(fn($state) => array_merge($state, [
                    'count' => $state['count'] + $by,
                    'updated_at' => now()->toISOString()
                ]));
            },

            'decrement' => function (int $by = 1) {
                $this->setState(fn($state) => array_merge($state, [
                    'count' => $state['count'] - $by,
                    'updated_at' => now()->toISOString()
                ]));
            },

            'resetState' => function () {
                $this->setState(fn($state) => array_merge($state, [
                    'count' => 0,
                    'updated_at' => now()->toISOString()
                ]));
            },

            'getInfo' => function () {
                return [
                    'count' => $this->count,
                    'created_at' => $this->created_at,
                    'persistence' => $this->getPersistenceType()
                ];
            }
        ];
    }

    protected function middlewares(): array
    {
        return []; // Add custom middlewares here. Closure or Class
    }

    protected function onUpdate(array $previousState, array $newState): void
    {
        // Automatic logging on every state change
        \Log::info('Counter state updated', [
            'previous_count' => $previousState['count'] ?? 0,
            'new_count' => $newState['count'] ?? 0,
            'changes' => array_diff_assoc($newState, $previousState)
        ]);
    }
}



namespace App\Http\Controllers;

use Roddy\StateForge\Facades\StateForge;
use App\Stores\CounterStore;

class CounterController extends Controller
{
    public function show()
    {
        $counter = StateForge::get(CounterStore::class); // or useStore(CounterStore::class)

        return response()->json([
            'count' => $counter->count,
            'info' => $counter->getInfo()
        ]);
    }

    public function increment()
    {
        $counter = StateForge::get(CounterStore::class); // or useStore(CounterStore::class)
        $counter->increment();

        // onUpdate() is automatically called with logging!

        return response()->json([
            'success' => true,
            'new_count' => $counter->count
        ]);
    }
}

// routes/web.php or routes/api.php
Route::get('/counter', [CounterController::class, 'show']);
Route::post('/counter/increment', [CounterController::class, 'increment']);

class YourStore extends BaseStore
{
    // 1. Define persistence type (file, cache, session, none)
    protected string $persistenceType = 'file';

    // 2. Initialize your state and methods
    protected function initializeState(): array { /* ... */ }

    // 3. Add custom middlewares
    protected function middlewares(): array { /* ... */ }

    // 4. Handle state changes automatically
    protected function onUpdate(array $previousState, array $newState): void { /* ... */ }
}

class UserStore extends BaseStore
{
    protected string $persistenceType = 'file';

    protected function initializeState(): array
    {
        return [
            'user' => null,
            'is_logged_in' => false,
            // ... methods
        ];
    }

    protected function middlewares(): array
    {
        return [
            // Custom middleware that runs on every state change
            function(callable $updater, array $state) {
                // Called before state update
                Log::debug('Middleware: Before update');
                $newState = $updater($state);
                Log::debug('Middleware: After update');
                return $newState;
            }
        ];
    }

    protected function onUpdate(array $previousState, array $newState): void
    {
        // Automatic handling after every state change
        if ($previousState['is_logged_in'] !== $newState['is_logged_in']) {
            event(new UserLoginStatusChanged(
                $newState['is_logged_in'],
                $newState['user']
            ));
        }
    }
}

protected function middlewares(): array
{
    return [
        // Validation middleware
        function(callable $updater, array $state) {
            $newState = $updater($state);

            // Validate state
            if (isset($newState['count']) && $newState['count'] < 0) {
                throw new \InvalidArgumentException('Count cannot be negative');
            }

            return $newState;
        },

        // Logging middleware
        function(callable $updater, array $state) {
            $start = microtime(true);
            $newState = $updater($state);
            $duration = microtime(true) - $start;

            Log::debug('State update completed', [
                'duration' => $duration,
                'changes' => array_diff_assoc($newState, $state)
            ]);

            return $newState;
        },

        // Analytics middleware
        function(callable $updater, array $state) {
            $newState = $updater($state);

            if (app()->environment('production')) {
                Analytics::track('state_updated', [
                    'store' => static::class,
                    'changes' => array_keys(array_diff_assoc($newState, $state))
                ]);
            }

            return $newState;
        }
    ];
}

protected function onUpdate(array $previousState, array $newState): void
{
    // Example 1: Automatic syncing with database
    if (isset($newState['user']) && $newState['user'] !== $previousState['user']) {
        // Sync user preferences to database
        DB::table('user_preferences')->updateOrCreate(
            ['user_id' => $newState['user']['id']],
            ['preferences' => json_encode($newState['preferences'])]
        );
    }

    // Example 2: Real-time broadcasting
    if (isset($newState['cart_items']) && $newState['cart_items'] !== $previousState['cart_items']) {
        broadcast(new CartUpdated($newState['cart_items']));
    }

    // Example 3: Cache invalidation
    if (isset($newState['settings'])) {
        Cache::forget('user_settings_' . auth()->id());
    }

    // Example 4: Audit logging
    AuditLog::create([
        'event' => 'state_update',
        'store' => static::class,
        'old_state' => $previousState,
        'new_state' => $newState,
        'changes' => array_diff_assoc($newState, $previousState)
    ]);
}



namespace App\Stores;

use Roddy\StateForge\Stores\BaseStore;

class CartStore extends BaseStore
{
    protected string $persistenceType = 'file';

    protected function initializeState(): array
    {
        return [
            'items' => [],
            'total' => 0,
            'item_count' => 0,
            'coupon' => null,
            'discount' => 0,
            'created_at' => now()->toISOString(),

            'addItem' => function ($productId, $name, $price, $quantity = 1) {
                $this->setState(function($state) use ($productId, $name, $price, $quantity) {
                    $items = $state['items'];
                    $existingIndex = $this->findItemIndex($items, $productId);

                    if ($existingIndex !== -1) {
                        $items[$existingIndex]['quantity'] += $quantity;
                    } else {
                        $items[] = [
                            'id' => $productId,
                            'name' => $name,
                            'price' => $price,
                            'quantity' => $quantity,
                            'added_at' => now()->toISOString()
                        ];
                    }

                    return $this->calculateCartTotals(array_merge($state, [
                        'items' => $items,
                        'updated_at' => now()->toISOString()
                    ]));
                });
            },

            'removeItem' => function ($productId) {
                $this->setState(function($state) use ($productId) {
                    $items = array_filter($state['items'], fn($item) => $item['id'] !== $productId);

                    return $this->calculateCartTotals(array_merge($state, [
                        'items' => array_values($items),
                        'updated_at' => now()->toISOString()
                    ]));
                });
            },

            'applyCoupon' => function ($code) {
                $this->setState(function($state) use ($code) {
                    $coupon = \App\Models\Coupon::where('code', $code)->valid()->first();
                    $discount = $coupon ? $coupon->calculateDiscount($state['total']) : 0;

                    return array_merge($state, [
                        'coupon' => $coupon,
                        'discount' => $discount,
                        'updated_at' => now()->toISOString()
                    ]);
                });
            },

            'clearCart' => function () {
                $this->setState(fn($state) => array_merge($state, [
                    'items' => [],
                    'total' => 0,
                    'item_count' => 0,
                    'coupon' => null,
                    'discount' => 0,
                    'updated_at' => now()->toISOString()
                ]));
            },

            'getSummary' => function () {
                return [
                    'item_count' => $this->item_count,
                    'total' => $this->total,
                    'discount' => $this->discount,
                    'final_total' => $this->total - $this->discount,
                    'items' => $this->items,
                    'persistence' => $this->getPersistenceType()
                ];
            }
        ];
    }

    protected function middlewares(): array
    {
        return [
            // Validate cart items
            function(callable $updater, array $state) {
                $newState = $updater($state);

                // Ensure quantities are positive
                foreach ($newState['items'] as $item) {
                    if ($item['quantity'] < 1) {
                        throw new \InvalidArgumentException('Item quantity must be at least 1');
                    }
                }

                return $newState;
            }
        ];
    }

    protected function onUpdate(array $previousState, array $newState): void
    {
        // Log cart changes
        \Log::info('Cart updated', [
            'previous_items' => count($previousState['items']),
            'new_items' => count($newState['items']),
            'total_change' => $newState['total'] - $previousState['total']
        ]);

        // Broadcast real-time updates
        if ($newState['items'] !== $previousState['items']) {
            broadcast(new \App\Events\CartUpdated(
                auth()->user(),
                $newState['items'],
                $newState['total']
            ));
        }

        // Sync to database if user is logged in
        if (auth()->check() && $newState['items'] !== $previousState['items']) {
            \App\Jobs\SyncCartToDatabase::dispatch(
                auth()->id(),
                $newState['items']
            );
        }
    }

    private function findItemIndex(array $items, $productId): int
    {
        foreach ($items as $index => $item) {
            if ($item['id'] === $productId) {
                return $index;
            }
        }
        return -1;
    }

    private function calculateCartTotals(array $state): array
    {
        $items = $state['items'];
        $item_count = array_sum(array_column($items, 'quantity'));
        $subtotal = array_sum(array_map(fn($item) => $item['price'] * $item['quantity'], $items));

        return array_merge($state, [
            'item_count' => $item_count,
            'subtotal' => $subtotal,
            'total' => $subtotal - ($state['discount'] ?? 0)
        ]);
    }
}



namespace App\Stores;

use Roddy\StateForge\Stores\BaseStore;

class UserPreferencesStore extends BaseStore
{
    protected string $persistenceType = 'cache';

    protected function initializeState(): array
    {
        return [
            'theme' => 'light',
            'language' => 'en',
            'notifications' => true,
            'font_size' => 'medium',
            'timezone' => 'UTC',
            'created_at' => now()->toISOString(),

            'setTheme' => function (string $theme) {
                $this->setState(fn($state) => array_merge($state, [
                    'theme' => $theme,
                    'updated_at' => now()->toISOString()
                ]));
            },

            'setLanguage' => function (string $language) {
                $this->setState(fn($state) => array_merge($state, [
                    'language' => $language,
                    'updated_at' => now()->toISOString()
                ]));
            },

            'toggleNotifications' => function () {
                $this->setState(fn($state) => array_merge($state, [
                    'notifications' => !$state['notifications'],
                    'updated_at' => now()->toISOString()
                ]));
            },

            'updateAll' => function (array $preferences) {
                $this->setState(fn($state) => array_merge($state, [
                    ...$preferences,
                    'updated_at' => now()->toISOString()
                ]));
            },

            'getPreferences' => function () {
                return [
                    'theme' => $this->theme,
                    'language' => $this->language,
                    'notifications' => $this->notifications,
                    'font_size' => $this->font_size,
                    'timezone' => $this->timezone,
                    'persistence' => $this->getPersistenceType()
                ];
            }
        ];
    }

    protected function middlewares(): array
    {
        return [
            // Validate preferences
            function(callable $updater, array $state) {
                $newState = $updater($state);

                $allowedThemes = ['light', 'dark', 'system'];
                $allowedLanguages = ['en', 'es', 'fr', 'de'];
                $allowedFontSizes = ['small', 'medium', 'large'];

                if (isset($newState['theme']) && !in_array($newState['theme'], $allowedThemes)) {
                    throw new \InvalidArgumentException("Invalid theme: {$newState['theme']}");
                }

                if (isset($newState['language']) && !in_array($newState['language'], $allowedLanguages)) {
                    throw new \InvalidArgumentException("Invalid language: {$newState['language']}");
                }

                if (isset($newState['font_size']) && !in_array($newState['font_size'], $allowedFontSizes)) {
                    throw new \InvalidArgumentException("Invalid font size: {$newState['font_size']}");
                }

                return $newState;
            }
        ];
    }

    protected function onUpdate(array $previousState, array $newState): void
    {
        // Sync to database when user is logged in
        if (auth()->check()) {
            \App\Jobs\SyncPreferencesToDatabase::dispatch(
                auth()->id(),
                $newState
            );
        }

        // Update session based on preferences
        if ($newState['theme'] !== $previousState['theme']) {
            session(['theme' => $newState['theme']]);
        }

        if ($newState['language'] !== $previousState['language']) {
            session(['locale' => $newState['language']]);
            app()->setLocale($newState['language']);
        }

        // Log preference changes
        \Log::info('User preferences updated', [
            'user_id' => auth()->id(),
            'changes' => array_diff_assoc($newState, $previousState)
        ]);
    }
}

use Roddy\StateForge\Facades\StateForge;
use App\Stores\CounterStore;

// Get or create store (auto-discovered)
$store = StateForge::get(CounterStore::class);

// Create store with custom configuration
$store = StateForge::create(CounterStore::class, [
    'persistence' => 'cache',
    'cache_ttl' => 3600
]);

// Store management
StateForge::all(); // Get all stores
StateForge::exists(CounterStore::class); // Check if store exists
StateForge::reset(CounterStore::class); // Reset specific store
StateForge::reset(); // Reset all stores
StateForge::getStoreInfo(); // Get store information
StateForge::getClientId(); // Get client identifier

// Change persistence at runtime
StateForge::setPersistence(CounterStore::class, 'file');

$counter = StateForge::get(CounterStore::class);  // or useStore(CounterStore::class)

// Access state properties
echo $counter->count;
echo $counter->created_at;

// Call store methods
$counter->increment(5);
$counter->decrement(2);
$counter->resetState();

// Get state
$state = $counter->getState();

// Hooks
$counter->before('method', $callback);
$counter->after('method', $callback);

// Events
$counter->on('event', $listener);
$counter->off('event', $listener);

// Lifecycle info
$persistence = $counter->getPersistenceType(); // 'file', 'cache', 'session', 'none'
$hooks = $counter->getHookInfo(); // Get hook information
$listeners = $counter->getEventListeners(); // Get event listeners

protected string $persistenceType = 'file';

protected string $persistenceType = 'cache';

protected string $persistenceType = 'session';

protected string $persistenceType = 'none';

return [
    'default' => [
        'persistence' => 'file',
        'auto_persist' => true,
    ],

    'persistence' => [
        'file' => [
            'path' => storage_path('app/private/stateforge'),
            'auto_cleanup' => true,
            'cleanup_after_days' => 30,
        ],

        'cache' => [
            'driver' => null, // Use default cache driver
            'prefix' => 'stateforge',
            'ttl' => 3600 * 24 * 30, // 30 days
        ],

        'session' => [
            'prefix' => 'stateforge',
        ],
    ],

    'client' => [
        'cookie_name' => 'stateforge_client_id',
        'cookie_lifetime' => 60 * 24 * 365, // 1 year
        'cleanup_after_days' => 30,
    ],

    'auto_discovery' => [
        'enabled' => true,
        'path' => app_path('Stores'),
    ],
];



namespace App\Livewire;

use Livewire\Component;
use Roddy\StateForge\Facades\StateForge;
use App\Stores\CounterStore;

class CounterComponent extends Component
{
    public $count = 0;

    protected $counter;

    public function mount()
    {
        $this->counter = StateForge::get(CounterStore::class); // or useStore(CounterStore::class)
        $this->count = $this->counter->count;

        // onUpdate() will handle automatic syncing
    }

    public function increment()
    {
        $this->counter->increment();
        $this->count = $this->counter->count;
    }

    public function render()
    {
        return view('livewire.counter-component');
    }
}



namespace App\StateForge\Middlewares;

use Roddy\StateForge\Contracts\Middleware;

class ValidationMiddleware implements Middleware
{
    public function __invoke(callable $updater, array $state): array
    {
        $newState = $updater($state);

        // Add validation logic
        if (isset($newState['count']) && $newState['count'] < 0) {
            throw new \InvalidArgumentException('Count cannot be negative');
        }

        return $newState;
    }
}

class AnalyticsMiddleware implements Middleware
{
    public function __invoke(callable $updater, array $state): array
    {
        $newState = $updater($state);

        // Track state changes
        if (app()->environment('production')) {
            \App\Jobs\TrackStateChange::dispatch(
                get_class($this),
                array_keys(array_diff_assoc($newState, $state))
            );
        }

        return $newState;
    }
}

// Usage in store
protected function middlewares(): array
{
    return [
        \App\StateForge\Middlewares\ValidationMiddleware::class,
        \App\StateForge\Middlewares\AnalyticsMiddleware::class,
    ];
}

// ❌ Wrong - might create new instance
$store = new CounterStore();

// ✅ Correct - use facade
$store = StateForge::get(CounterStore::class);
bash
php artisan vendor:publish --provider="Roddy\\StateForge\\StateForgeServiceProvider" --tag=stateforge-config
bash
# Clean up stores for clients not seen in 30 days (default)
php artisan stateforge:cleanup

# Clean up stores for clients not seen in 60 days
php artisan stateforge:cleanup --days=60

# Schedule cleanup (in app/Console/Kernel.php)
$schedule->command('stateforge:cleanup --days=30')->daily();