crodas/simple-view-engine
A simple, fast PHP view engine with a lightweight template syntax. It compiles templates to plain PHP for speed, supports template inheritance and output escaping, and drops into existing PHP projects without heavy dependencies or framework lock-in.
What it is: a framework-agnostic, standalone port of Laravel 4's Blade compiler. It parses @directive / {{ }} templates and compiles them to plain PHP files you include. It is not a Laravel view layer — no service provider, no facade, no view() helper, no shared-data bag. It is a template → PHP compiler you wire up yourself.
Install and orient yourself
composer require crodas/simple-view-engine
First files to read, in this order:
README.md — the entire public API is 2–3 methods; read it end to end.src/…/SimpleView.php — the render/driver class; note the constructor arguments (cache/compile directory vs. template directory) and the exact render() signature before writing anything.tests/ — the most reliable documentation of directive semantics, especially escaping.Minimal end-to-end use
require __DIR__ . '/vendor/autoload.php';
$view = new SimpleView\SimpleView(__DIR__ . '/cache'); // compile target; must be writable
echo $view->render(__DIR__ . '/views/greeting.php', [
'name' => 'Ada',
'items' => ['a', 'b', 'c'],
]);
{{-- views/greeting.php --}}
@foreach ($items as $item)
<li>{{ $item }}</li>
@endforeach
Hello, {{{ $name }}}.
Your first real use case: it is a code generator, not a page-renderer. Reach for it when you have a small CLI/build task that emits repetitive files — model/repository scaffolding, migration stubs, config bundles, Dockerfiles, email bodies, static HTML — and you want @foreach + @if + layout inheritance instead of string concatenation and sprintf. Treat "generate 40 files from one template + one data array" as the target workload; that is where the compile-once-then-include model pays off and where its thin feature set is not a liability.
Two-phase render (compile, then include). The cheapest way to use this package at scale is to separate compilation from execution: compile every template once during your build/deploy step, commit or ship the resulting PHP files, and let production only include them. Cache the renderer instance if you are looping over many templates in one process — re-instantiating per file throws away any internal state.
Layout inheritance is the whole point. Write one layout.php with @yield('content') and @yield('title'), then have each generated file start with:
@extends('layout')
@section('title')
{{ $module }} module
@stop
@section('content')
class {{ $module }} extends Model { ... }
@stop
For a code generator this is what keeps 40 emitted files consistent. @include for reusable fragments (e.g. a phpdoc.php block), remembering includes resolve against the view root, not the including file.
Data as a plain array, and shape it deliberately. Build one associative array per file and pass it whole. Because data is extracted into the template's local scope, keep keys in snake_case or a namespace-ish prefix ($module, $module_fields) so they cannot collide with loop variables or $this. Pre-compute anything non-trivial in PHP — formatted dates, pluralised names, indentation strings — and hand the template finished values. Do not put business logic in templates; you will debug it through a compiled PHP file.
Wrap it in a thin service of your own. Given the package's zero adoption, do not scatter new SimpleView(...) calls through your codebase. Create one ViewFactory/Renderer class that owns the instance, the template root, the cache root, and a render(string $template, array $data): string method. That single seam lets you swap engines, add escaping policy, add shared/global data (which the package lacks), and test generators without touching templates.
Dev vs. prod workflow. In development, delete the compile directory (or wrap compilation in a filemtime check you control) so edits to .php templates are picked up. In CI, add a job that compiles all templates and php -ls every output file — this catches directive syntax errors and PHP syntax errors introduced by your data before they reach a user. Snapshot-test the generated files with a "golden file" diff so template refactors produce reviewable, byte-level diffs.
Integration tips.
$container['view'] and write a 15-line adapter that maps your framework's render call onto SimpleView::render.templates/ directory + --force/--dry-run flags. Print rendered output to stdout on --dry-run; that alone makes templates fast to iterate on.assertStringContainsString('extends Model', $out)). Never assert on exact whitespace unless your templates are whitespace-controlled.Extension point. The compiler is the extension point: if you need a @datetime($x) directive, subclass/extend the compiler rather than pre-processing template text with str_replace — regex pre-processing of template source is a fast route to unmaintainable generators.
This is Laravel 4 Blade, so the escaping rules are inverted from what you know. {{ $var }} is raw output and {{{ $var }}} is the escaped form; {!! !!} does not exist. If you port modern Blade knowledge into this package you will silently ship unescaped output. Verify the escaping behaviour against the tests before your first render with user-supplied data, and if the semantics are unclear, escape in PHP (htmlspecialchars($x, ENT_QUOTES)) and echo the already-safe string. Any template that interpolates data you did not produce yourself is a potential XSS.
Never let an untrusted party supply template source. Directives compile to arbitrary PHP. Template files are code; treat the template directory with the same trust boundary as your src/ directory. Do not build a "user-editable emails" feature on top of this.
Stale compiled output is the #1 operational failure. If the package caches by template path without checking mtime — or if your deploy copies templates but not the cache — you get yesterday's output with today's data. Make "clear the compile directory" an explicit, idempotent deploy step, and be aware that OPcache with opcache.validate_timestamps=0 will serve stale compiled files even after you clear the cache directory. Restart PHP-FPM or call opcache_invalidate after clearing.
Writable cache dir, and concurrency. The compile directory must exist and be writable by the web/CLI user; a first run under a different user (root, or a cron job) can leave root-owned files that the next web request cannot overwrite. Under concurrent load, compilation is a read-modify-write on a shared path — if you compile at runtime rather than at deploy, you can get partially written PHP being included. Prefer deploy-time compilation; if you must compile at runtime, ensure your wrapper serialises it or compiles to a per-process temp path and rename()s atomically.
Undefined variables are PHP notices, not friendly errors. There is no $view->share(), no error bag, no "did you mean" — a missing key in the data array surfaces as Undefined variable or Undefined array key inside the compiled file, with a line number pointing at generated code, not your template. When debugging a confusing error, open the compiled PHP file; it is plain, readable, and usually one glance explains everything (e.g. a directive that swallowed a @ in an email address, or an @if that never closed).
Directive gotchas worth knowing up front.
@ in output (@example.com, @media, @import in CSS) can be misread as a directive. Escape it or restructure the line.@included file are not reliably available to the parent layout — define sections in the child template that does the @extends.@stop vs @show vs @endsection differ; this package is old enough that only some are implemented. Check the compiler, don't assume.@include resolves relative to the configured view root. Nested include paths that work in dev (absolute-ish) will break in prod.php -l + a normaliser) as a post-step rather than contorting the templates.Age and maintenance: budget for it. Five stars, zero dependents, and a PHP-5-era codebase means you should expect deprecation noise or hard failures on modern PHP (old string-interpolation styles, create_function, each(), ${var} in strings, loose preg_replace with /e). Strategy, in order of preference: (1) fork and vendor it under your own namespace so a rogue upstream tag cannot break your build; (2) pin the exact version in composer.json and lock it; (3) if you do patch, keep the patch as a small diff and add a test that fails without it. Do not put this on a path where a security review is mandatory without first auditing the compiler.
The honest trade-off. The value here is Blade syntax without Laravel in ~1 dependency. If you only need str_replace-level templating, plain PHP files with <?= ?> and extract() cost you nothing and never go stale. If you like the syntax and want something maintained, Twig or league/plates (which is Blade-adjacent and actively maintained) give you the same ergonomics with escaping defaults that match modern expectations. Choose SimpleView deliberately — for its compile-to-PHP model and tiny footprint — and wrap it so that reversing that choice later is a one-class change.
How can I help you explore Laravel packages today?