Views
ZephyrPHP uses Twig as its templating engine, providing a clean, secure, and flexible way to build your HTML views with powerful features like composers, shared data, and components.
Creating Views
Views are stored in the pages/ directory with a .twig extension:
pages/
├── layouts/
│ └── app.twig
├── components/
│ ├── button.twig
│ └── alert.twig
├── users/
│ ├── index.twig
│ └── show.twig
└── home.twig
Rendering Views
Use the render() method in your controllers:
<?php
class UserController extends Controller
{
public function index()
{
return $this->render('users/index', [
'users' => User::query()->all(),
'title' => 'All Users'
]);
}
}
Using the view() Helper
// Simple render
return view('users/index', ['users' => $users]);
// Dot notation
return view('users.index', ['users' => $users]);
Fluent View Builder
use ZephyrPHP\View\View;
return View::getInstance()
->make('users.show')
->with('user', $user)
->with('posts', $user->posts())
->render();
// Magic methods
return View::getInstance()
->make('dashboard')
->withUser($user)
->withStats($stats)
->render();
Displaying Data
Output variables using double curly braces:
<h1>{{ title }}</h1>
<p>Welcome, {{ user.name }}!</p>
{# Output is automatically escaped for security #}
<p>{{ user_input }}</p>
{# To output raw HTML (use carefully!) #}
{{ raw_html|raw }}
Control Structures
Conditionals
{% if user %}
<p>Hello, {{ user.name }}!</p>
{% else %}
<p>Please log in.</p>
{% endif %}
{% if user.role == 'admin' %}
<a href="/admin">Admin Panel</a>
{% elseif user.role == 'editor' %}
<a href="/editor">Editor Panel</a>
{% endif %}
Loops
<ul>
{% for user in users %}
<li>{{ user.name }} - {{ user.email }}</li>
{% else %}
<li>No users found.</li>
{% endfor %}
</ul>
{# Loop with index #}
{% for user in users %}
<p>{{ loop.index }}. {{ user.name }}</p>
{% endfor %}
{# Loop variables #}
{{ loop.index }} {# Current iteration (1-indexed) #}
{{ loop.index0 }} {# Current iteration (0-indexed) #}
{{ loop.first }} {# True if first iteration #}
{{ loop.last }} {# True if last iteration #}
{{ loop.length }} {# Total number of items #}
Template Inheritance
Create a base layout and extend it in child templates:
<!DOCTYPE html>
<html>
<head>
<title>{% block title %}ZephyrPHP{% endblock %}</title>
<link rel="stylesheet" href="{{ asset('css/styles.css') }}">
{% block styles %}{% endblock %}
</head>
<body>
<nav>
{% include 'partials/nav.twig' %}
</nav>
<main>
{% block content %}{% endblock %}
</main>
<footer>
© {{ "now"|date("Y") }} ZephyrPHP
</footer>
{% block scripts %}{% endblock %}
</body>
</html>
{% extends 'layouts/app.twig' %}
{% block title %}Users - ZephyrPHP{% endblock %}
{% block content %}
<h1>Users</h1>
<ul>
{% for user in users %}
<li>{{ user.name }}</li>
{% endfor %}
</ul>
{% endblock %}
View Composers
View composers automatically inject data into specific views, eliminating repetitive code in controllers.
Registering Composers
<?php
use ZephyrPHP\View\View;
// In a service provider or bootstrap file
// Inject data into specific views
View::composer('layouts/app', function($view) {
$view->with('siteTitle', 'My Application');
$view->with('currentUser', auth()->user());
});
// Multiple views
View::composer(['layouts/app', 'layouts/admin'], function($view) {
$view->with('notifications', Notification::unread());
});
// Wildcard patterns
View::composer('admin.*', function($view) {
$view->with('adminStats', Stats::getAdminStats());
});
View::composer('users.*', function($view) {
$view->with('userCount', User::count());
});
Class-Based Composers
<?php
namespace App\Composers;
use ZephyrPHP\View\ViewContext;
class NavigationComposer
{
public function compose(ViewContext $view)
{
$view->with('menuItems', $this->getMenuItems());
$view->with('currentRoute', Route::currentRouteName());
}
private function getMenuItems(): array
{
return [
['label' => 'Home', 'route' => 'home'],
['label' => 'About', 'route' => 'about'],
['label' => 'Contact', 'route' => 'contact'],
];
}
}
// Register the class
View::composer('partials/nav', NavigationComposer::class);
Shared Data
Share data with all views globally:
<?php
use ZephyrPHP\View\View;
// Share a single value
View::share('appName', 'My Application');
View::share('currentYear', date('Y'));
// Share multiple values
View::share([
'appName' => 'My Application',
'appVersion' => '1.0.0',
'user' => auth()->user(),
]);
// Access in any template
// -
Components
Create reusable UI components:
<button
type="{{ type|default('button') }}"
class="btn btn-{{ variant|default('primary') }} {{ class|default('') }}"
{{ disabled ? 'disabled' : '' }}
>
{{ slot }}
</button>
<div class="alert alert-{{ type|default('info') }}" role="alert">
{% if title %}
<strong>{{ title }}</strong>
{% endif %}
{{ slot }}
</div>
Using Components
{# Using the component function #}
{{ component('button', {variant: 'primary', type: 'submit'}, {default: 'Save Changes'}) }}
{{ component('alert', {type: 'success', title: 'Success!'}, {default: 'Your changes have been saved.'}) }}
{# Or using include with the components path #}
{% include 'components/button.twig' with {
variant: 'danger',
slot: 'Delete'
} %}
Including Templates
Include partial templates:
{# Simple include #}
{% include 'partials/header.twig' %}
{# Include with variables #}
{% include 'partials/user-card.twig' with {'user': user} %}
{# Include if exists #}
{% include 'partials/sidebar.twig' ignore missing %}
{# Include with only specific variables #}
{% include 'partials/user-card.twig' with {'user': user} only %}
View Existence
Check if views exist before rendering:
<?php
use ZephyrPHP\View\View;
$view = View::getInstance();
// Check if template exists
if ($view->exists('emails/welcome')) {
return $view->render('emails/welcome', $data);
}
// Render if exists, otherwise return default
$content = $view->renderIfExists('partials/sidebar', [], '');
// Use first existing template
return $view->first([
"users/{$type}/show",
'users/show',
'errors/404'
], $data);
Rendering Collections
Render a template for each item in a collection:
<?php
use ZephyrPHP\View\View;
// Render each user
$html = View::getInstance()->each(
'partials/user-card', // Template
$users, // Data array
'user', // Variable name for each item
'partials/no-users' // Optional empty template
);
echo $html;
Built-in Functions
ZephyrPHP provides many helper functions in templates:
Asset URLs
<link rel="stylesheet" href="{{ asset('css/styles.css') }}">
<script src="{{ asset('js/app.js') }}"></script>
<img src="{{ asset('images/logo.png') }}">
Route Generation
<a href="{{ route('users.index') }}">All Users</a>
<a href="{{ route('users.show', {id: user.id}) }}">View User</a>
{# Check current route #}
{% if route_is('users.*') %}
<span class="active">Users Section</span>
{% endif %}
{# Get current route name #}
{% set currentRoute = current_route() %}
CSRF Protection
<form method="POST">
{{ csrf_field()|raw }}
<!-- form fields -->
</form>
{# Or just the token value #}
<input type="hidden" name="_token" value="{{ csrf_token() }}">
Session & Flash Messages
{# Get session value #}
{{ session('user_id') }}
{# Flash messages #}
{% if flash('success') %}
<div class="alert alert-success">
{{ flash('success') }}
</div>
{% endif %}
{% if flash('error') %}
<div class="alert alert-danger">
{{ flash('error') }}
</div>
{% endif %}
Old Input
<input type="text" name="email" value="{{ old('email') }}">
<input type="text" name="name" value="{{ old('name', user.name) }}">
Request Data
{# Access request data #}
{{ request('search') }}
{{ request('page', 1) }}
Conditional Classes
<div class="{{ class_list({
'card': true,
'card-active': isActive,
'card-featured': isFeatured
}) }}">
Content
</div>
Filters
Twig filters transform data:
String Filters
{{ name|upper }} {# JOHN #}
{{ name|lower }} {# john #}
{{ name|capitalize }} {# John #}
{{ name|title }} {# John Doe #}
{{ text|striptags }} {# Remove HTML #}
{{ text|nl2br }} {# Convert newlines to <br> #}
{{ text|trim }} {# Trim whitespace #}
{{ text|limit(100) }} {# Truncate to 100 chars... #}
{{ text|slug }} {# my-text-here #}
Array Filters
{{ users|length }} {# Count items #}
{{ users|first }} {# First item #}
{{ users|last }} {# Last item #}
{{ users|join(', ') }} {# Join with separator #}
Date Filters
{{ post.created_at|date('F j, Y') }} {# January 1, 2024 #}
{{ post.created_at|date('Y-m-d H:i:s') }} {# 2024-01-01 12:00:00 #}
{{ post.created_at|relative_time }} {# 2 hours ago #}
Number Filters
{{ price|number_format(2, '.', ',') }} {# 1,234.56 #}
{{ price|money }} {# $1,234.00 #}
{{ price|money('€') }} {# €1,234.00 #}
{{ fileSize|bytes }} {# 1.5 MB #}
Default Values
{{ user.nickname|default('Anonymous') }}
Template Namespaces
Organize templates with namespaces:
<?php
use ZephyrPHP\View\View;
$view = View::getInstance();
// Add a namespace
$view->addNamespace('admin', BASE_PATH . '/pages/admin');
$view->addNamespace('emails', BASE_PATH . '/pages/emails');
// Use namespace in templates
return $view->render('admin::dashboard', $data);
return $view->render('emails::welcome', $data);
Comments
{# This is a comment and won't appear in the HTML output #}
{#
Multi-line
comment
#}
Debugging
{# Dump a variable for debugging #}
{{ dump(user) }}
{# Dump and die #}
{{ dd(user) }}
Clearing View Cache
<?php
use ZephyrPHP\View\View;
// Clear compiled templates
View::getInstance()->clearCompiled();
// Via CLI
php craftsman view:clear
Twig automatically escapes output to prevent XSS attacks. Only use |raw filter when you're certain the content is safe.
Complete Example
<?php
namespace App\Providers;
use ZephyrPHP\View\View;
use ZephyrPHP\Container\Container;
use ZephyrPHP\Container\ServiceProvider;
class ViewServiceProvider extends ServiceProvider
{
public function boot(Container $container): void
{
// Share global data
View::share('appName', env('APP_NAME', 'ZephyrPHP'));
View::share('currentYear', date('Y'));
// Register composers
View::composer('layouts.*', function($view) {
$view->with('user', auth()->user());
$view->with('notifications', Notification::recent());
});
View::composer('admin.*', AdminComposer::class);
}
}