PHP Application Driver - An Inversion of Control Template Engine
PAD is a PHP template engine that inverts the traditional web application architecture. Instead of PHP code including templates, PAD templates drive the execution flow, seamlessly integrating data access, control structures, and presentation in a unified template syntax.
Requirements: PHP 8.0+
Traditional PHP frameworks follow a “code-first” approach where PHP scripts control the flow and include template fragments. PAD inverts this paradigm: templates are first-class citizens that orchestrate everything from data retrieval to output generation. This “template-first” approach creates a natural separation where the visual structure of the application mirrors its logical structure.
{tags}Every page consists of two files that PAD automatically pairs:
pagename.php - Returns data (variables, arrays)pagename.pad - Template that renders the dataPages are accessed via query string:
/myapp/ → index.pad/myapp/?about → about.pad/myapp/?admin/users → admin/users.padPAD uses a hierarchical directory structure for inheritance:
APP/
├── _inits.pad # Initialization template (wraps all pages)
├── _inits.php # Initialization code (runs before pages)
├── _exits.pad # Exit template (wraps all pages)
├── _exits.php # Exit code (runs after pages)
├── _lib/ # Library files (auto-included)
│ └── helpers.php
├── index.pad # Homepage template
├── index.php # Homepage data
└── admin/
├── _inits.pad # Admin section wrapper
├── dashboard.pad # Inherits from parent _inits.pad
└── dashboard.php
Child directories automatically inherit parent templates. The _inits.pad files wrap content like layouts.
APP - Path to your application directory (must end with /)DAT - Path to your data directory (must end with /)<?php
include __DIR__ . '/../padHome.php';
define('APP', "$padHome/apps/myapp/");
define('DAT', "$padHome/DATA/");
include "$padHome/pad/pad.php";
?>
index.php:
<?php
$message = 'Hello World!';
$items = ['Apple', 'Banana', 'Cherry'];
?>
index.pad:
<html>
<head><title>Hello</title></head>
<body>
<h1>{$message}</h1>
<ul>
{items}
<li>{$items}</li>
{/items}
</ul>
</body>
</html>
{$simple} {# Simple variable #}
{$user.name} {# Object/array property #}
{$items[0]} {# Array index #}
{!text} {# HTML-escaped output #}
{$value | default('N/A')} {# Default value #}
Transform values with pipe functions:
{echo $name | upper} {# Uppercase #}
{echo $text | trim | lower} {# Chain multiple functions #}
{echo $date | date('Y-m-d')} {# With parameters #}
{echo $value | + 1} {# Arithmetic (space required) #}
{echo $text | after('@')} {# String manipulation #}
{echo $html | html} {# Escape HTML #}
{echo $name | contains('admin')} {# String contains #}
Important: Pipes require {echo} - bare {$var | func} won’t work.
Common functions: trim, upper, lower, html, url, date, replace, left, right, contains, in, between, exists
Conditionals:
{if $condition}
Content if true
{elseif $other}
Alternative
{else}
Default
{/if}
{case $status}
{when 'active'}Active{/when}
{when 'pending'}Pending{/when}
{else}Unknown{/else}
{/case}
Loops:
{users} {# Iterate data tag #}
<li>{$name} - {$email}</li>
{/users}
{while $i le 10} {# While loop #}
Item {$i}
{increment $i}
{/while}
{files dir="images" mask="*.jpg"}
<img src="{$path}">
{/files}
Loop Control:
{continue 'tagname'} # Skip to next iteration
{cease 'tagname'} # Soft stop (graceful end)
{break 'tagname'} # Hard stop (immediate exit)
Access iteration state with property@tag syntax:
{items}
{if first@items}<ul>{/if}
<li class="{if odd@items}odd{/if}">{$name}</li>
{if last@items}</ul>{/if}
{/items}
Properties: first@tag, last@tag, even@tag, odd@tag, current@tag, count@tag, remaining@tag, key@tag, data@tag
Control tag behavior with options:
{users sort="name" first="10"} {# Sort and limit #}
{products sort="price DESC" page="1" rows="20"}
{items shuffle first="5"} {# Random selection #}
{data content="users" toData="cached"} {# Data operations #}
{data 'colors'}
["red", "green", "blue"]
{/data}
{colors}{$colors} {/colors}
Supports JSON, XML, YAML, and CSV formats.
Resolve naming conflicts with explicit type prefixes:
{app:mytag} # App tag from _tags/
{pad:tagname} # Built-in PAD tag
{php:strlen(@)} # Call PHP function
{data:items} # Defined data block
{pull:mySequence} # Stored sequence
{field:"name from users"} # Database field
Create _tags/mytag.php in your app:
<?php
$format = $padPrm[$pad]['format'] ?? $padOpt[$pad][1] ?? 'default';
return "Output: $format";
?>
Use as {mytag 'value'} or {mytag format='value'}.
// In PHP
$user = db("RECORD * FROM users WHERE id={0}", [$id]);
$users = db("ARRAY * FROM users ORDER BY name");
$count = db("FIELD COUNT(*) FROM users");
$exists = db("CHECK users WHERE email='{0}'", [$email]);
// In templates
{field "count(*) from users"}
{table "SELECT * FROM users"}
<tr><td>{$name}</td></tr>
{/table}
{users table} {# Query users table #}
{$name} - {$email}
{/users}
{orders table where="user_id = $userId" sort="date DESC"}
Order #{$id}: {$total}
{/orders}
80+ mathematical sequences with transformations:
{fibonacci rows=10}{$fibonacci} {/fibonacci}
{sequence '1..10', push='nums'}
{resume add=5}
{pull:nums}{$sequence} {/pull:nums}
See sequences/ for complete documentation.
When a request arrives, PAD is initialized via pad.php:
Request → pad.php → config/config.php → start/enter/start.php
The entry point:
PAD constant (framework path)APP and DAT constantsThe build system assembles the complete page:
build/build.php
├── build/dirs.php → Create directory hierarchy
├── build/_lib.php → Collect library files
├── build/base.php → Build template structure
│ ├── _inits.pad files (outer to inner)
│ ├── @pad@ placeholder
│ └── _exits.pad files (inner to outer)
└── build/page.php → Process page
├── _inits.php execution
├── page.php execution → returns data
├── page.pad template
└── _exits.php execution
The @pad@ placeholder is replaced with the page content, creating a nested structure where parent templates wrap child content.
PAD processes templates through a level-based system. Each tag creates a new level:
{outer} ← Level 0
{inner} ← Level 1
{field} ← Level 2
{/inner}
{/outer}
The level processor (level/level.php):
{ and }When a tag is encountered, PAD determines its type:
| Prefix | Type | Example |
|---|---|---|
$ |
Variable | {$name} |
# |
Option | {#param} |
& |
Tag reference | {&tagname} |
@ |
Property | {first@tag} |
| (none) | Tag/Field | {users}, {if} |
Types are resolved in order: app → pad → data → content → field → tag
When a tag has data (array), PAD iterates through each item:
{users} ← Data: [{name: 'A'}, {name: 'B'}]
<li>{$name}</li> ← Occurrence 1: name = 'A'
← Occurrence 2: name = 'B'
{/users}
The occurrence system (occurrence/) manages:
current@tag)Expressions in templates are parsed and evaluated by the eval subsystem:
{$price * 1.1 | round(2)}
Evaluation pipeline:
Operator precedence: ! → ** * / % + - → . → comparisons → AND XOR OR
After processing, PAD generates the final output:
Template Processing → $padResult → $padOut → Output Buffer → Response
pad/
├── pad.php # Main entry point
│
├── start/ # Execution lifecycle
│ ├── enter/ # Entry points (page, code, ajax, redirect)
│ ├── start/ # Initialization phase
│ └── end/ # Termination phase
│
├── build/ # Page assembly
│ ├── build.php # Main build orchestrator
│ ├── base.php # Template structure
│ ├── page.php # Page processing
│ └── _lib.php # Library collection
│
├── level/ # Tag processing
│ ├── level.php # Main level processor
│ ├── setup.php # Level initialization
│ ├── parms/ # Parameter parsing
│ └── pipes/ # Pipe operations
│
├── occurrence/ # Data iteration
│ ├── occurrence.php # Main occurrence handler
│ ├── init.php # Occurrence initialization
│ ├── set.php # Variable setup
│ └── end.php # Occurrence finalization
│
├── walk/ # Tree walking
│ ├── next.php # Next iteration
│ └── end.php # Level completion
│
├── eval/ # Expression evaluation
│ ├── eval.php # Main evaluator
│ ├── parse.php # Expression parser
│ ├── actions/ # Operator actions
│ ├── go/ # Operator execution
│ └── single/ # Type resolvers
│
├── types/ # Tag type handlers (25+)
├── tags/ # Template tags (40+)
├── functions/ # Pipe functions (40+)
├── options/ # Tag options (50+)
├── handling/ # Data handling (15 handlers)
├── properties/ # Tag properties (25 properties)
├── constructs/ # Special constructs (7 constructs)
│
├── lib/ # PHP library
├── config/ # Configuration
├── database/ # Database layer
├── cache/ # Caching system
├── error/ # Error handling
├── events/ # Event system
├── info/ # Debug/profiling
└── inits/ # Initialization
┌─────────────────────────────────────────────────────────────────┐
│ REQUEST │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ pad.php │
│ ├── Define PAD constant │
│ ├── Validate APP/DAT │
│ └── Include config & start │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ start/ │
│ ├── Initialize globals │
│ ├── Set up stores │
│ └── Begin execution │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ build/ │
│ ├── Collect _lib files │
│ ├── Build base structure (_inits.pad + @pad@ + _exits.pad) │
│ ├── Execute _inits.php │
│ ├── Execute page.php → get data │
│ ├── Load page.pad template │
│ └── Replace @pad@ with content │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ level/ + occurrence/ + walk/ │
│ ├── Find { } tag delimiters │
│ ├── Parse tag name, parameters, options │
│ ├── Detect type and load handler │
│ ├── For data tags: iterate occurrences │
│ ├── Process child content recursively │
│ └── Collect output │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ eval/ │
│ ├── Parse expressions │
│ ├── Resolve variables │
│ ├── Execute operators │
│ └── Apply pipe functions │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ RESPONSE │
└─────────────────────────────────────────────────────────────────┘
apps/myapp/
├── index.php / index.pad # Page pair
├── _inits.php # Runs before all pages
├── _inits.pad # Wraps all pages (use @pad@ placeholder)
├── _exits.php / _exits.pad # Runs after all pages
├── _lib/ # Auto-included PHP functions
├── _tags/ # Custom template tags
├── _functions/ # Custom pipe functions
├── _data/ # Static data files (JSON, XML)
├── _include/ # Template snippets
├── _callbacks/ # Iteration callbacks
├── _options/ # Custom tag options
└── _config/config.php # App configuration
Built-in caching system for performance:
{expensive_query cache="3600"} {# Cache for 1 hour #}
...
{/expensive_query}
Handle AJAX requests seamlessly:
{ajax}
{# Content returned as AJAX response #}
{/ajax}
Execute code in isolation:
{sandbox}
{# Isolated execution environment #}
{/sandbox}
Hook into framework events for debugging and customization:
Execute PHP code at specific points:
{data callback="myFunction"}
{# myFunction called for each row #}
{/data}
In _config/config.php or pad/config/config.php:
$padErrorAction // 'pad', 'boot', 'php', 'stop', 'exit', 'ignore', 'log', 'dump'
$padInfo // Debug: 'trace', 'stats', 'track', 'xml', 'xref'
$padOutputType // 'web', 'file', 'download', 'console'
$padCache // Enable caching
// Database
$padSqlHost
$padSqlDatabase
$padSqlUser
$padSqlPassword
$padInfo = 'trace' for execution tracing{dump} tag for variable inspection{trace} tag for execution traceDATA/ directory for error dumps and logsAPP/
├── _lib/ # Shared libraries
├── _inits.pad # Global layout
├── _inits.php # Global initialization
├── components/ # Reusable components
│ ├── header.pad
│ └── footer.pad
├── pages/ # Main pages
│ ├── home/
│ └── about/
└── api/ # API endpoints
└── users/
_inits.pad handle layoutsfirst, page, rows options| Operator | Meaning |
|---|---|
eq, == |
Equal |
ne, != |
Not equal |
gt, > |
Greater than |
lt, < |
Less than |
ge, >= |
Greater or equal |
le, <= |
Less or equal |
and, or |
Logical operators |
range (a, b) |
Value in range |
| Function | Purpose |
|---|---|
upper, lower |
Case conversion |
trim |
Remove whitespace |
html, url |
Encoding |
date('fmt') |
Format date |
+ n, - n, * n, / n |
Arithmetic |
left(n), cut(n) |
Truncate |
after('x'), before('x') |
Extract substring |
contains('x') |
Check substring |
. 'str' |
Concatenate |
{echo} - {$var | upper} won’t work{echo $x | + 1} not | +1{count 'items'} not {count items}{ } as tags; use external filespadRedirect() - Don’t use exit or die in PAD appsRun regression tests by visiting /regression in browser. Tests compare current output against stored HTML snapshots.
See LICENSE file for details.