- Add comprehensive health check system with multiple endpoints - Add Prometheus metrics endpoint - Add production logging configurations (5 strategies) - Add complete deployment documentation suite: * QUICKSTART.md - 30-minute deployment guide * DEPLOYMENT_CHECKLIST.md - Printable verification checklist * DEPLOYMENT_WORKFLOW.md - Complete deployment lifecycle * PRODUCTION_DEPLOYMENT.md - Comprehensive technical reference * production-logging.md - Logging configuration guide * ANSIBLE_DEPLOYMENT.md - Infrastructure as Code automation * README.md - Navigation hub * DEPLOYMENT_SUMMARY.md - Executive summary - Add deployment scripts and automation - Add DEPLOYMENT_PLAN.md - Concrete plan for immediate deployment - Update README with production-ready features All production infrastructure is now complete and ready for deployment.
1381 lines
34 KiB
Markdown
1381 lines
34 KiB
Markdown
# LiveComponents Attributes Reference
|
|
|
|
Complete reference for all LiveComponents attributes with examples and best practices.
|
|
|
|
## Table of Contents
|
|
|
|
- [Component Attributes](#component-attributes)
|
|
- [@LiveProp](#liveprop)
|
|
- [@LiveAction](#liveaction)
|
|
- [@Fragment](#fragment)
|
|
- [Security Attributes](#security-attributes)
|
|
- [@RateLimit](#ratelimit)
|
|
- [@Authorize](#authorize)
|
|
- [@Idempotent](#idempotent)
|
|
- [@Encrypted](#encrypted)
|
|
- [Performance Attributes](#performance-attributes)
|
|
- [@Optimistic](#optimistic)
|
|
- [@Cached](#cached)
|
|
- [@NoBatch](#nobatch)
|
|
- [Validation Attributes](#validation-attributes)
|
|
- [@Validated](#validated)
|
|
- [State Management Attributes](#state-management-attributes)
|
|
- [@Persisted](#persisted)
|
|
|
|
---
|
|
|
|
## Component Attributes
|
|
|
|
### @LiveProp
|
|
|
|
**Purpose**: Mark a component property as reactive - automatically synchronized between server and client.
|
|
|
|
**Signature**:
|
|
```php
|
|
#[LiveProp(
|
|
writable: bool = false, // Allow client-side updates
|
|
lazy: bool = false, // Load on first access
|
|
computed: bool = false, // Computed from other props
|
|
dehydrate: ?callable = null, // Custom serialization
|
|
hydrate: ?callable = null // Custom deserialization
|
|
)]
|
|
```
|
|
|
|
**Basic Usage**:
|
|
```php
|
|
final class Counter extends LiveComponent
|
|
{
|
|
#[LiveProp]
|
|
public int $count = 0;
|
|
|
|
#[LiveProp(writable: true)]
|
|
public string $searchQuery = '';
|
|
|
|
#[LiveProp(lazy: true)]
|
|
public array $expensiveData = [];
|
|
|
|
#[LiveAction]
|
|
public function increment(): void
|
|
{
|
|
$this->count++;
|
|
}
|
|
}
|
|
```
|
|
|
|
**Writable Props** (Two-Way Binding):
|
|
```php
|
|
final class SearchForm extends LiveComponent
|
|
{
|
|
#[LiveProp(writable: true)]
|
|
public string $query = '';
|
|
|
|
#[LiveProp(writable: true)]
|
|
public array $filters = [];
|
|
|
|
#[LiveAction]
|
|
public function search(): void
|
|
{
|
|
// $this->query automatically updated from client
|
|
$this->results = $this->searchService->search($this->query, $this->filters);
|
|
}
|
|
}
|
|
```
|
|
|
|
**HTML Template**:
|
|
```html
|
|
<div data-lc-component="SearchForm">
|
|
<!-- Automatically synced to server on change -->
|
|
<input
|
|
type="text"
|
|
data-lc-model="query"
|
|
placeholder="Search..."
|
|
/>
|
|
|
|
<!-- Current value: {{ query }} -->
|
|
</div>
|
|
```
|
|
|
|
**Lazy Props** (Load on Demand):
|
|
```php
|
|
final class Dashboard extends LiveComponent
|
|
{
|
|
#[LiveProp]
|
|
public string $userId;
|
|
|
|
#[LiveProp(lazy: true)]
|
|
public array $analytics = [];
|
|
|
|
#[LiveAction]
|
|
public function loadAnalytics(): void
|
|
{
|
|
// Only load when explicitly requested
|
|
$this->analytics = $this->analyticsService->getForUser($this->userId);
|
|
}
|
|
}
|
|
```
|
|
|
|
**Computed Props**:
|
|
```php
|
|
final class ShoppingCart extends LiveComponent
|
|
{
|
|
#[LiveProp]
|
|
public array $items = [];
|
|
|
|
#[LiveProp(computed: true)]
|
|
public function total(): Money
|
|
{
|
|
return array_reduce(
|
|
$this->items,
|
|
fn($total, $item) => $total->add($item->price),
|
|
Money::zero()
|
|
);
|
|
}
|
|
}
|
|
```
|
|
|
|
**Custom Serialization**:
|
|
```php
|
|
final class DateRangePicker extends LiveComponent
|
|
{
|
|
#[LiveProp(
|
|
dehydrate: [self::class, 'dehydrateDate'],
|
|
hydrate: [self::class, 'hydrateDate']
|
|
)]
|
|
public \DateTimeImmutable $startDate;
|
|
|
|
public static function dehydrateDate(\DateTimeImmutable $date): string
|
|
{
|
|
return $date->format('Y-m-d');
|
|
}
|
|
|
|
public static function hydrateDate(string $date): \DateTimeImmutable
|
|
{
|
|
return new \DateTimeImmutable($date);
|
|
}
|
|
}
|
|
```
|
|
|
|
**Best Practices**:
|
|
- ✅ Use `writable: true` only for user input fields
|
|
- ✅ Use `lazy: true` for expensive data that's not always needed
|
|
- ✅ Keep props serializable (primitives, arrays, or custom dehydrate/hydrate)
|
|
- ❌ Don't make service dependencies `#[LiveProp]`
|
|
- ❌ Don't expose sensitive data as LiveProps
|
|
|
|
**Gotchas**:
|
|
- Writable props validate on server before updating
|
|
- Computed props are read-only on client
|
|
- Lazy props won't be available until explicitly loaded
|
|
|
|
---
|
|
|
|
### @LiveAction
|
|
|
|
**Purpose**: Mark a method as a server-side action that can be triggered from the client.
|
|
|
|
**Signature**:
|
|
```php
|
|
#[LiveAction(
|
|
name: ?string = null, // Custom action name
|
|
debounce: ?int = null, // Debounce in milliseconds
|
|
throttle: ?int = null, // Throttle in milliseconds
|
|
confirm: ?string = null // Confirmation message
|
|
)]
|
|
```
|
|
|
|
**Basic Usage**:
|
|
```php
|
|
final class TodoList extends LiveComponent
|
|
{
|
|
#[LiveProp]
|
|
public array $todos = [];
|
|
|
|
#[LiveAction]
|
|
public function addTodo(string $text): void
|
|
{
|
|
$this->todos[] = [
|
|
'id' => uniqid(),
|
|
'text' => $text,
|
|
'done' => false
|
|
];
|
|
}
|
|
|
|
#[LiveAction]
|
|
public function toggleTodo(string $id): void
|
|
{
|
|
foreach ($this->todos as &$todo) {
|
|
if ($todo['id'] === $id) {
|
|
$todo['done'] = !$todo['done'];
|
|
}
|
|
}
|
|
}
|
|
|
|
#[LiveAction]
|
|
public function removeTodo(string $id): void
|
|
{
|
|
$this->todos = array_filter(
|
|
$this->todos,
|
|
fn($todo) => $todo['id'] !== $id
|
|
);
|
|
}
|
|
}
|
|
```
|
|
|
|
**HTML Template**:
|
|
```html
|
|
<div data-lc-component="TodoList">
|
|
<ul>
|
|
<for items="todos" as="todo">
|
|
<li>
|
|
<input
|
|
type="checkbox"
|
|
data-lc-action="toggleTodo"
|
|
data-lc-params='{"id": "{{ todo.id }}"}'
|
|
{checked:todo.done}
|
|
/>
|
|
{{ todo.text }}
|
|
<button
|
|
data-lc-action="removeTodo"
|
|
data-lc-params='{"id": "{{ todo.id }}"}'
|
|
>
|
|
Delete
|
|
</button>
|
|
</li>
|
|
</for>
|
|
</ul>
|
|
</div>
|
|
```
|
|
|
|
**Debounced Actions** (Search Input):
|
|
```php
|
|
final class SearchComponent extends LiveComponent
|
|
{
|
|
#[LiveProp(writable: true)]
|
|
public string $query = '';
|
|
|
|
#[LiveProp]
|
|
public array $results = [];
|
|
|
|
#[LiveAction(debounce: 300)]
|
|
public function search(): void
|
|
{
|
|
// Only executes 300ms after user stops typing
|
|
$this->results = $this->searchService->search($this->query);
|
|
}
|
|
}
|
|
```
|
|
|
|
**Throttled Actions** (Rate Limited):
|
|
```php
|
|
final class AnalyticsTracker extends LiveComponent
|
|
{
|
|
#[LiveAction(throttle: 1000)]
|
|
public function trackEvent(string $eventName): void
|
|
{
|
|
// Maximum once per second
|
|
$this->analyticsService->track($eventName);
|
|
}
|
|
}
|
|
```
|
|
|
|
**Confirmation Actions**:
|
|
```php
|
|
final class UserManager extends LiveComponent
|
|
{
|
|
#[LiveAction(confirm: 'Are you sure you want to delete this user?')]
|
|
public function deleteUser(string $userId): void
|
|
{
|
|
$this->userService->delete($userId);
|
|
}
|
|
}
|
|
```
|
|
|
|
**Custom Action Names**:
|
|
```php
|
|
final class PaymentForm extends LiveComponent
|
|
{
|
|
#[LiveAction(name: 'submit-payment')]
|
|
public function processPayment(array $paymentData): void
|
|
{
|
|
$this->paymentService->process($paymentData);
|
|
}
|
|
}
|
|
```
|
|
|
|
**Best Practices**:
|
|
- ✅ Use debounce for search/autocomplete
|
|
- ✅ Use throttle for tracking/analytics
|
|
- ✅ Use confirm for destructive actions
|
|
- ✅ Keep actions focused (single responsibility)
|
|
- ❌ Don't perform long-running operations (use jobs)
|
|
- ❌ Don't return data (update LiveProps instead)
|
|
|
|
**Gotchas**:
|
|
- Actions automatically re-render component by default
|
|
- Debounce/throttle applied client-side before request
|
|
- Confirmation shows native browser confirm dialog
|
|
|
|
---
|
|
|
|
### @Fragment
|
|
|
|
**Purpose**: Mark an action to update only specific fragments of the component (partial rendering).
|
|
|
|
**Signature**:
|
|
```php
|
|
#[Fragment(
|
|
string|array $names // Fragment name(s) to update
|
|
)]
|
|
```
|
|
|
|
**Basic Usage**:
|
|
```php
|
|
final class Dashboard extends LiveComponent
|
|
{
|
|
#[LiveProp]
|
|
public array $stats = [];
|
|
|
|
#[LiveProp]
|
|
public array $notifications = [];
|
|
|
|
#[LiveAction]
|
|
#[Fragment('stats')]
|
|
public function refreshStats(): void
|
|
{
|
|
// Only re-renders the 'stats' fragment
|
|
$this->stats = $this->statsService->getLatest();
|
|
}
|
|
|
|
#[LiveAction]
|
|
#[Fragment('notifications')]
|
|
public function refreshNotifications(): void
|
|
{
|
|
// Only re-renders the 'notifications' fragment
|
|
$this->notifications = $this->notificationService->getUnread();
|
|
}
|
|
|
|
#[LiveAction]
|
|
#[Fragment(['stats', 'notifications'])]
|
|
public function refreshAll(): void
|
|
{
|
|
// Re-renders both fragments
|
|
$this->stats = $this->statsService->getLatest();
|
|
$this->notifications = $this->notificationService->getUnread();
|
|
}
|
|
}
|
|
```
|
|
|
|
**HTML Template**:
|
|
```html
|
|
<div data-lc-component="Dashboard">
|
|
<div data-lc-fragment="stats">
|
|
<h2>Statistics</h2>
|
|
<for items="stats" as="stat">
|
|
<div>{{ stat.label }}: {{ stat.value }}</div>
|
|
</for>
|
|
<button data-lc-action="refreshStats">Refresh Stats</button>
|
|
</div>
|
|
|
|
<div data-lc-fragment="notifications">
|
|
<h2>Notifications</h2>
|
|
<for items="notifications" as="notification">
|
|
<div>{{ notification.message }}</div>
|
|
</for>
|
|
<button data-lc-action="refreshNotifications">Refresh Notifications</button>
|
|
</div>
|
|
|
|
<button data-lc-action="refreshAll">Refresh Everything</button>
|
|
</div>
|
|
```
|
|
|
|
**Nested Fragments**:
|
|
```php
|
|
final class ProductList extends LiveComponent
|
|
{
|
|
#[LiveProp]
|
|
public array $products = [];
|
|
|
|
#[LiveProp]
|
|
public array $filters = [];
|
|
|
|
#[LiveAction]
|
|
#[Fragment('product-list')]
|
|
public function applyFilter(string $category): void
|
|
{
|
|
$this->filters['category'] = $category;
|
|
$this->products = $this->productService->search($this->filters);
|
|
}
|
|
|
|
#[LiveAction]
|
|
#[Fragment('product-item')]
|
|
public function toggleFavorite(string $productId): void
|
|
{
|
|
$this->productService->toggleFavorite($productId);
|
|
}
|
|
}
|
|
```
|
|
|
|
**HTML Template with Nested Fragments**:
|
|
```html
|
|
<div data-lc-component="ProductList">
|
|
<div data-lc-fragment="product-list">
|
|
<for items="products" as="product">
|
|
<div data-lc-fragment="product-item" data-product-id="{{ product.id }}">
|
|
<h3>{{ product.name }}</h3>
|
|
<button
|
|
data-lc-action="toggleFavorite"
|
|
data-lc-params='{"productId": "{{ product.id }}"}'
|
|
>
|
|
{favorite:product.isFavorite}
|
|
</button>
|
|
</div>
|
|
</for>
|
|
</div>
|
|
</div>
|
|
```
|
|
|
|
**Performance Benefits**:
|
|
```php
|
|
// Without fragments (full re-render)
|
|
#[LiveAction]
|
|
public function updateSmallPart(): void
|
|
{
|
|
// Re-renders entire 10KB template
|
|
}
|
|
|
|
// With fragments (partial re-render)
|
|
#[LiveAction]
|
|
#[Fragment('small-part')]
|
|
public function updateSmallPart(): void
|
|
{
|
|
// Only re-renders 500 bytes
|
|
// 95% network reduction!
|
|
}
|
|
```
|
|
|
|
**Best Practices**:
|
|
- ✅ Use fragments for large components with independent sections
|
|
- ✅ Name fragments semantically (`user-profile`, not `fragment-1`)
|
|
- ✅ Keep fragments independent (minimal cross-dependencies)
|
|
- ✅ Use nested fragments for list items
|
|
- ❌ Don't create too many small fragments (overhead)
|
|
- ❌ Don't use fragments for tiny components (< 1KB)
|
|
|
|
**Gotchas**:
|
|
- Fragment names must match exactly between PHP and HTML
|
|
- Nested fragments require unique identifiers (e.g., `data-product-id`)
|
|
- Missing fragment in template causes full re-render
|
|
|
|
---
|
|
|
|
## Security Attributes
|
|
|
|
### @RateLimit
|
|
|
|
**Purpose**: Limit the rate at which an action can be executed to prevent abuse.
|
|
|
|
**Signature**:
|
|
```php
|
|
#[RateLimit(
|
|
int $requests, // Number of allowed requests
|
|
int $window, // Time window in seconds
|
|
string $key = 'ip' // Rate limit key ('ip', 'user', 'component')
|
|
)]
|
|
```
|
|
|
|
**IP-Based Rate Limiting**:
|
|
```php
|
|
final class ContactForm extends LiveComponent
|
|
{
|
|
#[LiveAction]
|
|
#[RateLimit(requests: 3, window: 3600, key: 'ip')]
|
|
public function submitForm(array $formData): void
|
|
{
|
|
// Max 3 submissions per hour per IP address
|
|
$this->emailService->send($formData);
|
|
}
|
|
}
|
|
```
|
|
|
|
**User-Based Rate Limiting**:
|
|
```php
|
|
final class ApiExplorer extends LiveComponent
|
|
{
|
|
#[LiveAction]
|
|
#[RateLimit(requests: 100, window: 60, key: 'user')]
|
|
public function executeQuery(string $query): void
|
|
{
|
|
// Max 100 queries per minute per authenticated user
|
|
$this->results = $this->apiService->query($query);
|
|
}
|
|
}
|
|
```
|
|
|
|
**Component-Based Rate Limiting**:
|
|
```php
|
|
final class ExpensiveReport extends LiveComponent
|
|
{
|
|
#[LiveAction]
|
|
#[RateLimit(requests: 5, window: 300, key: 'component')]
|
|
public function generateReport(): void
|
|
{
|
|
// Max 5 reports per 5 minutes for this component instance
|
|
$this->report = $this->reportService->generate();
|
|
}
|
|
}
|
|
```
|
|
|
|
**Multiple Rate Limits**:
|
|
```php
|
|
final class PaymentForm extends LiveComponent
|
|
{
|
|
#[LiveAction]
|
|
#[RateLimit(requests: 3, window: 60, key: 'ip')]
|
|
#[RateLimit(requests: 10, window: 3600, key: 'user')]
|
|
public function processPayment(array $paymentData): void
|
|
{
|
|
// IP: Max 3 per minute
|
|
// User: Max 10 per hour
|
|
$this->paymentService->process($paymentData);
|
|
}
|
|
}
|
|
```
|
|
|
|
**Best Practices**:
|
|
- ✅ Use `key: 'ip'` for public endpoints (contact forms, registrations)
|
|
- ✅ Use `key: 'user'` for authenticated endpoints
|
|
- ✅ Use `key: 'component'` for expensive operations
|
|
- ✅ Set generous limits to avoid false positives
|
|
- ❌ Don't rate limit critical user flows too aggressively
|
|
|
|
**Gotchas**:
|
|
- Rate limit exceeded returns 429 Too Many Requests
|
|
- Client automatically shows retry-after countdown
|
|
- Rate limits are per-action (not per-component)
|
|
|
|
---
|
|
|
|
### @Authorize
|
|
|
|
**Purpose**: Restrict action execution to users with specific roles or permissions.
|
|
|
|
**Signature**:
|
|
```php
|
|
#[Authorize(
|
|
array $roles = [], // Required roles (any match)
|
|
array $permissions = [], // Required permissions (all match)
|
|
bool $requireAll = false // Require all roles (default: any)
|
|
)]
|
|
```
|
|
|
|
**Role-Based Authorization**:
|
|
```php
|
|
final class UserManagement extends LiveComponent
|
|
{
|
|
#[LiveAction]
|
|
#[Authorize(roles: ['admin'])]
|
|
public function deleteUser(string $userId): void
|
|
{
|
|
// Only admins can delete users
|
|
$this->userService->delete($userId);
|
|
}
|
|
|
|
#[LiveAction]
|
|
#[Authorize(roles: ['admin', 'moderator'])]
|
|
public function banUser(string $userId): void
|
|
{
|
|
// Admins OR moderators can ban
|
|
$this->userService->ban($userId);
|
|
}
|
|
}
|
|
```
|
|
|
|
**Permission-Based Authorization**:
|
|
```php
|
|
final class DocumentEditor extends LiveComponent
|
|
{
|
|
#[LiveAction]
|
|
#[Authorize(permissions: ['documents.edit'])]
|
|
public function saveDocument(string $content): void
|
|
{
|
|
$this->documentService->save($content);
|
|
}
|
|
|
|
#[LiveAction]
|
|
#[Authorize(permissions: ['documents.delete'])]
|
|
public function deleteDocument(string $documentId): void
|
|
{
|
|
$this->documentService->delete($documentId);
|
|
}
|
|
}
|
|
```
|
|
|
|
**Combined Authorization**:
|
|
```php
|
|
final class BillingPanel extends LiveComponent
|
|
{
|
|
#[LiveAction]
|
|
#[Authorize(
|
|
roles: ['admin', 'billing'],
|
|
permissions: ['billing.process-refund'],
|
|
requireAll: true
|
|
)]
|
|
public function processRefund(string $orderId): void
|
|
{
|
|
// Must have (admin OR billing role) AND billing.process-refund permission
|
|
$this->billingService->refund($orderId);
|
|
}
|
|
}
|
|
```
|
|
|
|
**Dynamic Authorization**:
|
|
```php
|
|
final class ProjectEditor extends LiveComponent
|
|
{
|
|
#[LiveProp]
|
|
public string $projectId;
|
|
|
|
#[LiveAction]
|
|
public function saveProject(array $data): void
|
|
{
|
|
// Custom authorization logic
|
|
if (!$this->authService->canEditProject($this->projectId)) {
|
|
throw new UnauthorizedException('You cannot edit this project');
|
|
}
|
|
|
|
$this->projectService->save($this->projectId, $data);
|
|
}
|
|
}
|
|
```
|
|
|
|
**Best Practices**:
|
|
- ✅ Use roles for broad access control
|
|
- ✅ Use permissions for fine-grained control
|
|
- ✅ Combine roles and permissions for complex requirements
|
|
- ✅ Show/hide UI elements based on authorization
|
|
- ❌ Don't rely solely on client-side authorization
|
|
|
|
**Gotchas**:
|
|
- Authorization failure returns 403 Forbidden
|
|
- Unauthorized actions don't execute (state unchanged)
|
|
- Client-side UI should match server-side authorization
|
|
|
|
---
|
|
|
|
### @Idempotent
|
|
|
|
**Purpose**: Ensure an action can be safely retried without duplicate side effects.
|
|
|
|
**Signature**:
|
|
```php
|
|
#[Idempotent(
|
|
string $keyParam = 'idempotencyKey', // Parameter name for idempotency key
|
|
int $ttl = 3600 // TTL in seconds (default: 1 hour)
|
|
)]
|
|
```
|
|
|
|
**Basic Usage**:
|
|
```php
|
|
final class OrderForm extends LiveComponent
|
|
{
|
|
#[LiveAction]
|
|
#[Idempotent]
|
|
public function submitOrder(string $idempotencyKey, array $orderData): void
|
|
{
|
|
// Duplicate requests with same idempotencyKey return cached result
|
|
$order = $this->orderService->create($orderData);
|
|
$this->orderId = $order->id;
|
|
}
|
|
}
|
|
```
|
|
|
|
**HTML Template**:
|
|
```html
|
|
<div data-lc-component="OrderForm">
|
|
<form data-lc-action="submitOrder">
|
|
<!-- Idempotency key automatically generated -->
|
|
<input type="hidden" name="idempotencyKey" data-lc-idempotency />
|
|
|
|
<button type="submit">Submit Order</button>
|
|
</form>
|
|
</div>
|
|
```
|
|
|
|
**Custom Key Parameter**:
|
|
```php
|
|
final class PaymentProcessor extends LiveComponent
|
|
{
|
|
#[LiveAction]
|
|
#[Idempotent(keyParam: 'transactionId', ttl: 7200)]
|
|
public function processPayment(string $transactionId, array $paymentData): void
|
|
{
|
|
// Uses 'transactionId' as idempotency key
|
|
// Cached for 2 hours
|
|
$this->paymentService->process($transactionId, $paymentData);
|
|
}
|
|
}
|
|
```
|
|
|
|
**Use Cases**:
|
|
- ✅ Order submissions
|
|
- ✅ Payment processing
|
|
- ✅ Email sending
|
|
- ✅ API calls to external services
|
|
- ✅ Database writes
|
|
|
|
**Best Practices**:
|
|
- ✅ Use for all non-idempotent operations (CREATE, DELETE)
|
|
- ✅ Use longer TTL for critical operations (payments: 24 hours)
|
|
- ✅ Generate idempotency keys client-side (UUID v4)
|
|
- ❌ Don't use for read-only operations (unnecessary overhead)
|
|
|
|
**Gotchas**:
|
|
- Idempotent actions return cached response on retry
|
|
- TTL determines how long duplicates are prevented
|
|
- Client must provide idempotency key (automatic with `data-lc-idempotency`)
|
|
|
|
---
|
|
|
|
### @Encrypted
|
|
|
|
**Purpose**: Automatically encrypt and decrypt sensitive LiveProp values.
|
|
|
|
**Signature**:
|
|
```php
|
|
#[Encrypted(
|
|
string $algorithm = 'aes-256-gcm' // Encryption algorithm
|
|
)]
|
|
```
|
|
|
|
**Basic Usage**:
|
|
```php
|
|
final class PaymentForm extends LiveComponent
|
|
{
|
|
#[LiveProp]
|
|
public string $orderId;
|
|
|
|
#[LiveProp]
|
|
#[Encrypted]
|
|
public string $creditCardNumber = '';
|
|
|
|
#[LiveProp]
|
|
#[Encrypted]
|
|
public string $cvv = '';
|
|
|
|
#[LiveAction]
|
|
public function processPayment(): void
|
|
{
|
|
// Properties automatically decrypted here
|
|
$this->paymentService->charge(
|
|
$this->creditCardNumber,
|
|
$this->cvv
|
|
);
|
|
}
|
|
}
|
|
```
|
|
|
|
**Multiple Encrypted Props**:
|
|
```php
|
|
final class UserProfile extends LiveComponent
|
|
{
|
|
#[LiveProp]
|
|
public string $userId;
|
|
|
|
#[LiveProp]
|
|
#[Encrypted]
|
|
public string $ssn = '';
|
|
|
|
#[LiveProp]
|
|
#[Encrypted]
|
|
public string $bankAccount = '';
|
|
|
|
#[LiveProp]
|
|
#[Encrypted]
|
|
public array $sensitiveNotes = [];
|
|
}
|
|
```
|
|
|
|
**Best Practices**:
|
|
- ✅ Encrypt all PII (personally identifiable information)
|
|
- ✅ Encrypt financial data (credit cards, bank accounts)
|
|
- ✅ Encrypt authentication tokens
|
|
- ✅ Use in combination with HTTPS
|
|
- ❌ Don't encrypt non-sensitive data (performance overhead)
|
|
- ❌ Don't rely solely on encryption (validate and sanitize)
|
|
|
|
**Gotchas**:
|
|
- Encrypted props increase payload size (~30%)
|
|
- Encryption/decryption adds ~5ms latency
|
|
- Requires LIVECOMPONENT_ENCRYPTION_KEY in .env
|
|
|
|
---
|
|
|
|
## Performance Attributes
|
|
|
|
### @Optimistic
|
|
|
|
**Purpose**: Configure optimistic UI updates for instant perceived performance.
|
|
|
|
**Signature**:
|
|
```php
|
|
#[Optimistic(
|
|
bool $enabled = true, // Enable optimistic updates
|
|
array $updateProps = [], // Props to update optimistically
|
|
?string $rollbackOn = null // Rollback condition
|
|
)]
|
|
```
|
|
|
|
**Basic Usage**:
|
|
```php
|
|
final class LikeButton extends LiveComponent
|
|
{
|
|
#[LiveProp]
|
|
public int $likes = 0;
|
|
|
|
#[LiveProp]
|
|
public bool $isLiked = false;
|
|
|
|
#[LiveAction]
|
|
#[Optimistic(updateProps: ['likes', 'isLiked'])]
|
|
public function toggleLike(): void
|
|
{
|
|
// Client updates UI immediately
|
|
// Server confirms asynchronously
|
|
if ($this->isLiked) {
|
|
$this->likes--;
|
|
$this->isLiked = false;
|
|
} else {
|
|
$this->likes++;
|
|
$this->isLiked = true;
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
**HTML Template**:
|
|
```html
|
|
<div data-lc-component="LikeButton">
|
|
<button
|
|
data-lc-action="toggleLike"
|
|
data-lc-optimistic='{"likes": {{ likes + 1 }}, "isLiked": true}'
|
|
>
|
|
❤️ {{ likes }} {isLiked ? 'Unlike' : 'Like'}
|
|
</button>
|
|
</div>
|
|
```
|
|
|
|
**Conditional Rollback**:
|
|
```php
|
|
final class InventoryManager extends LiveComponent
|
|
{
|
|
#[LiveProp]
|
|
public int $stock = 100;
|
|
|
|
#[LiveAction]
|
|
#[Optimistic(
|
|
updateProps: ['stock'],
|
|
rollbackOn: 'stock < 0'
|
|
)]
|
|
public function reserveItem(int $quantity): void
|
|
{
|
|
if ($this->stock < $quantity) {
|
|
throw new InsufficientStockException();
|
|
}
|
|
|
|
$this->stock -= $quantity;
|
|
}
|
|
}
|
|
```
|
|
|
|
**Best Practices**:
|
|
- ✅ Use for user interactions (likes, votes, favorites)
|
|
- ✅ Use for non-critical updates
|
|
- ✅ Provide visual feedback for rollbacks
|
|
- ❌ Don't use for financial transactions
|
|
- ❌ Don't use for destructive actions
|
|
|
|
**Gotchas**:
|
|
- Optimistic updates happen instantly (<50ms)
|
|
- Server validation can trigger rollback
|
|
- Network failures automatically rollback
|
|
|
|
---
|
|
|
|
### @Cached
|
|
|
|
**Purpose**: Cache action results to improve performance for expensive operations.
|
|
|
|
**Signature**:
|
|
```php
|
|
#[Cached(
|
|
int $ttl = 3600, // Cache TTL in seconds
|
|
string $key = 'auto', // Cache key strategy
|
|
array $tags = [] // Cache tags for invalidation
|
|
)]
|
|
```
|
|
|
|
**Basic Usage**:
|
|
```php
|
|
final class ReportViewer extends LiveComponent
|
|
{
|
|
#[LiveProp]
|
|
public string $reportId;
|
|
|
|
#[LiveProp]
|
|
public array $reportData = [];
|
|
|
|
#[LiveAction]
|
|
#[Cached(ttl: 1800)]
|
|
public function loadReport(): void
|
|
{
|
|
// Expensive operation cached for 30 minutes
|
|
$this->reportData = $this->reportService->generate($this->reportId);
|
|
}
|
|
}
|
|
```
|
|
|
|
**Tagged Caching**:
|
|
```php
|
|
final class ProductCatalog extends LiveComponent
|
|
{
|
|
#[LiveProp]
|
|
public string $categoryId;
|
|
|
|
#[LiveProp]
|
|
public array $products = [];
|
|
|
|
#[LiveAction]
|
|
#[Cached(ttl: 3600, tags: ['products', 'category:{categoryId}'])]
|
|
public function loadProducts(): void
|
|
{
|
|
$this->products = $this->productService->getByCategory($this->categoryId);
|
|
}
|
|
|
|
#[LiveAction]
|
|
public function invalidateCache(): void
|
|
{
|
|
// Invalidate all product caches
|
|
$this->cache->invalidateTags(['products']);
|
|
}
|
|
}
|
|
```
|
|
|
|
**Custom Cache Keys**:
|
|
```php
|
|
final class UserDashboard extends LiveComponent
|
|
{
|
|
#[LiveProp]
|
|
public string $userId;
|
|
|
|
#[LiveAction]
|
|
#[Cached(ttl: 600, key: 'user:{userId}:dashboard')]
|
|
public function loadDashboard(): void
|
|
{
|
|
// Cache key: "user:123:dashboard"
|
|
$this->dashboardData = $this->dashboardService->getForUser($this->userId);
|
|
}
|
|
}
|
|
```
|
|
|
|
**Best Practices**:
|
|
- ✅ Cache expensive database queries
|
|
- ✅ Cache external API calls
|
|
- ✅ Use tags for batch invalidation
|
|
- ✅ Set appropriate TTL based on data freshness needs
|
|
- ❌ Don't cache user-specific sensitive data
|
|
- ❌ Don't cache highly dynamic data
|
|
|
|
**Gotchas**:
|
|
- Cache invalidation is eventual (not immediate)
|
|
- Tagged invalidation requires cache driver support
|
|
- Cache misses execute full action
|
|
|
|
---
|
|
|
|
### @NoBatch
|
|
|
|
**Purpose**: Disable automatic request batching for an action.
|
|
|
|
**Signature**:
|
|
```php
|
|
#[NoBatch]
|
|
```
|
|
|
|
**Basic Usage**:
|
|
```php
|
|
final class FileUploader extends LiveComponent
|
|
{
|
|
#[LiveAction]
|
|
#[NoBatch]
|
|
public function uploadFile(UploadedFile $file): void
|
|
{
|
|
// File uploads should not be batched
|
|
$this->uploadedFileId = $this->uploadService->store($file);
|
|
}
|
|
|
|
#[LiveAction]
|
|
public function deleteFile(string $fileId): void
|
|
{
|
|
// Regular action - can be batched
|
|
$this->uploadService->delete($fileId);
|
|
}
|
|
}
|
|
```
|
|
|
|
**Use Cases**:
|
|
- ✅ File uploads
|
|
- ✅ Real-time operations
|
|
- ✅ Time-sensitive actions
|
|
- ✅ Operations requiring immediate response
|
|
|
|
**Best Practices**:
|
|
- ✅ Use for operations that can't be delayed
|
|
- ✅ Use for operations with large payloads
|
|
- ❌ Don't overuse (batching improves performance)
|
|
|
|
**Gotchas**:
|
|
- Disables batching only for this specific action
|
|
- Other actions in the same component can still batch
|
|
- Increases number of HTTP requests
|
|
|
|
---
|
|
|
|
## Validation Attributes
|
|
|
|
### @Validated
|
|
|
|
**Purpose**: Automatically validate action parameters before execution.
|
|
|
|
**Signature**:
|
|
```php
|
|
#[Validated(
|
|
array $rules = [], // Validation rules per parameter
|
|
?string $errorFragment = null // Fragment to update on validation error
|
|
)]
|
|
```
|
|
|
|
**Basic Usage**:
|
|
```php
|
|
final class RegistrationForm extends LiveComponent
|
|
{
|
|
#[LiveProp]
|
|
public array $errors = [];
|
|
|
|
#[LiveAction]
|
|
#[Validated(
|
|
rules: [
|
|
'email' => ['required', 'email', 'unique:users'],
|
|
'password' => ['required', 'min:8', 'confirmed'],
|
|
'username' => ['required', 'alphanumeric', 'min:3', 'max:20']
|
|
],
|
|
errorFragment: 'form-errors'
|
|
)]
|
|
public function register(string $email, string $password, string $username): void
|
|
{
|
|
// Parameters automatically validated
|
|
$user = $this->authService->register($email, $password, $username);
|
|
$this->redirect('/dashboard');
|
|
}
|
|
}
|
|
```
|
|
|
|
**HTML Template**:
|
|
```html
|
|
<div data-lc-component="RegistrationForm">
|
|
<form data-lc-action="register">
|
|
<div>
|
|
<input type="email" name="email" required />
|
|
<if condition="errors.email">
|
|
<span class="error">{{ errors.email }}</span>
|
|
</if>
|
|
</div>
|
|
|
|
<div>
|
|
<input type="password" name="password" required />
|
|
<if condition="errors.password">
|
|
<span class="error">{{ errors.password }}</span>
|
|
</if>
|
|
</div>
|
|
|
|
<div>
|
|
<input type="text" name="username" required />
|
|
<if condition="errors.username">
|
|
<span class="error">{{ errors.username }}</span>
|
|
</if>
|
|
</div>
|
|
|
|
<button type="submit">Register</button>
|
|
</form>
|
|
|
|
<div data-lc-fragment="form-errors">
|
|
<if condition="errors">
|
|
<div class="alert alert-error">
|
|
Please correct the errors above.
|
|
</div>
|
|
</if>
|
|
</div>
|
|
</div>
|
|
```
|
|
|
|
**Available Validation Rules**:
|
|
- `required` - Field must be present and non-empty
|
|
- `email` - Valid email format
|
|
- `min:n` - Minimum length/value
|
|
- `max:n` - Maximum length/value
|
|
- `numeric` - Numeric value
|
|
- `alphanumeric` - Alphanumeric characters only
|
|
- `unique:table,column` - Unique in database
|
|
- `exists:table,column` - Exists in database
|
|
- `confirmed` - Match confirmation field (password_confirmation)
|
|
- `regex:pattern` - Match regex pattern
|
|
- `in:foo,bar,baz` - Value in list
|
|
- `url` - Valid URL format
|
|
- `date` - Valid date format
|
|
|
|
**Custom Validation**:
|
|
```php
|
|
final class ProductForm extends LiveComponent
|
|
{
|
|
#[LiveAction]
|
|
public function saveProduct(array $productData): void
|
|
{
|
|
// Custom validation logic
|
|
$validator = new ProductValidator();
|
|
|
|
if (!$validator->validate($productData)) {
|
|
$this->errors = $validator->getErrors();
|
|
return; // Stop execution
|
|
}
|
|
|
|
$this->productService->save($productData);
|
|
}
|
|
}
|
|
```
|
|
|
|
**Best Practices**:
|
|
- ✅ Validate all user input
|
|
- ✅ Use appropriate rules for each field type
|
|
- ✅ Provide clear error messages
|
|
- ✅ Use `errorFragment` to update only error section
|
|
- ❌ Don't trust client-side validation alone
|
|
|
|
**Gotchas**:
|
|
- Validation errors prevent action execution
|
|
- Errors automatically populate `$errors` property
|
|
- Validation runs before authorization checks
|
|
|
|
---
|
|
|
|
## State Management Attributes
|
|
|
|
### @Persisted
|
|
|
|
**Purpose**: Automatically persist component state across requests/sessions.
|
|
|
|
**Signature**:
|
|
```php
|
|
#[Persisted(
|
|
string $storage = 'session', // Storage type ('session', 'cookie', 'database')
|
|
int $ttl = 3600, // TTL in seconds
|
|
array $props = [] // Specific props to persist (default: all LiveProps)
|
|
)]
|
|
```
|
|
|
|
**Session Persistence**:
|
|
```php
|
|
final class ShoppingCart extends LiveComponent
|
|
{
|
|
#[LiveProp]
|
|
#[Persisted(storage: 'session')]
|
|
public array $items = [];
|
|
|
|
#[LiveAction]
|
|
public function addItem(string $productId, int $quantity): void
|
|
{
|
|
// Cart persists across page navigations
|
|
$this->items[] = [
|
|
'product_id' => $productId,
|
|
'quantity' => $quantity
|
|
];
|
|
}
|
|
}
|
|
```
|
|
|
|
**Cookie Persistence**:
|
|
```php
|
|
final class ThemeSwitcher extends LiveComponent
|
|
{
|
|
#[LiveProp]
|
|
#[Persisted(storage: 'cookie', ttl: 31536000)]
|
|
public string $theme = 'light';
|
|
|
|
#[LiveAction]
|
|
public function toggleTheme(): void
|
|
{
|
|
// Theme persists for 1 year
|
|
$this->theme = $this->theme === 'light' ? 'dark' : 'light';
|
|
}
|
|
}
|
|
```
|
|
|
|
**Database Persistence**:
|
|
```php
|
|
final class DraftEditor extends LiveComponent
|
|
{
|
|
#[LiveProp]
|
|
public string $documentId;
|
|
|
|
#[LiveProp]
|
|
#[Persisted(storage: 'database', ttl: 86400)]
|
|
public string $draftContent = '';
|
|
|
|
#[LiveAction]
|
|
public function autosave(string $content): void
|
|
{
|
|
// Draft persists for 24 hours
|
|
$this->draftContent = $content;
|
|
}
|
|
}
|
|
```
|
|
|
|
**Selective Persistence**:
|
|
```php
|
|
final class FilterPanel extends LiveComponent
|
|
{
|
|
#[LiveProp]
|
|
public array $filters = [];
|
|
|
|
#[LiveProp]
|
|
public array $results = [];
|
|
|
|
#[Persisted(storage: 'session', props: ['filters'])]
|
|
public function mount(): void
|
|
{
|
|
// Only 'filters' persisted, not 'results'
|
|
}
|
|
}
|
|
```
|
|
|
|
**Best Practices**:
|
|
- ✅ Use session for temporary state (shopping carts)
|
|
- ✅ Use cookies for user preferences (theme, locale)
|
|
- ✅ Use database for long-term drafts
|
|
- ✅ Set appropriate TTL based on data sensitivity
|
|
- ❌ Don't persist large amounts of data
|
|
- ❌ Don't persist sensitive data in cookies
|
|
|
|
**Gotchas**:
|
|
- Session storage cleared on logout
|
|
- Cookie storage limited to 4KB
|
|
- Database storage requires additional queries
|
|
|
|
---
|
|
|
|
## Combining Attributes
|
|
|
|
**Multiple Attributes on Single Action**:
|
|
```php
|
|
final class PaymentProcessor extends LiveComponent
|
|
{
|
|
#[LiveAction]
|
|
#[RateLimit(requests: 3, window: 3600, key: 'user')]
|
|
#[Authorize(permissions: ['payments.process'])]
|
|
#[Idempotent(ttl: 86400)]
|
|
#[Validated(rules: [
|
|
'amount' => ['required', 'numeric', 'min:1'],
|
|
'currency' => ['required', 'in:USD,EUR,GBP']
|
|
])]
|
|
#[Fragment('payment-result')]
|
|
public function processPayment(
|
|
string $idempotencyKey,
|
|
float $amount,
|
|
string $currency
|
|
): void {
|
|
// Secure, rate-limited, idempotent, validated payment processing
|
|
$this->paymentService->charge($amount, $currency);
|
|
}
|
|
}
|
|
```
|
|
|
|
**Execution Order**:
|
|
1. **@RateLimit** - Check rate limits first
|
|
2. **@Authorize** - Check permissions
|
|
3. **@Validated** - Validate parameters
|
|
4. **@Idempotent** - Check for duplicate requests
|
|
5. **@Cached** - Check cache (if applicable)
|
|
6. **Action Execution** - Execute action logic
|
|
7. **@Fragment** - Update specific fragments
|
|
8. **@Optimistic** - Apply optimistic updates
|
|
9. **@Persisted** - Persist state changes
|
|
|
|
---
|
|
|
|
## Summary Table
|
|
|
|
| Attribute | Purpose | Performance Impact | Use Case |
|
|
|-----------|---------|-------------------|----------|
|
|
| `@LiveProp` | Reactive properties | Low | State synchronization |
|
|
| `@LiveAction` | Server actions | Low | User interactions |
|
|
| `@Fragment` | Partial updates | **High** (70%+ reduction) | Large components |
|
|
| `@RateLimit` | Prevent abuse | Low | Public endpoints |
|
|
| `@Authorize` | Access control | Low | Restricted actions |
|
|
| `@Idempotent` | Duplicate prevention | Low | Critical operations |
|
|
| `@Encrypted` | Data protection | Medium (+30% payload) | Sensitive data |
|
|
| `@Optimistic` | Instant UI | **High** (<50ms latency) | Interactive UIs |
|
|
| `@Cached` | Response caching | **High** (avoid queries) | Expensive ops |
|
|
| `@NoBatch` | Disable batching | Negative (more requests) | File uploads |
|
|
| `@Validated` | Input validation | Low | User input |
|
|
| `@Persisted` | State persistence | Medium | User preferences |
|
|
|
|
---
|
|
|
|
## Debugging Attributes
|
|
|
|
**Check Attribute Configuration**:
|
|
```javascript
|
|
// Client-side debugging
|
|
const component = LiveComponent.getComponent('component-id');
|
|
console.log(component.config.attributes);
|
|
|
|
// Output:
|
|
{
|
|
rateLimit: { requests: 60, window: 60 },
|
|
fragments: ['user-profile', 'notifications'],
|
|
optimistic: { enabled: true, updateProps: ['likes'] },
|
|
cached: { ttl: 3600, tags: ['users'] }
|
|
}
|
|
```
|
|
|
|
**Server-side Debugging**:
|
|
```php
|
|
// Get action metadata
|
|
$reflection = new \ReflectionMethod(MyComponent::class, 'myAction');
|
|
$attributes = $reflection->getAttributes(LiveAction::class);
|
|
|
|
foreach ($attributes as $attribute) {
|
|
var_dump($attribute->getArguments());
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Best Practices Summary
|
|
|
|
1. **Use `@Fragment`** for components >5KB or with independent sections
|
|
2. **Use `@Optimistic`** for non-critical user interactions (likes, votes)
|
|
3. **Use `@RateLimit`** on all public endpoints and expensive operations
|
|
4. **Use `@Authorize`** for all privileged actions
|
|
5. **Use `@Idempotent`** for CREATE/DELETE operations
|
|
6. **Use `@Encrypted`** for PII and financial data
|
|
7. **Use `@Cached`** for expensive queries (>100ms)
|
|
8. **Use `@Validated`** for all user input
|
|
9. **Combine attributes** for defense-in-depth security
|
|
10. **Test attribute behavior** with integration tests
|
|
|
|
---
|
|
|
|
For more examples and advanced patterns, see:
|
|
- [Advanced Features Guide](advanced-features.md)
|
|
- [Performance Guide](performance-guide.md)
|
|
- [Security Guide](security-guide.md)
|