Exposed

The Exposed attribute is the bridge between your PHP backend and pp.fetchFunction(...) on the client. By default, functions are private to the server. Only functions or methods explicitly marked with #[Exposed] can be called from the frontend.

Why Exposed Exists

Private by Default

Server safety first.

Regular PHP functions are not publicly callable from PulsePoint. This prevents accidental exposure of internal logic.

Controlled Access

Add auth and role rules.

You can require authentication, restrict access to specific roles, and apply rate limits directly on the exposed handler.

Client Integration

Works with fetchFunction.

Exposed handlers are resolved and executed when the client calls pp.fetchFunction().

Constructor Signature

#[Exposed(
    requiresAuth: false,
    allowedRoles: null,
    limits: null
)]
Argument Type Description
requiresAuth bool When true, the caller must be authenticated before the function can run.
allowedRoles ?array Restricts access to specific roles such as ['admin'] or ['admin', 'editor']. This effectively implies authenticated access.
limits string|array|null Applies one or more rate limits, for example '5/minute' or ['5/m', '100/d'].

Attribute Class

<?php

namespace PP\Attributes;

use Attribute;

#[Attribute(Attribute::TARGET_FUNCTION | Attribute::TARGET_METHOD)]
class Exposed
{
    /**
     * @param bool $requiresAuth
     * @param array|null $allowedRoles
     * @param string|array|null $limits Explicit rate limits (e.g., "5/minute" or ["5/m", "100/d"])
     */
    public function __construct(
        public bool $requiresAuth = false,
        public ?array $allowedRoles = null,
        public string|array|null $limits = null
    ) {}
}

Registry Resolution

PulsePoint keeps track of exposed functions through the registry. It maps a short callable name to the fully qualified function name so the framework can resolve what should run when the client requests it.

<?php

declare(strict_types=1);

namespace PP\Attributes;

final class ExposedRegistry
{
    /** @var array<string,string> map shortName => fullyQualifiedFunctionName */
    private static array $functions = [];

    public static function registerFunction(string $shortName, string $fqn): void
    {
        // last one wins (helps if same name is defined multiple times)
        self::$functions[$shortName] = $fqn;
    }

    public static function resolveFunction(string $shortName): ?string
    {
        return self::$functions[$shortName] ?? null;
    }

    public static function all(): array
    {
        return self::$functions;
    }
}

What the registry stores

Short names mapped to callable targets.

  • registerFunction(string $shortName, string $fqn): void — Registers an exposed function name.
  • resolveFunction(string $shortName): ?string — Resolves the callable requested by the client.
  • all(): array — Returns the full registry map.

Usage Examples

1. Public function

This function is callable from the client without authentication.

<?php

use PP\Attributes\Exposed;

#[Exposed]
function getProducts(): array
{
    return [
        ['id' => 1, 'name' => 'Desk'],
        ['id' => 2, 'name' => 'Chair'],
    ];
}

2. Auth-protected function

Use requiresAuth: true when the handler should only be available to signed-in users.

<?php

use PP\Attributes\Exposed;

#[Exposed(requiresAuth: true)]
function getProfile(): array
{
    return [
        'name' => 'Jefferson',
        'email' => 'user@example.com',
    ];
}

3. Role-based access

Restrict sensitive handlers to one or more roles.

<?php

use PP\Attributes\Exposed;

#[Exposed(allowedRoles: ['admin', 'manager'])]
function getAdminStats(): array
{
    return [
        'users' => 128,
        'sales' => 5230,
    ];
}

4. Rate-limited function

Add request throttling for endpoints that should be protected from abuse.

<?php

use PP\Attributes\Exposed;

#[Exposed(limits: ['10/minute', '200/day'])]
function submitContactForm(array $payload): array
{
    return [
        'success' => true,
        'message' => 'Message submitted successfully.',
    ];
}

5. Calling from PulsePoint

Once a function is exposed, the client can call it with pp.fetchFunction(...).

<script type="text/pp">
const state = pp.state({
    items: [],
    loading: false
});

async function loadProducts() {
    state.loading = true;

    const response = await pp.fetchFunction('getProducts');

    state.items = response?.data ?? [];
    state.loading = false;
}
</script>

Notes

  • Functions are not callable from the client unless they are explicitly marked with #[Exposed].
  • allowedRoles should be treated as protected access and typically requires an authenticated user context.
  • Use rate limits for write-heavy or abuse-prone actions such as contact forms, search endpoints, or AI-triggered handlers.
  • Keep internal-only helpers unexposed so they remain server-private.