preflow/components

Preflow components — component base class, renderer, error boundaries

Maintainers

Package info

github.com/getpreflow/components

pkg:composer/preflow/components

Statistics

Installs: 45

Dependents: 4

Suggesters: 0

Stars: 0

Open Issues: 0

v0.13.1 2026-04-18 00:19 UTC

README

Core component system for Preflow. Provides an abstract base class, a renderer with lifecycle management, an error boundary, and a Twig integration.

Installation

composer require preflow/components

Requires PHP 8.4+.

What it does

Each component is a PHP class co-located with a .twig template. The renderer calls resolveState() (load data from DB, session, etc.) then renders the template with the component's public properties as variables. The output is wrapped in a tagged element keyed by a stable component ID.

Errors are caught by ErrorBoundary: in dev mode it renders an inline panel with the exception class, message, component class, props, lifecycle phase, and stack trace; in prod mode it calls the component's fallback() or renders a hidden <div>.

API

Component (abstract base class)

Method Description
resolveState(): void Override to load data before render.
actions(): string[] Return allowed action names for this component.
action{Name}(array $params): void Implement one method per declared action.
fallback(\Throwable $e): ?string Return fallback HTML on error (prod only). Return null to hide.
setProps(array $props): void Set props from outside (called by the renderer/extension).
getProps(): array Read current props.
getComponentId(): string ShortClassName-{propsHash} — stable across renders with same props.
getTag(): string Wrapper HTML tag (default div). Override $tag to change.

Public properties are automatically exposed as Twig template variables alongside componentId.

ComponentRenderer

$renderer->render(Component $component): string                  // full lifecycle + wrapper
$renderer->renderFragment(Component $component): string          // inner HTML only (for HTMX partials)
$renderer->renderResolved(Component $component): string          // skip resolveState (after action dispatch)
$renderer->renderResolvedFragment(Component $component): string  // skip resolveState, inner HTML only

renderResolvedFragment() combines the two partial-render flags: it skips resolveState() (state was already mutated by an action) and returns inner HTML without the wrapper element. Used by ComponentEndpoint when the HTMX target does not match the component's own ID.

ComponentExtension (Twig)

Registers {{ component('Name', {props}) }}. Component names are resolved via a short-name → FQCN map or a fully-qualified class name.

Usage

Component class (src/Components/Counter/Counter.php):

use Preflow\Components\Component;

final class Counter extends Component
{
    public int $count = 0;

    public function resolveState(): void
    {
        $this->count = (int) ($_SESSION['count'] ?? 0);
    }

    public function actions(): array
    {
        return ['increment'];
    }

    public function actionIncrement(array $params): void
    {
        $this->count++;
        $_SESSION['count'] = $this->count;
    }
}

Template (src/Components/Counter/Counter.twig):

<p>Count: {{ count }}</p>
<button>+1</button>

In a page template:

{{ component('Counter', { initialCount: 0 }) }}

{# Or using a fully-qualified class name: #}
{{ component('App\\Components\\Counter\\Counter') }}

Register the extension:

$componentMap = ['Counter' => App\Components\Counter\Counter::class];
$twig->addExtension(new ComponentExtension($renderer, $componentMap));