ImportComponent

ImportComponent turns a PHP partial into a real PulsePoint component boundary: it executes the file in an isolated namespace (preventing function redeclare collisions), parses the produced HTML, enforces exactly one root element, injects pp-component + serialized props as attributes, and returns clean, hydratable markup.

Best for: server-first pages that need small reactive islands (PulsePoint), while keeping PHP templating ergonomics.
Icon style:

Concept

The core idea is simple: every imported PHP file becomes a single-root component that PulsePoint can target later. The root node is automatically decorated with:

  • pp-component — a deterministic ID derived from the file path (CRC32 → base36), stable for that path.
  • Serialized props as attributes — scalar values become strings, booleans become true/false, arrays/objects become JSON.
  • PulsePoint mustache compatibility — when a prop contains mustache (ex: {count}), the prop key is converted from camelCase → kebab-case, keeping HTML attribute rules while preserving JS naming conventions.

How it Works Internally

Pipeline
  1. Execute component in an isolated namespace
    Props are extract()'d into local scope, then file source is wrapped in a unique namespace and eval()'d. This prevents global function redeclare collisions.
  2. Capture HTML output
    Output is buffered and returned as a string.
  3. Parse as XML fragment
    Uses TemplateCompiler::convertToXml() to safely parse the HTML fragment.
  4. Enforce exactly one root element
    If multiple root elements are found, an exception is thrown.
  5. Inject pp-component + props
    Root gets pp-component="s..." and prop attributes.
  6. Serialize and echo final HTML
    The resulting HTML is echoed and stored in ImportComponent::sections().
Automatic Exposed Function Registry

During isolated execution, ImportComponent detects any newly defined functions in the sandbox namespace that have the @Exposed attribute and registers them into ExposedRegistry. This enables PulsePoint to call PHP-exposed server functions (via your existing Exposed tooling) without requiring global function names in the main namespace.

// Inside a component file:
use PP\Attributes\Exposed;

#[Exposed]
function saveDraft(array $payload): array {
  // ...
  return ['ok' => true];
}

// ImportComponent will automatically register it
// under its short name: "saveDraft".

API Reference

static render(string $filePath, array $props = []): void

Executes the PHP file, enforces single-root output, injects attributes, stores a snapshot in ImportComponent::sections(), and echoes the final HTML.

static import(string $filePath, array $props = []): void

Alias of render(). Use whichever reads better in templates.

static sections(): array

Returns an array snapshot of imported sections keyed by $filePath, containing: path, html, and props. Useful for debugging, devtools, or server-side inspection.

Props, Attributes, and Serialization Rules

  • Null props are skipped — if a value is null, no attribute is emitted.
  • Booleans become "true" / "false".
  • Scalars (string/int/float) are cast to string.
  • Arrays/objects become JSON using: JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES.
  • Mustache-aware attribute naming — if the serialized value contains mustache syntax ({...}), the prop key is converted from camelCasekebab-case.
// Example:
ImportComponent::render(APP_PATH . '/inc/counter.php', [
  'count'    => '{count}',     // becomes count="{count}"
  'setCount' => '{setCount}',  // becomes set-count="{setCount}"
  'config'   => ['step' => 2], // becomes config='{"step":2}'
]);

Single Root Element is Mandatory

ImportComponent throws an exception unless the imported file renders exactly one root element. Wrap your template in a single container (or a semantic tag like section, article, etc.).

Usage Examples

1. Basic Import

Import a PHP component file and automatically generate a stable pp-component id.

<?php
use PP\ImportComponent;

ImportComponent::render(APP_PATH . '/inc/Hero.php');
?>

2. Passing Props (Server Data → Attributes)

PHP Input
<?php
use PP\ImportComponent;

$user = ['id' => 7, 'name' => 'Jefferson'];

ImportComponent::render(APP_PATH . '/inc/UserCard.php', [
  'user' => $user,
  'variant' => 'compact',
  'isOnline' => true,
]);
?>
HTML Output
<div
  pp-component="s1x..."
  user='{"id":7,"name":"Jefferson"}'
  variant="compact"
  isOnline="true"
>
  <!-- component output... -->
</div>

3. PulsePoint Reactive Bindings (Mustache → kebab-case)

Any prop value containing mustache ({...}) triggers camelCase → kebab-case conversion for the attribute name. This is ideal for passing signals/setters into components.

<?php
use PP\ImportComponent;
?>

<div class="space-y-4">

  <?php ImportComponent::render(APP_PATH . '/inc/SearchBox.php', [
    'query'    => '{query}',
    'setQuery' => '{setQuery}', // becomes set-query="{setQuery}"
  ]); ?>

  <script type="text/pp">
    const [query, setQuery] = pp.state('');
  </script>

</div>

4. Component File Template (Single Root + Icon Style)

Your component file must output a single root element. Below is a pattern-friendly template that matches Tailwind + Shadcn tokens and demonstrates the Lucide icon style inside the markup.

<?php
// File: /inc/SearchBox.php
declare(strict_types=1);

// props are available as local variables if passed:
// $query, $setQuery
?>

<div class="rounded-lg border bg-card p-4">
  <div class="flex items-center gap-2">

    <!-- Lucide style (shown as example) -->
    <!-- <Search class="size-4" /> -->

    <span class="text-sm font-medium text-foreground">Search</span>
  </div>

  <div class="mt-3">
    <input
      class="w-full rounded-md border border-input bg-background px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground"
      placeholder="Type to search..."
      value="{query}"
      oninput="{(e) => setQuery(e.target.value)}"
    />
  </div>
</div>

5. Debugging: Inspect Imported Sections

sections() keeps a record of everything imported during the request. This is useful for debugging, devtools overlays, or server logs.

<?php
use PP\ImportComponent;

ImportComponent::render(APP_PATH . '/inc/Hero.php', ['title' => 'Hello']);
ImportComponent::render(APP_PATH . '/inc/UserCard.php', ['user' => ['id' => 1]]);

$sections = ImportComponent::sections();

// Example: var_dump($sections[APP_PATH . '/inc/Hero.php']['html']);
?>

Notes & Best Practices

  • Keep component files deterministic — since the ID is path-based, moving/renaming files changes pp-component.
  • Avoid multiple roots — wrap everything in a single container. If you need multiple siblings, nest them inside one root node.
  • Strict types handling — a leading declare(strict_types=1); is stripped before eval to keep sandbox execution safe.
  • Security posture — only import trusted files from your own codebase. This is a server-side render tool; do not pass user-supplied file paths.
  • PulsePoint-friendly attributes — use mustache bindings for signals/setters; camelCase keys will become valid HTML attributes automatically.