iviphp / view
View discovery and PHP template rendering for the IviPHP ecosystem.
Requires
- php: >=8.2
- iviphp/support: ^0.1
This package is not auto-updated.
Last update: 2026-07-22 01:41:05 UTC
README
View discovery and native PHP template rendering for the IviPHP ecosystem.
Overview
iviphp/view provides a small, framework-independent view layer for server-rendered PHP applications.
It includes:
- named view discovery;
- dot-notation view names;
- multiple template directories;
- native PHP template rendering;
- isolated template execution;
- output buffering;
- shared template data;
- configurable template extensions;
- cached view-path resolution;
- strict view-name validation;
- strict template-variable validation;
- safe rendering exceptions;
- no global static view state.
The package focuses on server-rendered PHP views. SPA asset management, frontend bundling, hydration and client-side routing belong in separate integration packages.
Installation
composer require iviphp/view
Requirements
- PHP 8.2 or later
- Composer
iviphp/support
Package structure
src/
├── Engines/
│ └── PhpEngine.php
├── Exceptions/
│ └── ViewException.php
├── View.php
├── ViewFinder.php
├── ViewInterface.php
└── ViewManager.php
Basic usage
Create a view directory:
resources/
└── views/
└── home.php
Create the template:
<!-- resources/views/home.php --> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title><?= htmlspecialchars($title, ENT_QUOTES, 'UTF-8') ?></title> </head> <body> <h1><?= htmlspecialchars($heading, ENT_QUOTES, 'UTF-8') ?></h1> </body> </html>
Configure the view service:
<?php declare(strict_types=1); use Ivi\View\View; $view = View::fromPaths([ __DIR__ . '/resources/views', ]);
Render the template:
$html = $view->render( 'home', [ 'title' => 'IviPHP', 'heading' => 'Welcome to IviPHP', ] );
The rendered output is returned as a string.
The package does not send output directly to the client.
Configuration
Create a view instance from configuration:
$view = View::fromConfig([ 'paths' => [ __DIR__ . '/resources/views', ], 'extension' => 'php', 'shared' => [ 'applicationName' => 'IviPHP', ], ]);
Supported configuration fields:
| Field | Required | Default | Description |
|---|---|---|---|
paths |
yes | none | Template directories |
extension |
no | php |
Template file extension |
shared |
no | [] |
Variables shared with every view |
At least one readable view directory is required.
View names
Views are referenced using dot notation.
$view->render('home'); $view->render('users.profile'); $view->render('admin.dashboard.index');
These names resolve to:
home.php
users/profile.php
admin/dashboard/index.php
A view name may contain:
- letters;
- numbers;
- underscores;
- hyphens;
- dots separating path segments.
Valid view names:
home
users.profile
admin.dashboard
components.alert-box
errors.404
Invalid view names:
../home
users/profile
users\profile
.users
users.
users..profile
Directory traversal and direct filesystem paths are rejected.
Checking whether a view exists
if ($view->exists('users.profile')) { $html = $view->render( 'users.profile', [ 'user' => $user, ] ); }
exists() returns false when the named view cannot be found.
Invalid view names still produce a ViewException.
Rendering views
$html = $view->render( 'users.profile', [ 'user' => [ 'id' => 42, 'name' => 'Gaspard', ], ] );
Inside resources/views/users/profile.php:
<article>
<h1>
<?= htmlspecialchars(
$user['name'],
ENT_QUOTES,
'UTF-8'
) ?>
</h1>
<p>User ID: <?= (int) $user['id'] ?></p>
</article>
The make() alias
make() is an alias of render():
$html = $view->make( 'users.profile', [ 'user' => $user, ] );
Template variables
Associative array keys become local variables inside the PHP template.
$html = $view->render( 'article', [ 'title' => 'Building with IviPHP', 'content' => 'A small framework can still be powerful.', 'published' => true, ] );
Inside the template:
<article>
<h1>
<?= htmlspecialchars(
$title,
ENT_QUOTES,
'UTF-8'
) ?>
</h1>
<p>
<?= htmlspecialchars(
$content,
ENT_QUOTES,
'UTF-8'
) ?>
</p>
<?php if ($published): ?>
<span>Published</span>
<?php endif; ?>
</article>
Template variable names must be valid PHP variable identifiers.
Valid names:
user
currentUser
application_name
pageTitle
Invalid names:
user-name
page.title
42user
Reserved template variables
The rendering engine rejects variables that could interfere with PHP internals or the rendering scope.
Reserved names include:
GLOBALS
_SERVER
_GET
_POST
_FILES
_COOKIE
_SESSION
_REQUEST
_ENV
this
Variables beginning with __ivi are also reserved.
$view->render( 'home', [ '__iviPath' => 'invalid', ] );
This produces a ViewException.
Escaping output
The view engine does not automatically escape template values.
Escape untrusted HTML output explicitly:
<?= htmlspecialchars( $userInput, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8' ) ?>
Integer values can be cast:
<?= (int) $userId ?>
JSON data can be encoded safely:
<script>
window.applicationData = <?= json_encode(
$applicationData,
JSON_THROW_ON_ERROR
| JSON_HEX_TAG
| JSON_HEX_AMP
| JSON_HEX_APOS
| JSON_HEX_QUOT
) ?>;
</script>
Shared view data
Share a variable with every rendered view:
$view->share( 'applicationName', 'IviPHP' );
The variable becomes available in every template:
<title>
<?= htmlspecialchars(
$applicationName,
ENT_QUOTES,
'UTF-8'
) ?>
</title>
Share multiple values:
$view->shareMany([ 'applicationName' => 'IviPHP', 'environment' => 'production', 'currentYear' => 2026, ]);
Overriding shared data
Data passed directly to render() overrides shared data with the same name.
$view->share( 'title', 'Default title' ); $html = $view->render( 'home', [ 'title' => 'Home page', ] );
The template receives:
Home page
Inspecting shared data
Check whether a shared variable exists:
$view->hasShared('applicationName');
Retrieve a shared variable:
$name = $view->shared( 'applicationName', 'Application' );
Return every shared variable:
$data = $view->allShared();
Removing shared data
Remove one shared variable:
$view->forgetShared('environment');
Remove all shared variables:
$view->flushShared();
Multiple view directories
$view = View::fromPaths([ __DIR__ . '/resources/views', __DIR__ . '/modules/blog/views', ]);
Directories are searched in declaration order.
When the same named view exists in multiple directories, the first matching file is used.
Example:
resources/views/layouts/app.php
modules/blog/views/layouts/app.php
With this configuration:
$view = View::fromPaths([ __DIR__ . '/resources/views', __DIR__ . '/modules/blog/views', ]);
layouts.app resolves to:
resources/views/layouts/app.php
Adding view directories
$view->addPath( __DIR__ . '/modules/shop/views' );
The new path is appended to the search list.
Prepend a path so it has higher priority:
$view->prependPath( __DIR__ . '/themes/custom/views' );
Equivalent:
$view->addPath( __DIR__ . '/themes/custom/views', true );
Removing view directories
$view->removePath( __DIR__ . '/modules/shop/views' );
Removing a missing or inaccessible path has no effect.
Replacing all view directories
$view->setPaths([ __DIR__ . '/new/views', __DIR__ . '/shared/views', ]);
All cached view-path resolutions are cleared automatically.
Inspecting view directories
$paths = $view->paths();
The returned paths are canonical absolute filesystem paths.
Resolving a view path
$path = $view->path('users.profile');
Example result:
/var/www/application/resources/views/users/profile.php
A ViewException is thrown when the view cannot be found.
View path caching
Resolved view paths are cached in memory.
$view->render('home'); $view->render('home');
The second render can reuse the previously resolved path.
The template file is still executed again. Only filesystem discovery is cached.
Forgetting one resolved path
$view->forget('home');
The next render searches the configured directories again.
Clearing all resolved paths
$view->flush();
This clears cached view-path resolutions.
It does not remove shared data or template files.
Custom template extension
The default extension is .php.
A different extension can be configured:
$view = View::fromPaths( paths: [ __DIR__ . '/resources/views', ], extension: 'phtml' );
A view named:
users.profile
then resolves to:
users/profile.phtml
However, the included PhpEngine only renders files ending in .php.
For the initial package, use the default php extension with PhpEngine.
Additional template engines can be introduced in later versions.
View manager
Create a ViewFinder:
use Ivi\View\ViewFinder; $finder = new ViewFinder([ __DIR__ . '/resources/views', ]);
Create a manager:
use Ivi\View\ViewManager; $manager = new ViewManager($finder);
Render a view:
$html = $manager->render( 'home', [ 'title' => 'IviPHP', ] );
Create a manager directly from paths:
$manager = ViewManager::fromPaths( paths: [ __DIR__ . '/resources/views', ], sharedData: [ 'applicationName' => 'IviPHP', ] );
Creating the main view entry point
use Ivi\View\View; $view = new View($manager);
The View class delegates its operations to ViewManager.
View finder
Use ViewFinder directly:
use Ivi\View\ViewFinder; $finder = new ViewFinder([ __DIR__ . '/resources/views', ]); $path = $finder->find('users.profile');
Check existence:
$exists = $finder->exists('users.profile');
Return the generated relative path:
$relativePath = $finder->relativePath( 'users.profile' );
Result:
users/profile.php
Finder extension
Return the configured extension:
$extension = $finder->extension();
Change it:
$finder->setExtension('php');
Changing the extension clears all cached path resolutions.
PHP rendering engine
Use the engine directly:
use Ivi\View\Engines\PhpEngine; $engine = new PhpEngine(); $html = $engine->render( view: 'home', path: __DIR__ . '/resources/views/home.php', data: [ 'title' => 'IviPHP', ] );
The engine:
- validates the view name;
- validates the template path;
- validates template variable names;
- opens an output buffer;
- executes the template inside an isolated closure;
- captures the generated output;
- restores the output-buffer state;
- returns the rendered output.
Output buffering
Template output is captured automatically:
<h1>Hello</h1>
Rendering returns:
<h1>Hello</h1>
The engine does not call echo outside the template and does not write directly to the HTTP response.
Output-buffer protection
Templates must not leave the output-buffer stack in an inconsistent state.
Avoid manually opening or closing output buffers inside templates:
<?php ob_start(); ?>
The engine detects unexpected output-buffer changes and produces a ViewException.
Template includes
A template can include another trusted PHP file:
<?php include __DIR__ . '/../partials/header.php'; ?> <main> <h1> <?= htmlspecialchars( $title, ENT_QUOTES, 'UTF-8' ) ?> </h1> </main> <?php include __DIR__ . '/../partials/footer.php'; ?>
Direct includes are normal PHP behavior.
The included file is not resolved through ViewFinder.
For reusable application views, rendering named templates through View or ViewManager is preferred.
Simple layout example
Directory structure:
resources/views/
├── layouts/
│ └── app.php
└── pages/
└── home.php
resources/views/pages/home.php:
<section>
<h1>
<?= htmlspecialchars(
$heading,
ENT_QUOTES,
'UTF-8'
) ?>
</h1>
<p>
<?= htmlspecialchars(
$message,
ENT_QUOTES,
'UTF-8'
) ?>
</p>
</section>
Render the page content:
$content = $view->render( 'pages.home', [ 'heading' => 'Welcome', 'message' => 'Your application is ready.', ] );
Render the layout:
$html = $view->render( 'layouts.app', [ 'title' => 'Home', 'content' => $content, ] );
resources/views/layouts/app.php:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title> <?= htmlspecialchars( $title, ENT_QUOTES, 'UTF-8' ) ?> </title> </head> <body> <?= $content ?> </body> </html>
Only render trusted HTML content without escaping.
Component example
Create:
resources/views/components/alert.php
Template:
<div class="alert alert-<?= htmlspecialchars( $type, ENT_QUOTES, 'UTF-8' ) ?>"> <?= htmlspecialchars( $message, ENT_QUOTES, 'UTF-8' ) ?> </div>
Render:
$alert = $view->render( 'components.alert', [ 'type' => 'success', 'message' => 'The operation completed successfully.', ] );
Rendering collections
$html = $view->render( 'users.index', [ 'users' => [ [ 'id' => 1, 'name' => 'Alice', ], [ 'id' => 2, 'name' => 'Bob', ], ], ] );
Template:
<ul>
<?php foreach ($users as $user): ?>
<li>
<?= htmlspecialchars(
$user['name'],
ENT_QUOTES,
'UTF-8'
) ?>
</li>
<?php endforeach; ?>
</ul>
Conditional rendering
<?php if ($user !== null): ?> <p> Welcome, <?= htmlspecialchars( $user['name'], ENT_QUOTES, 'UTF-8' ) ?> </p> <?php else: ?> <a href="/login">Sign in</a> <?php endif; ?>
Templates are regular PHP files and support normal PHP syntax.
View exceptions
View failures throw:
Ivi\View\Exceptions\ViewException
Example:
use Ivi\View\Exceptions\ViewException; try { $html = $view->render( 'users.profile', [ 'user' => $user, ] ); } catch (ViewException $exception) { error_log( json_encode([ 'message' => $exception->getMessage(), 'context' => $exception->context(), ]) ); }
Safe exception context
ViewException provides diagnostic context without including template values.
$context = $exception->context();
Example:
[
'view' => 'users.profile',
'path' => '/var/www/application/resources/views/users/profile.php',
]
Invalid data failures only include variable names:
[
'view' => 'users.profile',
'variables' => [
'user',
'pageTitle',
],
]
Template values are never added to exception context.
Exception factories
use Ivi\View\Exceptions\ViewException; ViewException::invalidViewName( $view, $reason ); ViewException::viewNotFound( $view, $paths ); ViewException::invalidPath( $path, $reason ); ViewException::directoryUnavailable( $directory ); ViewException::viewUnreadable( $view, $path ); ViewException::renderFailed( $view, $path, $previous ); ViewException::invalidData( $view, $variables, $reason ); ViewException::unsupportedEngine( $engine ); ViewException::invalidConfiguration( $engine, $reason );
Error handling
use Ivi\View\Exceptions\ViewException; try { $html = $view->render( 'dashboard.index', [ 'user' => $currentUser, 'statistics' => $statistics, ] ); } catch (ViewException $exception) { error_log( json_encode([ 'message' => $exception->getMessage(), 'context' => $exception->context(), ]) ); $html = '<h1>Unable to render this page.</h1>'; }
Do not expose filesystem paths or internal rendering errors directly to application users in production.
Complete example
Project structure:
application/
├── public/
│ └── index.php
└── resources/
└── views/
├── layouts/
│ └── app.php
└── pages/
└── home.php
public/index.php:
<?php declare(strict_types=1); use Ivi\View\Exceptions\ViewException; use Ivi\View\View; require dirname(__DIR__) . '/vendor/autoload.php'; $view = View::fromConfig([ 'paths' => [ dirname(__DIR__) . '/resources/views', ], 'shared' => [ 'applicationName' => 'IviPHP', 'currentYear' => 2026, ], ]); try { $content = $view->render( 'pages.home', [ 'heading' => 'Modern PHP, kept simple.', 'message' => 'Build your application from focused packages.', ] ); $html = $view->render( 'layouts.app', [ 'title' => 'Home', 'content' => $content, ] ); echo $html; } catch (ViewException $exception) { error_log( json_encode([ 'message' => $exception->getMessage(), 'context' => $exception->context(), ]) ); http_response_code(500); echo 'Unable to render the page.'; }
resources/views/pages/home.php:
<section>
<h1>
<?= htmlspecialchars(
$heading,
ENT_QUOTES,
'UTF-8'
) ?>
</h1>
<p>
<?= htmlspecialchars(
$message,
ENT_QUOTES,
'UTF-8'
) ?>
</p>
</section>
resources/views/layouts/app.php:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1" > <title> <?= htmlspecialchars( $title, ENT_QUOTES, 'UTF-8' ) ?> · <?= htmlspecialchars( $applicationName, ENT_QUOTES, 'UTF-8' ) ?> </title> </head> <body> <header> <strong> <?= htmlspecialchars( $applicationName, ENT_QUOTES, 'UTF-8' ) ?> </strong> </header> <main> <?= $content ?> </main> <footer> © <?= (int) $currentYear ?> </footer> </body> </html>
Available classes
ViewInterface
$view->exists($name); $view->render( $name, $data );
View
View::fromPaths( $paths, $sharedData, $extension ); View::fromConfig( $configuration ); $view->manager(); $view->finder(); $view->exists($name); $view->path($name); $view->render( $name, $data ); $view->make( $name, $data ); $view->share( $name, $value ); $view->shareMany($data); $view->hasShared($name); $view->shared( $name, $default ); $view->allShared(); $view->forgetShared($name); $view->flushShared(); $view->addPath( $path, $prepend ); $view->prependPath($path); $view->removePath($path); $view->setPaths($paths); $view->paths(); $view->forget($name); $view->flush();
ViewManager
ViewManager::fromPaths( $paths, $sharedData, $extension ); $manager->finder(); $manager->engine(); $manager->exists($name); $manager->path($name); $manager->render( $name, $data ); $manager->make( $name, $data ); $manager->share( $name, $value ); $manager->shareMany($data); $manager->hasShared($name); $manager->shared( $name, $default ); $manager->allShared(); $manager->forgetShared($name); $manager->flushShared(); $manager->addPath( $path, $prepend ); $manager->prependPath($path); $manager->removePath($path); $manager->setPaths($paths); $manager->paths(); $manager->forget($name); $manager->flush();
ViewFinder
$finder->addPath( $path, $prepend ); $finder->prependPath($path); $finder->removePath($path); $finder->setPaths($paths); $finder->paths(); $finder->extension(); $finder->setExtension($extension); $finder->exists($view); $finder->find($view); $finder->relativePath($view); $finder->forget($view); $finder->flush();
PhpEngine
$engine->render( $view, $path, $data );
ViewException
$exception->getMessage(); $exception->context(); $exception->getPrevious();
Security considerations
Escape untrusted output
The package does not automatically escape values.
Always escape untrusted content before inserting it into HTML:
<?= htmlspecialchars( $value, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8' ) ?>
Do not expose arbitrary template selection
Do not render a view name directly from user input:
$view->render($_GET['view']);
Even though names are validated, template selection should remain controlled by the application.
Use an allowlist:
$pages = [ 'home' => 'pages.home', 'about' => 'pages.about', ]; $page = $_GET['page'] ?? 'home'; $template = $pages[$page] ?? 'errors.404'; $html = $view->render($template);
Keep template directories trusted
Templates are executable PHP files.
Only trusted application code should be stored in view directories.
Do not allow users to upload PHP files into these directories.
Protect filesystem paths
Do not return view exception paths directly to users in production.
Filesystem paths may reveal internal application structure.
Escape attributes separately
Use HTML escaping inside attributes:
<a href="<?= htmlspecialchars( $url, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8' ) ?>"> Open </a>
Trusted rendered fragments
A rendered child view may be inserted into a layout without escaping:
<?= $content ?>
Only do this when $content was generated by trusted application templates.
SPA integration
This package focuses on server-rendered PHP templates.
A future SPA integration package can provide:
- Vite manifest discovery;
- JavaScript and CSS asset helpers;
- development-server detection;
- frontend entry-point resolution;
- preload tags;
- hydration data;
- frontend route fallback;
- Vue, React or Svelte integration;
- server-side rendering bridges.
Those features should remain separate from the core view package so applications can use native PHP views without installing frontend tooling.
A PHP view can already host a SPA entry point:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1" > <title> <?= htmlspecialchars( $title, ENT_QUOTES, 'UTF-8' ) ?> </title> </head> <body> <div id="app"></div> <script type="module" src="/assets/application.js" ></script> </body> </html>
Asset discovery and build-tool integration can be added later through a dedicated package.
Design principles
- Small framework-independent API
- Native PHP templates
- Dot-notation view names
- Multiple ordered view directories
- Strict path validation
- Strict variable-name validation
- Isolated template execution
- Captured template output
- Shared application data
- Cached view discovery
- No hidden global view state
- No automatic output escaping
- No automatic HTTP response emission
- Safe rendering diagnostics
- Clear separation from frontend tooling
Package boundaries
This package is responsible for:
- named view discovery;
- template directory management;
- native PHP rendering;
- output capture;
- shared template data;
- view-path caching;
- rendering exceptions.
It is not responsible for:
- HTTP responses;
- routing;
- controllers;
- frontend bundling;
- SPA routing;
- Vite integration;
- JavaScript compilation;
- CSS compilation;
- server-side JavaScript rendering;
- automatic HTML escaping;
- authentication;
- authorization;
- application bootstrapping.
These responsibilities belong to other IviPHP packages or application code.
Current limitations
The initial release does not include:
- template inheritance syntax;
- template directives;
- automatic layouts;
- components as PHP objects;
- view composers;
- namespaces;
- package view registration;
- compiled templates;
- automatic HTML escaping;
- frontend asset manifests;
- Vite integration;
- SPA hydration;
- React, Vue or Svelte adapters;
- server-side JavaScript rendering.
The package intentionally starts with a small native PHP rendering foundation.
Ecosystem
This package is part of the IviPHP ecosystem:
iviphp/contractsiviphp/supportiviphp/configiviphp/containeriviphp/httpiviphp/validationiviphp/databaseiviphp/cacheiviphp/viewiviphp/authiviphp/framework
Contributing
Contributions should preserve the focused and secure design of the package.
Changes should:
- preserve strict view-name validation;
- prevent directory traversal;
- preserve isolated PHP rendering;
- preserve output-buffer restoration;
- avoid exposing template data in exceptions;
- avoid automatic HTTP output;
- remain independent from the full framework;
- avoid hidden global state;
- keep frontend tooling outside the core package;
- support PHP 8.2 and later.
Security
Please report security vulnerabilities privately to the Softadastra maintainers instead of opening a public issue.
License
This project is licensed under the MIT License.
Maintainer
Created and maintained by Softadastra.