ForgeWire

A lightweight Livewire-like reactive component system for Forge Engine applications with server-side rendering and real-time updates.

Overview

ForgeWire provides reactive components for Forge applications with server-side rendering, state management, and real-time updates without requiring WebSockets. It uses HTTP requests to handle component interactions and state changes.

Key Capabilities

Reactive components
Server-side rendering
State management
HTTP-based updates
Component hydration
Action handling

Features

Component System

  • • Reactive component classes
  • • Server-side rendering
  • • Component lifecycle hooks
  • • View template integration

State Management

  • • Component state tracking
  • • Automatic state synchronization
  • • State validation
  • • Computed properties

Action Handling

  • • Method-based actions
  • • Automatic parameter binding
  • • Action validation
  • • Error handling

Integration

  • • Service container integration
  • • Route registration
  • • Session support
  • • CSRF protection

Installation

Use Forge Package Manager or manually download and install the module.

Using Forge Package Manager

# Install the latest version
php forge install:module ForgeWire

# Install a specific version
php forge install:module ForgeWire@1.0.5

Manual Installation

# Clone the repository
git clone https://github.com/forge-engine/modules.git

# Copy the ForgeWire module to your modules directory
cp -r modules/ForgeWire /path/to/your/forge/modules/

Dependencies: ForgeWire requires proper session management and CSRF protection for secure component interactions.

Configuration

Modify configuration file or use environment variables.

Basic Configuration

// config/forge_wire.php
return [
    "forge_wire" => [
        "example" => "hi"
    ]
];

Environment Variables

# Component settings
FORGEWIRE_CACHE_ENABLED=true
FORGEWIRE_DEBUG_MODE=false

# Session configuration
SESSION_LIFETIME=120
SESSION_SECURE=true

Usage

Create reactive components with ForgeWire for dynamic user interfaces.

Creating a Component

use App\Modules\ForgeWire\Core\WireComponent;
use App\Modules\ForgeWire\Attributes\Action;
use App\Modules\ForgeWire\Attributes\State;

class Counter extends WireComponent
{
    #[State]
    public int $count = 0;

    #[Action]
    public function increment(): void
    {
        $this->count++;
    }

    #[Action]
    public function decrement(): void
    {
        $this->count--;
    }

    public function render(): string
    {
        return $this->view('counter', ['count' => $this->count]);
    }
}

Component Template

<!-- views/counter.html -->
<div class="counter">
    <h3>Count: <span>{{ count }}</span></h3>
    <button wire:click="increment">+</button>
    <button wire:click="decrement">-</button>
</div>

Using Components in Views

ForgeWire provides several helper functions for rendering components in your views:

1. Using the wire() helper function

// Basic usage
<?= wire(\App\Components\Counter::class) ?>

// With props
<?= wire(\App\Components\Counter::class, ['start' => 10]) ?>

// With component ID
<?= wire(\App\Components\Counter::class, 'counter-1') ?>

// With both ID and props
<?= wire(\App\Components\Counter::class, 'counter-1', ['start' => 10]) ?>

2. Using the w() alias

// Shorter syntax
<?= w(\App\Components\Counter::class, ['start' => 10], 'counter-1') ?>

3. Using the wire_name() helper

// Converts kebab-case to PascalCase automatically
<?= wire_name('counter', 'counter-1') ?>
<?= wire_name('products-table', ['perPage' => 10], 'products-1') ?>

// These resolve to:
// - 'counter' -> App\Components\Counter
// - 'products-table' -> App\Components\ProductsTable

Layout Integration

Make sure to include the ForgeWire JavaScript in your layout. Add the forgewire() helper to your layout file:

<!-- Real example from main.php -->
<?php
use Forge\Core\Helpers\ModuleResources;
?>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Forge Engine</title>
    <?= ModuleResources::loadStyles("forge-ui") ?>
    <?= raw(csrf_meta()) ?>
</head>
<body>
    <div>
        <?= $content ?>
    </div>

    <script>
        window.csrfToken = "<?= window_csrf_token() ?>";
    </script>
    <?= forgewire(); ?>
    <script defer src="/assets/app/js/htmx.min.js"></script>
    <?= ModuleResources::loadScripts("forge-ui") ?>
</body>
</html>

Important: The forgewire() helper must be placed before the closing </body> tag to ensure proper loading of the ForgeWire JavaScript.

CSRF Protection

ForgeWire integrates with Forge Engine's CSRF protection. Use these helpers in your forms and layouts:

// In your layout head
<?= raw(csrf_meta()) ?>

// In your layout body
<script>
    window.csrfToken = "<?= window_csrf_token() ?>";
</script>

// In your forms
<form method="POST">
    <?= csrf_input() ?>
    <!-- Your form fields -->
</form>

Security: CSRF protection is automatically applied to all ForgeWire component actions to prevent cross-site request forgery attacks.

Checking ForgeWire Availability

Before using ForgeWire components, you can check if the module is available and properly configured:

// Check if ForgeWire is available
if (forgewire_is_available()) {
    // Safe to use ForgeWire components
    echo wire_name('counter');
    echo wire_name('products-table');
} else {
    // Fallback or error handling
    echo "ForgeWire is not available";
}

Best Practice: Always check if ForgeWire is available before using components, especially in shared layouts or conditional rendering scenarios.

Components

Base WireComponent

All ForgeWire components extend the base WireComponent class which provides core functionality.

mount(array $props = []): void

Initialize component with properties.

public function mount(array $props = []): void
{
    if (isset($props['initialValue'])) {
        $this->value = $props['initialValue'];
    }
}

render(): string

Render the component view.

public function render(): string
{
    return $this->view('component-name', [
        'data' => $this->data
    ]);
}

Actions

Action Methods

Actions are component methods that can be called from the frontend. Use the #[Action] attribute to mark methods as actions.

Basic Action

use App\Modules\ForgeWire\Attributes\Action;

class TodoList extends WireComponent
{
    #[Action]
    public function addTodo(string $title): void
    {
        $this->todos[] = [
            'id' => uniqid(),
            'title' => $title,
            'completed' => false
        ];
    }

    #[Action]
    public function toggleTodo(string $id): void
    {
        foreach ($this->todos as &$todo) {
            if ($todo['id'] === $id) {
                $todo['completed'] = !$todo['completed'];
                break;
            }
        }
    }
}

Attributes

#[Action]

Marks a method as callable from the frontend.

#[Action]
public function save(): void
{
    // Action logic
}

#[State]

Marks a property as reactive state.

#[State]
public string $message = '';

#[Computed]

Marks a method as a computed property.

#[Computed]
public function getFullName(): string
{
    return $this->firstName . ' ' . $this->lastName;
}

#[Validate]

Adds validation to component properties.

#[Validate('required|email')]
public string $email = '';

Helper Functions

Available Helper Functions

ForgeWire provides several helper functions to make working with components easier. These functions are automatically available in your views when the module is installed.

wire()

Main helper function for rendering components.

// Signature
wire(string $componentClass, mixed $a = null, mixed $b = null): string

// Examples
wire(Counter::class);
wire(Counter::class, ['start' => 10]);
wire(Counter::class, 'counter-1');
wire(Counter::class, 'counter-1', ['start' => 10]);

w()

Shorter alias for the wire() function.

// Same as wire() but shorter
w(Counter::class, ['start' => 10], 'counter-1');

wire_name()

Name-based component resolver that converts kebab-case to PascalCase.

// Converts kebab-case names to class names
wire_name('counter'); // App\Components\Counter
wire_name('products-table'); // App\Components\ProductsTable
wire_name('user-profile', ['userId' => 1], 'profile-1');

forgewire()

Includes the ForgeWire JavaScript in your layout.

// Add to your layout footer
<?= forgewire(); ?>
// Outputs: <script defer src="/assets/modules/forge-wire/js/forgewire.js" defer></script>

forgewire_is_available()

Check if ForgeWire is available and properly configured.

if (forgewire_is_available()) {
    // ForgeWire is ready to use
    echo wire(Counter::class);
}

Best Practices

Do's

  • • Keep components focused and single-purpose
  • • Use proper state management
  • • Validate user inputs
  • • Handle errors gracefully
  • • Use meaningful component names
  • • Document component APIs

Don'ts

  • • Don't create overly complex components
  • • Don't expose sensitive data in state
  • • Don't ignore validation errors
  • • Don't use direct DOM manipulation
  • • Don't bypass the component lifecycle
  • • Don't forget about security