Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Bladeone Laravel Package

eftec/bladeone

BladeOne is a standalone, lightweight implementation of Laravel's Blade template engine for plain PHP. It offers layouts, sections, includes, directives, caching, and extensibility without requiring Laravel, so you can use familiar Blade syntax in any PHP project.

View on GitHub
Deep Wiki
Context7

Getting Started

BladeOne is a single-class Blade implementation with zero dependencies — you get @extends, @section, @include, @if, @foreach, escaping, and custom directives without installing Laravel.

Install

composer require eftec/bladeone
# or drop the single BladeOne.php file in and require it

Minimum viable setup

require __DIR__ . '/vendor/autoload.php';

use eftec\bladeone\BladeOne;

$views = __DIR__ . '/views';   // .blade.php files
$cache = __DIR__ . '/cache';   // compiled PHP — writable, outside the docroot

$blade = new BladeOne($views, $cache, BladeOne::MODE_AUTO);

echo $blade->run('pages.home', ['title' => 'Home', 'user' => $user]);

views/pages/home.blade.php:

@extends('layouts.app')

@section('content')
    <h1>{{ $title }}</h1>
    @include('partials.nav', ['user' => $user])
@endsection

Where to look first

  1. The constructor signature and the MODE_* constants — they decide whether compilation happens per request (MODE_AUTO), never (MODE_FAST), or always (MODE_DEBUG). This is the single biggest operational lever.
  2. run(), runString(), share(), with(), compile() in the source file — that's the whole public surface you'll use daily.
  3. The compileString / compile* methods to see exactly which directives exist before assuming a Laravel directive is supported.
  4. The upstream wiki/examples (eftec/BladeOne) — the README is the de-facto documentation; there is no formal API reference.

First real use case: replace a hand-rolled include-and-echo layout in a legacy or micro-framework app. Point $views at your existing templates folder, move your header/footer into layouts/app.blade.php, and convert pages one at a time — old and new templates can coexist.

Implementation Patterns

Layouts and partials with dot notation

{{-- layouts/app.blade.php --}}
<!doctype html>
<html>
<head><title>@yield('title', 'App')</title></head>
<body>
  @include('partials.header')
  @yield('content')
  @include('partials.footer')
</body>
</html>

@extends('layouts.app') maps to layouts/app.blade.php. Keep one level of layout + one level of partials; deep inheritance chains are painful to debug without Laravel's error reporting.

Central view service instead of scattering new BladeOne

final class View
{
    private static BladeOne $blade;

    public static function boot(string $env): void
    {
        self::$blade = new BladeOne(
            __DIR__ . '/views',
            __DIR__ . '/cache',
            $env === 'prod' ? BladeOne::MODE_FAST : BladeOne::MODE_AUTO
        );
        self::$blade->setBaseUrl('https://example.test');
        self::$blade->share('appName', 'Acme');
        self::$blade->share('csrf', $_SESSION['csrf'] ?? '');
    }

    public static function render(string $view, array $data = []): string
    {
        return self::$blade->run($view, $data);
    }
}

share() is for request-lifetime globals (user, CSRF, app name); pass per-page data through run() so templates stay explicit and testable.

Custom directives for your domain

// compile-time directive: emits PHP
$blade->directive('money', function ($expr) {
    return "<?php echo number_format((float)($expr), 2); ?>";
});

// runtime directive: evaluated immediately, simpler, slower
$blade->directiveRT('now', fn () => date('c'));

// custom conditional: @admin ... @endadmin
$blade->if('admin', fn () => ($_SESSION['role'] ?? '') === 'admin');

This is how you backfill Laravel directives you miss:

$blade->directive('csrf', fn () => '<input type="hidden" name="_token" value="'
    . htmlspecialchars($_SESSION['csrf'] ?? '', ENT_QUOTES) . '">');

Emails and one-off strings

runString() compiles and renders an inline template, which is ideal for notification bodies where you don't want a file per variant. For anything reused, create a real view — string templates get no editor support and are easy to break with a stray quote.

Asset URLs and links

$blade->setBaseUrl('https://cdn.example.test');
$blade->setAssetDict(['logo' => '/img/logo.svg']);

Then {{ $blade->asset('logo') }} inside templates, or register an @asset directive. There is no global asset() helper like Laravel's — that trips people up constantly.

Deploy/build pattern (the one that matters)

// bin/compile-views.php — run in CI or on deploy
$blade = new BladeOne($views, $cache, BladeOne::MODE_DEBUG);
foreach (glob($views . '/**/*.blade.php') as $file) { /* derive view name */ }
$blade->compile('pages.home'); // compile each known view

Compile on deploy, then run production in MODE_FAST. This removes per-request filemtime() stat calls and eliminates the "half-written compiled file" race between concurrent requests on a cold cache.

Long-running workers (Swoole, RoadRunner, queue daemons)

Compile with MODE_DEBUG/manual compile() and call opcache_invalidate($compiledPath, true) after recompiling, or restart workers on deploy. MODE_AUTO's filemtime check is cheap but opcache will still serve the stale bytecode unless invalidation is enabled.

Extension via traits/subclassing

The ecosystem add-ons follow the same shape: BladeOneCache (fragment caching via @cache), BladeOneLang (@lang/_e), BladeOneHtml (form helpers). Pull in the trait rather than inventing your own caching directive — the compiled-cache key handling is the fiddly part. If you need to change compiled output globally (e.g., add a wrapper to e()), subclass and override compileString, not individual directives.

Gotchas and Tips

Compilation mode is the #1 source of "my changes don't show up."

  • MODE_AUTO recompiles when the template's mtime is newer than the compiled file. If a deploy preserves or back-dates mtimes (archive extraction, rsync with -t, a clock-skewed build box), edits silently never compile. Fix: find cache -name '*.php' -delete on deploy, or force touch on templates.
  • MODE_FAST ignores template mtimes entirely. Never deploy it without purging the compiled directory.
  • When debugging, switch to MODE_DEBUG (recompile every request) rather than guessing at mtimes.

Compiled files are executable PHP. Keep the compiled directory outside the web root. If shared hosting forces it inside, deny access via .htaccess/nginx location rules, and be aware include still works with a non-.php compiled extension if the engine supports configuring one. Anyone who can write a file there gets code execution.

Error line numbers point at the compiled file, not your template. On a syntax or runtime error, open the generated file in the cache directory and read the actual emitted PHP. That is also the fastest way to understand what a directive compiled into — inspect before you post a bug report. Cache the mapping from compiled line to template line mentally, or add a /* template: x line: y */ marker by overriding the compiler if you debug often.

Laravel-isms are not there. No @csrf, @auth, @can, @vite, @error, @dump, old(), route(), config(), csrf_token(), dd(). Templates copy-pasted from a Laravel project will render literal directive text or fatal. Port them deliberately: @csrf → custom directive, @auth → $blade->if('auth', ...), route() → precomputed URL in the data array.

Escaping and {!! !!}. {{ }} compiles to an e()-style htmlentities call (ENT_QUOTES, UTF-8) — good default, but it is not Laravel's e() (no doubleEncode parameter) and it will not protect you inside <script>, <style>, or HTML attributes expecting JSON. @json() may not exist depending on version; use json_encode(..., JSON_HEX_TAG|JSON_HEX_AMP) explicitly. @php ... @endphp blocks bypass escaping entirely — treat them as an escape hatch, not a pattern.

Dynamic view names from user input are an LFI risk. @include($userView) or run($request->get('page')) lets an attacker walk out of the template directory. Whitelist view names against a fixed array or regex before calling run()/include.

Variable scope and reserved names. @include inherits the parent scope in most versions, which makes partials convenient but implicit — pass data explicitly if you care about testability. Avoid variable names that collide with the engine: $this, $blade, and anything the compiler uses internally. A template variable called $loop is fine unless you rely on $loop metadata (index, first, last) — verify that support exists in your pinned version before designing around it, as $loop availability varies by release.

@section/@endsection vs @stop. Both usually work, but be consistent. Content placed outside @section blocks in a child template that uses @extends can produce output in unexpected positions; keep child templates limited to sections and @includes.

BOM and leading whitespace. A UTF-8 BOM before <?php in a template or in your bootstrap file emits bytes before headers and can break header()/session starts. Save templates as UTF-8 without BOM, and never leave a blank line before <?php in included config files.

Compilation is not atomic by default. Two simultaneous cold requests can both write the same compiled file. Precompiling on deploy (or at least warming the cache after clearing it) is the practical fix; if you cannot precompile, accept that the first requests after a purge may be slower and occasionally noisy.

License metadata is "NOASSERTION." Read the LICENSE file in the repository before shipping — don't infer MIT from the README. This matters for distribution and for anything client-facing.

Maintenance reality check. A single-file, low-velocity package means you are close to the source by design. Pin an exact version in composer.json, keep your custom directives and overrides in your own files (not patched into the vendor copy), and write one smoke test that renders a layout + partial + custom directive so a version bump surfaces breakage immediately.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console