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]. -
allowedRolesshould 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.