Some checks failed
Deploy Application / deploy (push) Has been cancelled
1343 lines
25 KiB
Markdown
1343 lines
25 KiB
Markdown
# LiveComponents API Reference
|
|
|
|
**Complete API Documentation for LiveComponents System**
|
|
|
|
This reference covers all public classes, methods, attributes, and interfaces for building LiveComponents applications.
|
|
|
|
---
|
|
|
|
## Table of Contents
|
|
|
|
1. [LiveComponent Base Class](#livecomponent-base-class)
|
|
2. [Attributes](#attributes)
|
|
3. [HTTP Integration](#http-integration)
|
|
4. [JavaScript API](#javascript-api)
|
|
5. [Value Objects](#value-objects)
|
|
6. [Services](#services)
|
|
7. [Events](#events)
|
|
|
|
---
|
|
|
|
## LiveComponent Base Class
|
|
|
|
### `abstract class LiveComponent`
|
|
|
|
Base class for all LiveComponents. Provides lifecycle hooks, rendering, and state management.
|
|
|
|
#### Properties
|
|
|
|
```php
|
|
/**
|
|
* Unique component instance ID
|
|
* Auto-generated on component creation
|
|
*/
|
|
protected readonly string $id;
|
|
|
|
/**
|
|
* Component name (class name without namespace)
|
|
*/
|
|
protected readonly string $name;
|
|
```
|
|
|
|
#### Lifecycle Methods
|
|
|
|
##### `public function mount(array $initialData = []): void`
|
|
|
|
Called when component is first created.
|
|
|
|
**Parameters**:
|
|
- `$initialData` - Initial data passed to component
|
|
|
|
**Usage**:
|
|
```php
|
|
public function mount(array $initialData = []): void
|
|
{
|
|
$this->userId = $initialData['user_id'] ?? null;
|
|
$this->loadUserData();
|
|
$this->enableSse(); // Enable real-time updates
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
##### `public function hydrate(array $state): void`
|
|
|
|
Called on subsequent requests to restore component state.
|
|
|
|
**Parameters**:
|
|
- `$state` - Serialized component state from client
|
|
|
|
**Usage**:
|
|
```php
|
|
public function hydrate(array $state): void
|
|
{
|
|
// State automatically restored to #[LiveProp] properties
|
|
// Override for custom hydration logic
|
|
parent::hydrate($state);
|
|
$this->recalculateDerivedState();
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
##### `public function updated(string $property, mixed $oldValue, mixed $newValue): void`
|
|
|
|
Called when a property is updated via two-way binding.
|
|
|
|
**Parameters**:
|
|
- `$property` - Property name that changed
|
|
- `$oldValue` - Previous value
|
|
- `$newValue` - New value
|
|
|
|
**Usage**:
|
|
```php
|
|
public function updated(string $property, mixed $oldValue, mixed $newValue): void
|
|
{
|
|
if ($property === 'searchTerm' && strlen($newValue) >= 3) {
|
|
$this->performSearch();
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
##### `public function unmount(): void`
|
|
|
|
Called when component is destroyed.
|
|
|
|
**Usage**:
|
|
```php
|
|
public function unmount(): void
|
|
{
|
|
// Clean up resources
|
|
$this->closeConnections();
|
|
$this->clearCache();
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
#### Rendering
|
|
|
|
##### `abstract public function render(): string`
|
|
|
|
Render component to HTML.
|
|
|
|
**Returns**: HTML string
|
|
|
|
**Usage**:
|
|
```php
|
|
public function render(): string
|
|
{
|
|
return $this->view('components/user-profile', [
|
|
'user' => $this->user,
|
|
'posts' => $this->posts
|
|
]);
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
##### `protected function view(string $template, array $data = []): string`
|
|
|
|
Render a template with data.
|
|
|
|
**Parameters**:
|
|
- `$template` - Template path (relative to views directory)
|
|
- `$data` - Data to pass to template
|
|
|
|
**Returns**: Rendered HTML
|
|
|
|
**Usage**:
|
|
```php
|
|
protected function render(): string
|
|
{
|
|
return $this->view('components/product-list', [
|
|
'products' => $this->products,
|
|
'totalCount' => $this->totalCount
|
|
]);
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
#### State Management
|
|
|
|
##### `protected function getState(): array`
|
|
|
|
Get component state for serialization.
|
|
|
|
**Returns**: Associative array of state
|
|
|
|
**Usage**:
|
|
```php
|
|
protected function getState(): array
|
|
{
|
|
return [
|
|
'userId' => $this->userId,
|
|
'filters' => $this->filters,
|
|
'page' => $this->page
|
|
];
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
##### `protected function setState(array $state): void`
|
|
|
|
Restore component state.
|
|
|
|
**Parameters**:
|
|
- `$state` - State array to restore
|
|
|
|
---
|
|
|
|
#### Real-Time Updates
|
|
|
|
##### `protected function enableSse(array $channels = []): void`
|
|
|
|
Enable Server-Sent Events for real-time updates.
|
|
|
|
**Parameters**:
|
|
- `$channels` - Channels to subscribe to (default: component-specific channel)
|
|
|
|
**Usage**:
|
|
```php
|
|
public function mount(): void
|
|
{
|
|
$this->enableSse([
|
|
'user.updates',
|
|
"team.{$this->teamId}.activity"
|
|
]);
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
##### `protected function broadcast(string $channel, array $data): void`
|
|
|
|
Broadcast update to all clients listening on channel.
|
|
|
|
**Parameters**:
|
|
- `$channel` - Channel name
|
|
- `$data` - Data to broadcast
|
|
|
|
**Usage**:
|
|
```php
|
|
#[LiveAction]
|
|
public function updateStatus(string $status): void
|
|
{
|
|
$this->status = $status;
|
|
|
|
// Broadcast to all team members
|
|
$this->broadcast("team.{$this->teamId}.updates", [
|
|
'type' => 'status_changed',
|
|
'user' => $this->user->name,
|
|
'status' => $status
|
|
]);
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
#### Static Factory
|
|
|
|
##### `public static function mount(string $componentClass, array $initialData = []): string`
|
|
|
|
Create and render component.
|
|
|
|
**Parameters**:
|
|
- `$componentClass` - Fully qualified class name
|
|
- `$initialData` - Initial data for component
|
|
|
|
**Returns**: Rendered component HTML
|
|
|
|
**Usage**:
|
|
```php
|
|
// In controller
|
|
public function dashboard(): ViewResult
|
|
{
|
|
return new ViewResult('dashboard', [
|
|
'statsWidget' => LiveComponent::mount(StatsWidget::class, [
|
|
'user_id' => $currentUser->id
|
|
])
|
|
]);
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Attributes
|
|
|
|
### `#[LiveProp]`
|
|
|
|
Marks a property for automatic state synchronization.
|
|
|
|
**Usage**:
|
|
```php
|
|
use App\Framework\LiveComponents\Attributes\LiveProp;
|
|
|
|
final class SearchComponent extends LiveComponent
|
|
{
|
|
#[LiveProp]
|
|
public string $searchTerm = '';
|
|
|
|
#[LiveProp]
|
|
public array $filters = [];
|
|
|
|
#[LiveProp]
|
|
public int $page = 1;
|
|
}
|
|
```
|
|
|
|
**Options**:
|
|
- Properties are automatically serialized/deserialized
|
|
- Two-way binding with `data-lc-model`
|
|
- State preserved across requests
|
|
|
|
---
|
|
|
|
### `#[LiveAction]`
|
|
|
|
Marks a method as a LiveComponent action.
|
|
|
|
**Usage**:
|
|
```php
|
|
use App\Framework\LiveComponents\Attributes\LiveAction;
|
|
|
|
#[LiveAction]
|
|
public function search(): void
|
|
{
|
|
$this->results = $this->searchService->search($this->searchTerm);
|
|
}
|
|
|
|
#[LiveAction]
|
|
public function addToCart(string $productId): void
|
|
{
|
|
$this->cart->add($productId);
|
|
}
|
|
```
|
|
|
|
**Callable From Client**:
|
|
```html
|
|
<button data-lc-action="search">Search</button>
|
|
<button
|
|
data-lc-action="addToCart"
|
|
data-lc-params='{"productId": "123"}'
|
|
>
|
|
Add to Cart
|
|
</button>
|
|
```
|
|
|
|
---
|
|
|
|
### `#[Fragment(string|array $names)]`
|
|
|
|
Marks action to only update specific fragments.
|
|
|
|
**Usage**:
|
|
```php
|
|
use App\Framework\LiveComponents\Attributes\Fragment;
|
|
|
|
#[LiveAction]
|
|
#[Fragment('search-results')]
|
|
public function search(): void
|
|
{
|
|
$this->results = $this->searchService->search($this->searchTerm);
|
|
// Only search-results fragment re-renders
|
|
}
|
|
|
|
#[LiveAction]
|
|
#[Fragment(['user-stats', 'activity-feed'])]
|
|
public function refreshDashboard(): void
|
|
{
|
|
$this->updateStats();
|
|
$this->loadActivity();
|
|
// Both fragments re-render
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### `#[RateLimit(int $requests, int $window)]`
|
|
|
|
Apply rate limiting to component or action.
|
|
|
|
**Usage**:
|
|
```php
|
|
use App\Framework\LiveComponents\Attributes\RateLimit;
|
|
|
|
#[RateLimit(requests: 10, window: 60)]
|
|
final class SearchComponent extends LiveComponent
|
|
{
|
|
#[LiveAction]
|
|
#[RateLimit(requests: 5, window: 60)]
|
|
public function expensiveSearch(): void
|
|
{
|
|
// Limited to 5 requests per minute
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### `#[Authorize(array $roles = [], array $permissions = [])]`
|
|
|
|
Require authorization for action.
|
|
|
|
**Usage**:
|
|
```php
|
|
use App\Framework\LiveComponents\Attributes\Authorize;
|
|
|
|
#[LiveAction]
|
|
#[Authorize(roles: ['admin'])]
|
|
public function deleteUser(string $userId): void
|
|
{
|
|
$this->userService->delete($userId);
|
|
}
|
|
|
|
#[LiveAction]
|
|
#[Authorize(permissions: ['users.edit'])]
|
|
public function updateUserRole(string $userId, string $role): void
|
|
{
|
|
$this->userService->updateRole($userId, $role);
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### `#[Idempotent]`
|
|
|
|
Ensure action executes exactly once (for critical operations).
|
|
|
|
**Usage**:
|
|
```php
|
|
use App\Framework\LiveComponents\Attributes\Idempotent;
|
|
|
|
#[LiveAction]
|
|
#[Idempotent]
|
|
public function processPayment(): void
|
|
{
|
|
$this->paymentGateway->charge($this->amount);
|
|
$this->orderService->createOrder($this->cartItems);
|
|
// Guaranteed to execute only once even if request duplicated
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### `#[Encrypted]`
|
|
|
|
Encrypt property value in client state.
|
|
|
|
**Usage**:
|
|
```php
|
|
use App\Framework\LiveComponents\Attributes\Encrypted;
|
|
|
|
final class PaymentForm extends LiveComponent
|
|
{
|
|
#[LiveProp]
|
|
#[Encrypted]
|
|
public string $creditCardNumber = '';
|
|
|
|
#[LiveProp]
|
|
#[Encrypted]
|
|
public string $cvv = '';
|
|
|
|
#[LiveProp] // Not encrypted - safe to expose
|
|
public string $cardholderName = '';
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### `#[Optimistic(string $property, string $operation)]`
|
|
|
|
Enable optimistic UI updates for action.
|
|
|
|
**Usage**:
|
|
```php
|
|
use App\Framework\LiveComponents\Attributes\Optimistic;
|
|
|
|
#[LiveAction]
|
|
#[Optimistic(property: 'isLiked', operation: 'toggle')]
|
|
#[Optimistic(property: 'likeCount', operation: 'increment')]
|
|
public function toggleLike(): void
|
|
{
|
|
$this->isLiked = !$this->isLiked;
|
|
$this->likeCount += $this->isLiked ? 1 : -1;
|
|
}
|
|
```
|
|
|
|
**Operations**:
|
|
- `toggle` - Boolean toggle
|
|
- `increment` - Increment number
|
|
- `decrement` - Decrement number
|
|
- `set` - Set to specific value
|
|
|
|
---
|
|
|
|
## HTTP Integration
|
|
|
|
### LiveComponent HTTP Endpoint
|
|
|
|
**POST** `/livecomponent/{componentId}/{action}`
|
|
|
|
Execute a LiveComponent action.
|
|
|
|
**Request Headers**:
|
|
```http
|
|
Content-Type: application/json
|
|
X-CSRF-Token: {token}
|
|
X-Component-Checksum: {checksum}
|
|
```
|
|
|
|
**Request Body**:
|
|
```json
|
|
{
|
|
"state": {
|
|
"searchTerm": "laptop",
|
|
"filters": ["electronics"],
|
|
"page": 1
|
|
},
|
|
"params": {
|
|
"productId": "123"
|
|
}
|
|
}
|
|
```
|
|
|
|
**Response**:
|
|
```json
|
|
{
|
|
"success": true,
|
|
"html": "<div data-component-id=\"abc123\">...</div>",
|
|
"fragments": {
|
|
"search-results": "<div data-lc-fragment=\"search-results\">...</div>"
|
|
},
|
|
"redirect": null,
|
|
"events": []
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### Batch Endpoint
|
|
|
|
**POST** `/livecomponent/{componentId}/batch`
|
|
|
|
Execute multiple actions in single request.
|
|
|
|
**Request Body**:
|
|
```json
|
|
{
|
|
"batch": [
|
|
{
|
|
"action": "incrementCounter",
|
|
"params": {}
|
|
},
|
|
{
|
|
"action": "updateName",
|
|
"params": {"name": "John"}
|
|
},
|
|
{
|
|
"action": "save",
|
|
"params": {}
|
|
}
|
|
],
|
|
"state": {
|
|
"count": 0,
|
|
"name": ""
|
|
}
|
|
}
|
|
```
|
|
|
|
**Response**:
|
|
```json
|
|
{
|
|
"success": true,
|
|
"results": [
|
|
{ "success": true, "html": "..." },
|
|
{ "success": true, "html": "..." },
|
|
{ "success": true, "html": "..." }
|
|
]
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### Upload Endpoint
|
|
|
|
**POST** `/livecomponent/{componentId}/upload`
|
|
|
|
Upload file chunks.
|
|
|
|
**Request Headers**:
|
|
```http
|
|
Content-Type: multipart/form-data
|
|
X-Upload-ID: {unique-upload-id}
|
|
X-Chunk-Index: {chunk-number}
|
|
X-Total-Chunks: {total-chunks}
|
|
```
|
|
|
|
**Response**:
|
|
```json
|
|
{
|
|
"success": true,
|
|
"chunk_index": 0,
|
|
"received_bytes": 1048576,
|
|
"complete": false
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### SSE Endpoint
|
|
|
|
**GET** `/livecomponent/sse/{componentId}`
|
|
|
|
Subscribe to Server-Sent Events.
|
|
|
|
**Response** (text/event-stream):
|
|
```
|
|
event: component-update
|
|
data: {"type":"fragment","fragment":"user-stats","html":"<div>...</div>"}
|
|
|
|
event: component-update
|
|
data: {"type":"full","html":"<div data-component-id=\"abc123\">...</div>"}
|
|
|
|
event: heartbeat
|
|
data: {"timestamp":1634567890}
|
|
```
|
|
|
|
---
|
|
|
|
## JavaScript API
|
|
|
|
### `LiveComponent` Global Object
|
|
|
|
Main JavaScript interface for LiveComponents.
|
|
|
|
#### Methods
|
|
|
|
##### `LiveComponent.mount(element: HTMLElement): void`
|
|
|
|
Initialize component on element.
|
|
|
|
**Usage**:
|
|
```javascript
|
|
const componentEl = document.querySelector('[data-component-id="abc123"]');
|
|
LiveComponent.mount(componentEl);
|
|
```
|
|
|
|
---
|
|
|
|
##### `LiveComponent.unmount(componentId: string): void`
|
|
|
|
Destroy component and clean up resources.
|
|
|
|
**Usage**:
|
|
```javascript
|
|
LiveComponent.unmount('abc123');
|
|
```
|
|
|
|
---
|
|
|
|
##### `LiveComponent.executeAction(componentId: string, action: string, params: object): Promise<void>`
|
|
|
|
Manually execute component action.
|
|
|
|
**Usage**:
|
|
```javascript
|
|
await LiveComponent.executeAction('abc123', 'search', {
|
|
query: 'laptops'
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
##### `LiveComponent.getComponent(componentId: string): Component | null`
|
|
|
|
Get component instance.
|
|
|
|
**Returns**: Component object or null
|
|
|
|
**Usage**:
|
|
```javascript
|
|
const component = LiveComponent.getComponent('abc123');
|
|
|
|
console.log(component.state);
|
|
console.log(component.name);
|
|
```
|
|
|
|
---
|
|
|
|
##### `LiveComponent.getState(componentId: string): object | null`
|
|
|
|
Get component state.
|
|
|
|
**Returns**: State object or null
|
|
|
|
**Usage**:
|
|
```javascript
|
|
const state = LiveComponent.getState('abc123');
|
|
|
|
console.log(state.searchTerm);
|
|
console.log(state.filters);
|
|
```
|
|
|
|
---
|
|
|
|
##### `LiveComponent.setState(componentId: string, state: object): void`
|
|
|
|
Update component state (triggers server sync).
|
|
|
|
**Usage**:
|
|
```javascript
|
|
LiveComponent.setState('abc123', {
|
|
searchTerm: 'new query',
|
|
page: 2
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
##### `LiveComponent.refresh(componentId: string): Promise<void>`
|
|
|
|
Force component refresh from server.
|
|
|
|
**Usage**:
|
|
```javascript
|
|
await LiveComponent.refresh('abc123');
|
|
```
|
|
|
|
---
|
|
|
|
##### `LiveComponent.configure(options: object): void`
|
|
|
|
Configure global LiveComponent settings.
|
|
|
|
**Options**:
|
|
- `batchSize` - Max actions per batch (default: 10)
|
|
- `batchDebounce` - Debounce delay in ms (default: 50)
|
|
- `optimisticUI` - Enable optimistic updates (default: true)
|
|
- `csrfProtection` - Enable CSRF protection (default: true)
|
|
- `enableSSE` - Enable Server-Sent Events (default: true)
|
|
|
|
**Usage**:
|
|
```javascript
|
|
LiveComponent.configure({
|
|
batchSize: 15,
|
|
batchDebounce: 100,
|
|
optimisticUI: true
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
#### Properties
|
|
|
|
##### `LiveComponent.components: Map<string, Component>`
|
|
|
|
All active components.
|
|
|
|
**Usage**:
|
|
```javascript
|
|
console.log(`Active components: ${LiveComponent.components.size}`);
|
|
|
|
LiveComponent.components.forEach((component, id) => {
|
|
console.log(`${id}: ${component.name}`);
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
### Component Object
|
|
|
|
Individual component instance (returned by `getComponent()`).
|
|
|
|
#### Properties
|
|
|
|
```typescript
|
|
interface Component {
|
|
id: string; // Unique component ID
|
|
name: string; // Component class name
|
|
state: object; // Current state
|
|
element: HTMLElement; // DOM element
|
|
isConnected: boolean; // SSE connection status
|
|
isPending: boolean; // Has pending actions
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### Data Attributes
|
|
|
|
> **Note**: All data attributes are centrally managed through PHP Enums and JavaScript Constants. See [Data Attributes Reference](../../framework/data-attributes-reference.md) for complete documentation.
|
|
|
|
#### `data-component-id`
|
|
|
|
Identifies component root element. **Required** on component container.
|
|
|
|
**PHP Enum**: `LiveComponentCoreAttribute::COMPONENT_ID`
|
|
**JavaScript Constant**: `LiveComponentCoreAttributes.COMPONENT_ID`
|
|
|
|
```html
|
|
<div data-component-id="{component_id}" data-component-name="SearchComponent">
|
|
<!-- Component content -->
|
|
</div>
|
|
```
|
|
|
|
---
|
|
|
|
#### `data-lc-model`
|
|
|
|
Two-way data binding for inputs.
|
|
|
|
**PHP Enum**: `LiveComponentFeatureAttribute::LC_MODEL`
|
|
**JavaScript Constant**: `LiveComponentFeatureAttributes.LC_MODEL`
|
|
|
|
```html
|
|
<input
|
|
type="text"
|
|
data-lc-model="searchTerm"
|
|
value="{searchTerm}"
|
|
/>
|
|
|
|
<select data-lc-model="category">
|
|
<option value="electronics">Electronics</option>
|
|
<option value="books">Books</option>
|
|
</select>
|
|
|
|
<input
|
|
type="checkbox"
|
|
data-lc-model="agreed"
|
|
{agreed ? 'checked' : ''}
|
|
/>
|
|
```
|
|
|
|
**Modifiers**:
|
|
- `data-lc-model.debounce.500="searchTerm"` - Debounce 500ms
|
|
- `data-lc-model.lazy="bio"` - Update on blur instead of input
|
|
|
|
---
|
|
|
|
#### `data-lc-action`
|
|
|
|
Trigger action on click.
|
|
|
|
**PHP Enum**: `LiveComponentCoreAttribute::LIVE_ACTION`
|
|
**JavaScript Constant**: `LiveComponentCoreAttributes.LIVE_ACTION`
|
|
|
|
```html
|
|
<button data-lc-action="search">Search</button>
|
|
|
|
<button
|
|
data-lc-action="addToCart"
|
|
data-lc-params='{"productId": "123"}'
|
|
>
|
|
Add to Cart
|
|
</button>
|
|
```
|
|
|
|
---
|
|
|
|
#### `data-lc-fragment`
|
|
|
|
Mark fragment for partial updates.
|
|
|
|
```html
|
|
<div data-lc-fragment="search-results">
|
|
<!-- Only this section updates -->
|
|
<for items="results" as="result">
|
|
<div>{result.title}</div>
|
|
</for>
|
|
</div>
|
|
```
|
|
|
|
---
|
|
|
|
#### `data-lc-submit`
|
|
|
|
Handle form submission.
|
|
|
|
```html
|
|
<form data-lc-submit="handleSubmit">
|
|
<input type="text" name="email" />
|
|
<button type="submit">Submit</button>
|
|
</form>
|
|
```
|
|
|
|
---
|
|
|
|
#### `data-optimistic`
|
|
|
|
Enable optimistic update for action.
|
|
|
|
```html
|
|
<button
|
|
data-lc-action="toggleLike"
|
|
data-optimistic="true"
|
|
>
|
|
{isLiked ? '❤️' : '🤍'} Like
|
|
</button>
|
|
```
|
|
|
|
---
|
|
|
|
#### `data-lc-upload`
|
|
|
|
Enable chunked file upload.
|
|
|
|
```html
|
|
<input
|
|
type="file"
|
|
data-lc-upload="handleFileUpload"
|
|
data-chunk-size="1048576"
|
|
data-max-file-size="10485760"
|
|
/>
|
|
|
|
<div data-upload-progress>
|
|
<div class="progress-bar" style="width: {uploadProgress}%"></div>
|
|
<span>{uploadProgress}%</span>
|
|
</div>
|
|
```
|
|
|
|
---
|
|
|
|
## Value Objects
|
|
|
|
### `ComponentId`
|
|
|
|
Unique component identifier.
|
|
|
|
```php
|
|
final readonly class ComponentId
|
|
{
|
|
public function __construct(public string $value) {}
|
|
|
|
public static function generate(): self
|
|
{
|
|
return new self(bin2hex(random_bytes(16)));
|
|
}
|
|
|
|
public function toString(): string
|
|
{
|
|
return $this->value;
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### `ComponentState`
|
|
|
|
Component state snapshot.
|
|
|
|
```php
|
|
final readonly class ComponentState
|
|
{
|
|
public function __construct(
|
|
public array $properties,
|
|
public string $checksum
|
|
) {}
|
|
|
|
public function toArray(): array
|
|
{
|
|
return $this->properties;
|
|
}
|
|
|
|
public function verify(string $checksum): bool
|
|
{
|
|
return hash_equals($this->checksum, $checksum);
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### `FragmentName`
|
|
|
|
Fragment identifier.
|
|
|
|
```php
|
|
final readonly class FragmentName
|
|
{
|
|
public function __construct(public string $value) {}
|
|
|
|
public static function fromString(string $name): self
|
|
{
|
|
return new self($name);
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Services
|
|
|
|
### `LiveComponentManager`
|
|
|
|
Core service for component lifecycle management.
|
|
|
|
```php
|
|
final readonly class LiveComponentManager
|
|
{
|
|
public function create(
|
|
string $componentClass,
|
|
array $initialData = []
|
|
): LiveComponent;
|
|
|
|
public function hydrate(
|
|
string $componentClass,
|
|
ComponentId $id,
|
|
ComponentState $state
|
|
): LiveComponent;
|
|
|
|
public function executeAction(
|
|
LiveComponent $component,
|
|
string $actionName,
|
|
array $params = []
|
|
): ActionResult;
|
|
|
|
public function render(LiveComponent $component): string;
|
|
|
|
public function renderFragment(
|
|
LiveComponent $component,
|
|
FragmentName $fragment
|
|
): string;
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### `LiveComponentState`
|
|
|
|
State management service.
|
|
|
|
```php
|
|
final readonly class LiveComponentState
|
|
{
|
|
public function serialize(LiveComponent $component): ComponentState;
|
|
|
|
public function deserialize(
|
|
string $componentClass,
|
|
array $state
|
|
): LiveComponent;
|
|
|
|
public function generateChecksum(array $state): string;
|
|
|
|
public function verifyChecksum(array $state, string $checksum): bool;
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Events
|
|
|
|
### Client-Side Events
|
|
|
|
#### `livecomponent:mount`
|
|
|
|
Fired when component is mounted.
|
|
|
|
```javascript
|
|
window.addEventListener('livecomponent:mount', (e) => {
|
|
console.log('Component mounted:', e.detail);
|
|
// { componentId, name, element }
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
#### `livecomponent:action-start`
|
|
|
|
Fired when action starts.
|
|
|
|
```javascript
|
|
window.addEventListener('livecomponent:action-start', (e) => {
|
|
console.log('Action starting:', e.detail);
|
|
// { componentId, action, params, optimistic }
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
#### `livecomponent:action-executed`
|
|
|
|
Fired when action completes.
|
|
|
|
```javascript
|
|
window.addEventListener('livecomponent:action-executed', (e) => {
|
|
console.log('Action completed:', e.detail);
|
|
// { componentId, action, duration, success }
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
#### `livecomponent:action-error`
|
|
|
|
Fired when action fails.
|
|
|
|
```javascript
|
|
window.addEventListener('livecomponent:action-error', (e) => {
|
|
console.error('Action failed:', e.detail);
|
|
// { componentId, action, error, response }
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
#### `livecomponent:fragment-updated`
|
|
|
|
Fired when fragment updates.
|
|
|
|
```javascript
|
|
window.addEventListener('livecomponent:fragment-updated', (e) => {
|
|
console.log('Fragment updated:', e.detail);
|
|
// { componentId, fragmentName, duration, nodesChanged }
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
#### `livecomponent:batch-sent`
|
|
|
|
Fired when batch request is sent.
|
|
|
|
```javascript
|
|
window.addEventListener('livecomponent:batch-sent', (e) => {
|
|
console.log('Batch sent:', e.detail);
|
|
// { componentId, actionsCount, payloadSize }
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
#### `livecomponent:optimistic-applied`
|
|
|
|
Fired when optimistic update is applied.
|
|
|
|
```javascript
|
|
window.addEventListener('livecomponent:optimistic-applied', (e) => {
|
|
console.log('Optimistic update:', e.detail);
|
|
// { componentId, action, stateBeforeOptimistic }
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
#### `livecomponent:optimistic-rollback`
|
|
|
|
Fired when optimistic update is rolled back.
|
|
|
|
```javascript
|
|
window.addEventListener('livecomponent:optimistic-rollback', (e) => {
|
|
console.warn('Optimistic rollback:', e.detail);
|
|
// { componentId, action, error, stateBeforeOptimistic }
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
#### `livecomponent:sse-connected`
|
|
|
|
Fired when SSE connection established.
|
|
|
|
```javascript
|
|
window.addEventListener('livecomponent:sse-connected', (e) => {
|
|
console.log('SSE connected:', e.detail);
|
|
// { componentId, channels }
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
#### `livecomponent:sse-disconnected`
|
|
|
|
Fired when SSE connection lost.
|
|
|
|
```javascript
|
|
window.addEventListener('livecomponent:sse-disconnected', (e) => {
|
|
console.warn('SSE disconnected:', e.detail);
|
|
// { componentId, reason }
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
#### `livecomponent:error`
|
|
|
|
Fired on any LiveComponent error.
|
|
|
|
```javascript
|
|
window.addEventListener('livecomponent:error', (e) => {
|
|
console.error('LiveComponent error:', e.detail);
|
|
// { componentId, type, message, code }
|
|
|
|
if (e.detail.code === 'CSRF_TOKEN_EXPIRED') {
|
|
LiveComponent.refreshCsrfToken();
|
|
}
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
### Server-Side Events
|
|
|
|
Events dispatched during server-side processing (integrate with framework Event System).
|
|
|
|
#### `ComponentMountedEvent`
|
|
|
|
```php
|
|
final readonly class ComponentMountedEvent
|
|
{
|
|
public function __construct(
|
|
public string $componentClass,
|
|
public ComponentId $componentId,
|
|
public array $initialData
|
|
) {}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
#### `ComponentActionExecutedEvent`
|
|
|
|
```php
|
|
final readonly class ComponentActionExecutedEvent
|
|
{
|
|
public function __construct(
|
|
public string $componentClass,
|
|
public ComponentId $componentId,
|
|
public string $actionName,
|
|
public array $params,
|
|
public float $duration,
|
|
public bool $success
|
|
) {}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
#### `ComponentUnmountedEvent`
|
|
|
|
```php
|
|
final readonly class ComponentUnmountedEvent
|
|
{
|
|
public function __construct(
|
|
public string $componentClass,
|
|
public ComponentId $componentId
|
|
) {}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Error Handling
|
|
|
|
### Common Errors
|
|
|
|
#### `ComponentNotFoundException`
|
|
|
|
Component class not found.
|
|
|
|
```php
|
|
throw ComponentNotFoundException::forClass('App\\Components\\NonExistent');
|
|
```
|
|
|
|
---
|
|
|
|
#### `ActionNotFoundException`
|
|
|
|
Action method not found on component.
|
|
|
|
```php
|
|
throw ActionNotFoundException::forAction('nonExistentAction', $componentClass);
|
|
```
|
|
|
|
---
|
|
|
|
#### `InvalidStateException`
|
|
|
|
Component state invalid or corrupted.
|
|
|
|
```php
|
|
throw InvalidStateException::checksumMismatch($expectedChecksum, $actualChecksum);
|
|
```
|
|
|
|
---
|
|
|
|
#### `RateLimitExceededException`
|
|
|
|
Rate limit exceeded for component/action.
|
|
|
|
```php
|
|
throw RateLimitExceededException::forAction($actionName, $retryAfter);
|
|
```
|
|
|
|
---
|
|
|
|
#### `UnauthorizedException`
|
|
|
|
User not authorized for action.
|
|
|
|
```php
|
|
throw UnauthorizedException::missingRole($requiredRole);
|
|
throw UnauthorizedException::missingPermission($requiredPermission);
|
|
```
|
|
|
|
---
|
|
|
|
## Type Definitions
|
|
|
|
### TypeScript Definitions
|
|
|
|
For TypeScript projects:
|
|
|
|
```typescript
|
|
// livecomponents.d.ts
|
|
declare global {
|
|
interface Window {
|
|
LiveComponent: {
|
|
mount(element: HTMLElement): void;
|
|
unmount(componentId: string): void;
|
|
executeAction(
|
|
componentId: string,
|
|
action: string,
|
|
params?: object
|
|
): Promise<void>;
|
|
getComponent(componentId: string): Component | null;
|
|
getState(componentId: string): object | null;
|
|
setState(componentId: string, state: object): void;
|
|
refresh(componentId: string): Promise<void>;
|
|
configure(options: LiveComponentConfig): void;
|
|
components: Map<string, Component>;
|
|
};
|
|
}
|
|
|
|
interface Component {
|
|
id: string;
|
|
name: string;
|
|
state: object;
|
|
element: HTMLElement;
|
|
isConnected: boolean;
|
|
isPending: boolean;
|
|
}
|
|
|
|
interface LiveComponentConfig {
|
|
batchSize?: number;
|
|
batchDebounce?: number;
|
|
optimisticUI?: boolean;
|
|
csrfProtection?: boolean;
|
|
enableSSE?: boolean;
|
|
}
|
|
}
|
|
|
|
export {};
|
|
```
|
|
|
|
---
|
|
|
|
**Next**: [Advanced Features](advanced-features.md) →
|