Lightpack View Template System

The Lightpack\View\Template class is the core of Lightpack's view rendering system. It provides a minimal, robust, and flexible API for rendering PHP templates, including support for data injection, template composition, layout inheritance, and content stacking.

Use the helper function template() to utilize the view templating capabilities.

Lightpack expects all your view templates to be located in the views directory of your project. You can organize your layouts, partials, and page templates here, using subdirectories as needed.

Available Methods

include()

include(string $file, array $data = []): string

component()

component(string $file, array $data = []): string


Understanding Differences

These methods all render templates, but their behavior around data merging and intended use is different. Here's a practical breakdown to make their differences clear:

Example

Include the views/profile.php template with passed data.

<?= template()->include('profile', [
    'user' => 'Alice', 
    'role' => 'admin',
    'theme' => 'dark'
]);

In views/profile.php template:

<h1>Profile</h1>

<!-- views/partials/header.php -->
<?= template()->include('partials/header', [
    'page' => 'Profile'
]) ?>

<!-- views/components/button.php -->
<?= template()->component('components/button', [
    'label' => 'Save'
]) ?>

Data available in each method:

Method Available Data
include() user, role, theme, page
component() label

Decision Table

Use case Method
Include a full page include
Insert a partial (header, nav) include
Render a stateless widget component
Need parent data in template include
Want only explicit data component

Tip: Use include() for all template composition (main views, partials, nested templates). Use component() for isolated, reusable widgets that shouldn't access parent data.


Practical Usage in Controllers

Typically, templates are rendered as HTML responses directly from controller actions.

class ProfileController
{
    public function show()
    {
        return response()->view('profile', [
            'name' => 'John Doe',
            'email' => 'johndoe@example.com',
        ]);
    }
}

This will look for the views/profile.php template file and render it with passed data as second argument.

<ul>
    <li>Name: <?= $name ?></li>
    <li>Email: <?= $email ?></li>
</ul>

Layout Inheritance

Layout inheritance allows child templates to declare which layout they want to use, and layouts render the child content where specified.

layout()

layout(string $file): void

<?= template()->layout('layouts/app') ?>

<h1>Dashboard</h1>
<p>Welcome back!</p>

content()

content(): string

<!DOCTYPE html>
<html>
    <body>
        <main>
            <?= template()->content() ?>
        </main>
    </body>
</html>

Complete Example

Controller:

class DashboardController
{
    public function index()
    {
        return response()->view('dashboard', [
            'title' => 'Dashboard',
            'stats' => ['sales' => 23, 'leads' => 100],
        ]);
    }
}

Directory Structure:

views/
├── layouts/
│   └── app.php          # Main layout
├── dashboard.php        # Dashboard page
└── partials/
    ├── header.php
    └── footer.php

views/dashboard.php (Child Template):

<?= template()->layout('layouts/app') ?>

<div class="dashboard">
    <h1><?= $title ?></h1>
    <p>Total Sales: <?= $stats['sales'] ?></p>
    <p>Total Leads: <?= $stats['leads'] ?></p>
</div>

views/layouts/app.php (Layout):

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>My App</title>
</head>
<body>
    <?= template()->include('partials/header') ?>

    <main>
        <?= template()->content() ?>
    </main>

    <?= template()->include('partials/footer') ?>
</body>
</html>

Nested Layouts

You can nest layouts multiple levels deep. Each template declares its immediate parent.

views/dashboard.php:

<?= template()->layout('layouts/admin') ?>
<h1>Dashboard</h1>

views/layouts/admin.php:

<?= template()->layout('layouts/app') ?>

<div>
    <aside>Admin Sidebar</aside>
    <div>
        <?= template()->content() ?>
    </div>
</div>

views/layouts/app.php:

<!DOCTYPE html>
<html>
<body>
    <?= template()->content() ?>
</body>
</html>

Result: app.php wraps admin.php, which wraps dashboard.php.


Stacks (Assets Management)

Stacks allow child templates to push content (like CSS/JS links) into specific locations in parent layouts. This is perfect for managing page-specific assets.

push()

push(string $stack): void

endPush()

endPush(): void

stack()

stack(string $stack): string

Example: Page-Specific Assets

views/layouts/app.php:

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>My App</title>

    <!-- Common CSS -->
    <link rel="stylesheet" href="/css/app.css">

    <!-- Page-specific styles stack -->
    <?= template()->stack('styles') ?>
</head>
<body>
    <?= template()->content() ?>

    <!-- Common JS -->
    <script src="/js/app.js"></script>

    <!-- Page-specific scripts stack -->
    <?= template()->stack('scripts') ?>
</body>
</html>

views/dashboard.php:

<?= template()->layout('layouts/app') ?>

<?php template()->push('styles') ?>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="/css/dashboard.css">
<?php template()->endPush() ?>

<?php template()->push('scripts') ?>
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
<script src="/js/dashboard.js"></script>
<?php template()->endPush() ?>

<h1>Dashboard</h1>
<p>Your dashboard content here...</p>

Rendered Output:

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>My App</title>
    <link rel="stylesheet" href="/css/app.css">
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
    <link rel="stylesheet" href="/css/dashboard.css">
</head>
<body>
    <h1>Dashboard</h1>
    <p>Your dashboard content here...</p>

    <script src="/js/app.js"></script>
    <script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
    <script src="/js/dashboard.js"></script>
</body>
</html>

Multiple Pushes to Same Stack

You can push to the same stack multiple times—all content accumulates:

<?php template()->push('scripts') ?>
<script src="/js/charts.js"></script>
<?php template()->endPush() ?>

<!-- Later in the same template -->

<?php template()->push('scripts') ?>
<script src="/js/analytics.js"></script>
<?php template()->endPush() ?>

Both scripts will be rendered when stack('scripts') is called.

Common Stack Names

Stack Name Purpose Location in Layout
styles CSS files and <style> tags In <head>
scripts JavaScript files and <script> tags Before </body>
meta Meta tags In <head>
head Any other head content In <head>

Escaping Output

Lightpack provides a convenient global function, _e(), to help you safely output dynamic data in your templates:

⚠️ Security: Always Escape User Input

DANGEROUS (XSS Vulnerability):

<!-- ❌ Never do this with user input! -->
<div><?= $userComment ?></div>
<h1><?= $userName ?></h1>
<a href="<?= $userUrl ?>">Link</a>

SAFE (Properly Escaped):

<!-- ✅ Always escape user-provided data -->
<div><?= _e($userComment) ?></div>
<h1><?= _e($userName) ?></h1>
<a href="<?= _e($userUrl) ?>">Link</a>

When to Escape

Always escape:

Do NOT escape:

Example

<?php foreach($comments as $comment): ?>
    <div class="comment">
        <strong><?= _e($comment->author) ?></strong>
        <p><?= _e($comment->text) ?></p>
    </div>
<?php endforeach ?>

Few Suggestions

Using PHP as a template engine doesn’t mean you have to write messy or “spaghetti” code. Follow these principles for clean, maintainable views:

Keep business logic out of templates.

Use Partials and Components

Leverage Layouts

Minimize Inline PHP

Name Variables Clearly

Escape Output Where Needed