v1 LiVue v1 is here — server-driven reactivity for Laravel using Vue.js Get Started →

PHP Composables

Reusable units of server-side logic that expose data and actions directly to your Vue templates under clean namespaces. Inspired by Vue's Composition API, but executed entirely on the server with full access to auth, database and services.

Creating a Composable

A composable is a PHP trait with a use* method that returns an array of data and closures (actions). Generate one with Artisan:

php artisan make:livue-composable Auth
app/LiVue/Composables/UseAuth.php
<?php

namespace App\LiVue\Composables;

trait UseAuth
{
    public function useAuth(): array
    {
        $user = auth()->user();

        return [
            // Data (available in template)
            'user' => $user,
            'name' => $user?->name ?? 'Guest',
            'isAuthenticated' => auth()->check(),
            'isAdmin' => $user?->hasRole('admin') ?? false,

            // Actions (callable from template)
            'logout' => function() {
                auth()->logout();
                $this->redirect('/login');
            },
        ];
    }
}

Using in a Component

Add the trait to your component. All use* methods are automatically discovered — no need to declare a $composables array. The method name minus the use prefix becomes the namespace in the template:

app/LiVue/Dashboard.php
class Dashboard extends Component
{
    use UseAuth, UseNotifications;
}

Tip — You can still use the $composables array, the #[Composable] attribute, or the getComposables() method for explicit control. Auto-discovery simply removes the need for boilerplate when you just want all use* methods to be active.

Template
<nav>
    <span>Welcome, {{ auth.name }}</span>
    <span v-if="auth.isAdmin">Admin</span>
    <span>{{ notifications.count }} unread</span>
    <button @click="auth.logout()">Logout</button>
</nav>

Composable State

Use composableState() to maintain private persistent state that does not require public properties on the component:

app/LiVue/Composables/UseCart.php
trait UseCart
{
    public function useCart(): array
    {
        // Private state - persists between requests
        $state = $this->composableState('cart', [
            'items' => [],
        ]);

        $items = collect($state['items']);

        return [
            'items' => $state['items'],
            'count' => $items->sum('qty'),
            'total' => $items->sum(fn($i) => $i['price'] * $i['qty']),
            'isEmpty' => $items->isEmpty(),

            'add' => function(int $productId, int $qty = 1) use ($state) {
                $product = Product::findOrFail($productId);
                $state['items'] = [...$state['items'], [
                    'product_id' => $productId,
                    'name' => $product->name,
                    'price' => $product->price,
                    'qty' => $qty,
                ]];
            },

            'remove' => function(int $index) use ($state) {
                $items = $state['items'];
                unset($items[$index]);
                $state['items'] = array_values($items);
            },

            'clear' => fn() => $state['items'] = [],
        ];
    }
}
Template
<div v-if="cart.isEmpty">Your cart is empty</div>

<div v-for="(item, index) in cart.items" :key="index">
    {{ item.name }} - {{ item.price }} EUR
    <button @click="cart.remove(index)">Remove</button>
</div>

<p>Total: {{ cart.total.toFixed(2) }} EUR ({{ cart.count }} items)</p>
<button @click="cart.add(42)">Add Product</button>
<button @click="cart.clear()">Clear Cart</button>

Global Composables

Register composables globally from a service provider with Component::use(). Global composables are available to all components without needing traits or declarations.

Closure-based

app/Providers/AppServiceProvider.php
use LiVue\Component;

public function boot(): void
{
    Component::use('auth', function () {
        $user = auth()->user();

        return [
            'name' => $user?->name ?? 'Guest',
            'isAdmin' => $user?->hasRole('admin') ?? false,
            'logout' => function () {
                auth()->logout();
                $this->redirect('/login');
            },
        ];
    });
}

The closure is bound to the component instance at runtime, so $this refers to the current component.

Class-based (mixin pattern)

Group related composables into a class. Each use* method must return a closure:

app/LiVue/Composables/AuthComposables.php
class AuthComposables
{
    public function useAuth()
    {
        return function (): array {
            return [
                'user' => auth()->user(),
                'logout' => fn () => auth()->logout(),
            ];
        };
    }

    public function usePermissions()
    {
        return function (): array {
            return [
                'can' => fn (string $p) => auth()->user()?->can($p) ?? false,
            ];
        };
    }
}

// In AppServiceProvider::boot()
Component::use(new AuthComposables());
// or
Component::use(AuthComposables::class);

Precedence — Local composables (defined on the component) always take precedence over global composables with the same namespace.

Naming Convention

The template namespace is derived from the method name by removing the use prefix and converting to camelCase:

Method Namespace Template access
useAuth() auth auth.name, auth.logout()
useShoppingCart() shoppingCart shoppingCart.items
useNotifications() notifications notifications.count

Security

Composables are re-executed on every AJAX request, so authorization checks and data are always fresh. Only closures explicitly returned are callable from the client — the server never exposes internal methods.

Rule Description
Action whitelist Only closures explicitly returned in the array are callable from the client
HMAC checksum All composable data is protected by the snapshot HMAC — tampered requests are rejected
Re-execution Composables run on every request, ensuring authorization is always verified
Blocked methods use, getGlobalComposables, flushGlobalComposables are not callable from the client