Usage Guide

Getting the ViewFactory

The ViewFactory is registered as a singleton by ViewsServiceProvider. Retrieve it from the container:

use WPZylos\Framework\Views\ViewFactory;

// By alias
$views = $app->make('view');

// By class name
$views = $app->make(ViewFactory::class);

Or instantiate directly:

$views = new ViewFactory($context);
// or with a custom base path
$views = new ViewFactory($context, '/path/to/views');

If no $basePath is provided, it defaults to $context->path('resources/views').


Rendering Views

Use render() to immediately render a view template and return the HTML string:

$html = $views->render('admin.settings', [
    'title' => 'Plugin Settings',
    'options' => $options,
]);

echo $html;

View Naming Convention

Views use dot notation where dots are converted to directory separators:

View NameResolved Path
admin.settingsresources/views/admin/settings.php
emails.welcomeresources/views/emails/welcome.php
products.indexresources/views/products/index.php
dashboardresources/views/dashboard.php

File Extension Resolution

The factory tries these extensions in order:

  1. .php
  2. .html.php
  3. .twig
  4. .html.twig
  5. Raw filename (no extension added)

The first matching file is used.


Deferred Rendering

Use make() to create a View instance without immediately rendering. This is useful when you need to build up data or pass the view object around:

$view = $views->make('admin.dashboard');

// Add data with chaining
$view->with('title', 'Dashboard')
     ->with('stats', $stats);

// Add multiple values at once
$view->with([
    'notices' => $notices,
    'user' => $currentUser,
]);

// Render explicitly
$html = $view->render();

// Or cast to string (triggers render automatically)
echo $view;

Sharing Data Globally

Use share() to make data available to every view rendered by this factory:

// Share a single value
$views->share('siteName', get_bloginfo('name'));

// Share multiple values
$views->share([
    'currentUser' => wp_get_current_user(),
    'isAdmin' => current_user_can('manage_options'),
    'pluginVersion' => '2.0.0',
]);

Shared data is merged with per-view data. Per-view data takes precedence if keys overlap.


Checking View Existence

if ($views->exists('admin.custom-page')) {
    echo $views->render('admin.custom-page', $data);
} else {
    echo $views->render('admin.not-found');
}

Writing PHP Templates

PHP templates receive data as extracted local variables via extract(). Use WordPress escaping functions for output:

<!-- resources/views/admin/settings.php -->
<div class="wrap">
    <h1><?= esc_html($title) ?></h1>

    <form method="post">
        <?php foreach ($options as $key => $option): ?>
            <label for="<?= esc_attr($key) ?>">
                <?= esc_html($option['label']) ?>
            </label>
            <input
                type="text"
                id="<?= esc_attr($key) ?>"
                name="<?= esc_attr($key) ?>"
                value="<?= esc_attr($option['value']) ?>"
            />
        <?php endforeach; ?>

        <?php wp_nonce_field('save_settings'); ?>
        <?php submit_button(); ?>
    </form>
</div>

Escaping in PHP Templates

Use WordPress's built-in escaping functions:

FunctionPurposeExample
esc_html()HTML content<?= esc_html($title) ?>
esc_attr()HTML attributesvalue="<?= esc_attr($v) ?>"
esc_url()URLshref="<?= esc_url($url) ?>"
esc_textarea()Textarea content<?= esc_textarea($text) ?>
wp_kses_post()Allow safe HTML<?= wp_kses_post($content) ?>

Using Twig Templates

Twig is an optional dependency. To use it:

1. Install Twig

composer require twig/twig:^3.0

2. Register the TwigEngine

use WPZylos\Framework\Views\Engines\TwigEngine;

$twigEngine = new TwigEngine(
    $views->getBasePath(),    // Base path (same as ViewFactory)
    '/path/to/cache',         // Cache directory (null to disable)
    false                     // Debug mode
);

$views->addEngine($twigEngine);

3. Create .twig Templates

{# resources/views/emails/welcome.twig #}
<h1>Welcome, {{ user.name }}!</h1>
<p>Thanks for joining {{ siteName }}.</p>

<ul>
{% for feature in features %}
    <li>{{ feature }}</li>
{% endfor %}
</ul>

Twig has HTML autoescape enabled by default.

Accessing the Twig Environment

For advanced customization (adding globals, filters, functions):

$twig = $twigEngine->getEnvironment();

// Add a global
$twig->addGlobal('wp_version', get_bloginfo('version'));

// Add a custom filter
$twig->addFilter(new \Twig\TwigFilter('price', function ($value) {
    return '$' . number_format($value, 2);
}));

Custom Template Engines

Implement EngineInterface to add support for any template format:

use WPZylos\Framework\Views\EngineInterface;

class MarkdownEngine implements EngineInterface
{
    public function render(string $path, array $data = []): string
    {
        $content = file_get_contents($path);
        // Process markdown and interpolate data
        return $this->parseMarkdown($content, $data);
    }

    public function canRender(string $path): bool
    {
        return str_ends_with($path, '.md');
    }
}

// Register your engine
$views->addEngine(new MarkdownEngine());

Custom engines are prepended to the engine list and are checked first during resolution.


Service Provider Registration

The ViewsServiceProvider automatically registers the ViewFactory:

$app->register(new ViewsServiceProvider());

// ViewFactory is now available as:
$views = $app->make('view');
$views = $app->make(ViewFactory::class);

The provider creates the ViewFactory with:

  • Context from $app->context()
  • Base path from $app->paths()->path('@views')

DaisyUI CSS Helper

The CssHelper provides prefixed CSS class names for DaisyUI components, ensuring style isolation between plugins.

Getting the CssHelper

use WPZylos\Framework\Views\CssHelper;

// By alias
$css = $app->make('css');

// By class name
$css = $app->make(CssHelper::class);

Generating Prefixed Classes

// Single class
echo $css->cls('btn');
// Output: 'fcds-btn'

// Multiple classes
echo $css->cls('btn', 'btn-primary', 'btn-lg');
// Output: 'fcds-btn fcds-btn-primary fcds-btn-lg'

Admin Root Scope

Wrap admin pages in a DaisyUI-scoped root container:

// Open admin wrapper
echo $css->adminOpen();
// Output: <div id="fcds-admin" class="wrap">

// Your admin page content here...

// Close wrapper
echo $css->adminClose();
// Output: </div>

Using in Templates

<!-- resources/views/admin/settings.php -->
<?= $css->adminOpen() ?>
    <div class="<?= esc_attr($css->cls('card', 'bg-base-100', 'shadow-xl')) ?>">
        <div class="<?= esc_attr($css->cls('card-body')) ?>">
            <h2 class="<?= esc_attr($css->cls('card-title')) ?>">Settings</h2>
            <button class="<?= esc_attr($css->cls('btn', 'btn-primary')) ?>">
                Save Changes
            </button>
        </div>
    </div>
<?= $css->adminClose() ?>

JavaScript Mount Points

The JsMount helper renders mount points for Vue.js and React applications, with automatic data passing via window variables.

Getting JsMount

use WPZylos\Framework\Views\JsMount;

// By alias
$js = $app->make('js.mount');

// By class name
$js = $app->make(JsMount::class);

Vue.js Admin Page

Render a Vue app mount point inside the admin DaisyUI root scope:

// In your admin page callback
public function renderSettingsPage(): void
{
    $js = $this->app->make('js.mount');

    echo $js->adminMount('vue-settings-app', [
        'apiUrl'  => rest_url('my-plugin/v1/'),
        'nonce'   => wp_create_nonce('wp_rest'),
        'options' => get_option('my_plugin_options', []),
    ]);
}

This outputs:

<div id="fcds-admin" class="wrap">
  <div id="vue-settings-app"></div>
  <script>window.mypluginData = {"apiUrl":"/wp-json/my-plugin/v1/","nonce":"abc123","options":[]};</script>
</div>

Your Vue app mounts to #vue-settings-app and reads config from window.mypluginData.

React Admin Page

The same helper works for React — just mount to the rendered element:

echo $js->adminMount('react-dashboard', [
    'user'    => wp_get_current_user()->display_name,
    'apiBase' => rest_url('my-plugin/v1/'),
], 'myPluginDashboard');

In your React entry file:

const root = ReactDOM.createRoot(document.getElementById('react-dashboard'));
root.render(<App config={window.myPluginDashboard} />);

Frontend Mount Point

For frontend pages (no admin root wrapper):

echo $js->frontendMount('product-gallery', [
    'products' => $products,
    'currency' => 'USD',
]);

This outputs:

<div id="product-gallery"></div>
<script>window.mypluginData = {"products":[...],"currency":"USD"};</script>

Shortcode Mount Point

Use shortcodeMount() for WordPress shortcodes — it works identically to frontendMount():

public function registerShortcode(): void
{
    add_shortcode('my_widget', [$this, 'renderWidget']);
}

public function renderWidget(array $atts): string
{
    $js = $this->app->make('js.mount');
    $id = 'my-widget-' . uniqid();

    return $js->shortcodeMount($id, [
        'title'  => $atts['title'] ?? 'Widget',
        'apiUrl' => rest_url('my-plugin/v1/widget'),
        'nonce'  => wp_create_nonce('wp_rest'),
    ]);
}

Custom Variable Names

By default, the JS variable name is derived from the plugin slug (e.g., mypluginData). Override it with the third parameter:

echo $js->adminMount('app', $data, 'MyCustomVar');
// Uses: window.MyCustomVar = {...};