Live binding
Name your spacecraft and see its call sign update.
Call sign: awaiting a name.
<label for="demo-name">Spacecraft name</label>
<input id="demo-name" name="name"
x-interact:model.live> Meet CakePHP Interact
Build interactive CakePHP interfaces with PHP and Alpine. Add live search, responsive forms, and smooth page visits without leaving the stack you know.
CakePHP 6.x (unreleased) · PHP 8.5+
Add to existing CakePHP pages No JavaScript build required
<?php
class ExoplanetsIsland extends Island
{
#[State(writable: true)]
public string $search = '';
#[Pagination(url: true, history: ['page'])]
public PaginationState $paging;
public function viewData(): array
{
$planets = $this->fetchTable(ExoplanetsTable::class);
$query = $planets->find('search', search: $this->search);
return ['planets' => $this->paginate($query, ['limit' => 2])];
}
} <?php
/** @var \App\Model\Entity\Exoplanet $planet */
?>
<form x-interact:submit="$server.sync()">
<label for="planet-search">Find a planet</label>
<input id="planet-search" type="search" name="search"
x-interact:model.live>
</form>
<?php foreach ($planets as $planet): ?>
<p><?= h($planet->name) ?></p>
<?php endforeach; ?>
<?= $this->Paginator->prev('Previous') ?>
<?= $this->Paginator->next('Next') ?> 300 planets in the collection
PHP → HTMLDiscovered in 2008
Discovered in 2009
Planet data from the NASA Exoplanet Archive.
How it works
Define behavior in PHP, express interactions with Alpine, and let Interact keep state and rendered HTML in sync.
Declare typed state and exposed actions. Use your CakePHP models, validation, and templates, with signed snapshots carrying island state between requests.
Use Alpine for local state, events, and transitions. Bind controls to PHP properties and call server actions directly from the same template.
Nest independent islands, pass reactive props, and coordinate through events. Stream progress, poll for updates, or connect a realtime provider when you need it.
Try Interact
Try each interaction, then take a peek at the code that makes it work.
Name your spacecraft and see its call sign update.
Call sign: awaiting a name.
<label for="demo-name">Spacecraft name</label>
<input id="demo-name" name="name"
x-interact:model.live> Pack supplies with an action that runs in PHP.
<?php
#[Action]
public function increment(): void
{
$this->count++;
} Run a simulated system check and see when it is working.
Simulated check · 450ms
<button :disabled="$server.busy">
<span x-show="$server.busy">Working…</span>
</button> Choose your display spacing. The URL remembers it.
<?php
#[State(writable: true, url: true, history: true)]
public string $density = 'comfortable'; Keep the mission clock fresh while this island is visible.
Mission clock · UTC · Updates every 5 seconds
<section x-interact:poll.5s.visible>
<!-- Fresh PHP output, just here. -->
</section> Open mission details instantly with local browser state.
Destination: the stars. This detail opens locally.
<div x-data="{ expanded: false }">
<button @click="expanded = !expanded">
View mission details
</button>
</div> Choose a snapshot. Interact uploads it, and Glide gives it a cosmic finish.
<?php
// src/Island/ImageUploadIsland.php
class ImageUploadIsland extends Island
{
#[Upload(maxFileSize: '2MB')]
public ?UploadedFileInterface $image = null;
}
// templates/island/image_upload.php
?>
<input type="file" name="image" x-interact:model>
<?php if ($image !== null): ?>
<?= $this->Glide->image($thumbnailPath, ['p' => 'space']) ?>
<?php endif; ?> Simplified: validation and Glide configuration omitted.
Send a signal from one island and watch another respond.
<?php
// In the sending island:
#[Action]
public function send(): void
{
$this->dispatch('mission-signal');
}
// In the receiving island:
#[Action(on: 'mission-signal')]
public function receive(): void
{
$this->received++;
} Two independent islands, connected by an event.
Watch a simulated mission report arrive in stages from one PHP action.
Simulated report · One request, three updates.
<?php
#[Action(stream: true)]
public function generate(): void
{
$line = 'Scanning the horizon.';
$this->stream('report', $line . "\n");
} More with Interact
There is more to explore when your application needs it.
Refresh subscribed islands or receive events from other requests and jobs. Bring a provider adapter, including the optional Mercure integration.
Realtime subscriptionsUse real links with enhanced navigation. Titles, history, scroll, and focus follow the visit without a full browser reload.
Explore navigationPass your ORM query to paginate(). The Paginator helper produces real links that update the island and keep its filters in the URL.
Paginate an islandNest independent islands, pass reactive props, bind child values, and coordinate through events. Load them lazily when useful.
Compose islands Explore lazy loadingSend files with your island action and show upload progress. Keep validation, storage, and permissions in your CakePHP application.
Handle uploadsBuild one
A PHP class, a template, and one render call. Once you have installed Interact, these three pieces make a working counter.
Declare the count as state and expose a PHP action to change it.
src/Island/CounterIsland.php
Use ordinary HTML and Alpine to call the action and show the result.
templates/island/counter.php
Add the island to an existing view. Your controller keeps its normal action.
An existing view template
<?php
declare(strict_types=1);
namespace App\Island;
use Interact\Attribute\Action;
use Interact\Attribute\State;
use Interact\Island;
/** Demonstrates server-owned state changed through an explicit PHP action. */
class CounterIsland extends Island
{
#[State]
public int $count = 0;
/** Increment this island without touching the other examples. */
#[Action]
public function increment(): void
{
$this->count++;
}
} <?php
declare(strict_types=1);
/** @var int $count */
?>
<p aria-live="polite">Count: <?= $count ?></p>
<button type="button"
@click="$server.call('increment')"
:disabled="$server.busy">
Add one
</button>
<p role="alert"
x-text="$server.error ? $server.error.message : ''"></p> <?php
declare(strict_types=1);
/** @var \App\View\AppView $this */
?>
<?= $this->Interact->render('Counter') ?> A search box, a live preview, or an inline editor. Add Interact where your application needs it.
Requires PHP 8.5+ and CakePHP 6.x, currently unreleased.