onelearningcommunity/grease
Grease is a menu of independent, byte-identical performance tiers for Laravel's hot paths—Eloquent hydration, casting and serialization, events, Blade, config, validation, the container and router. Opt in per tier; the model trait is the zero-config starting point. Zero framework changes, and anythi...
Opt-in performance across Laravel's hot paths — a menu of byte-identical tiers.
Grease is a menu of independent, byte-identical optimizations spanning the whole request
lifecycle — Eloquent hydration/casting/serialization, the event dispatcher, the Blade
compiler, config() reads, validation, the container, the request, and the router. They share one idea: Laravel re-derives the same stable facts on every row,
attribute, component, render, request, and query; Grease computes each once and reuses it.
Take the tiers whose hot paths you actually run — the model trait is the zero-config on-ramp,
not the whole package. Zero framework changes; anything that doesn't opt in runs pure vanilla.
composer require onelearningcommunity/grease
use Grease\Concerns\HasGrease;
class User extends Model
{
use HasGrease;
}
That's the model tier — the easiest win and a fine place to stop: no config, no provider, no
cache to warm. Its hydration, casting, and serialization now run the greased fast paths,
byte-identical to vanilla Eloquent. Prefer inheritance? Extend \Grease\GreasedModel instead.
The rest of Grease lives across the request, not just in Eloquent. Each tier is a separate opt-in — take the ones whose hot paths you run. Several are a single (non-auto-discovered) provider:
// bootstrap/providers.php, or the providers array in config/app.php
Grease\Events\GreaseEventServiceProvider::class, // faster event dispatcher, app-wide
Grease\View\GreaseViewServiceProvider::class, // faster Blade render (+ grease:view-cache)
Grease\Config\GreaseConfigServiceProvider::class, // memoized config() reads (+ grease:config-cache)
Grease\Validation\GreaseValidationServiceProvider::class, // memoized validation rule parsing
A few more foundation tiers go deeper into the request lifecycle. They can't be a provider (they're constructed before any provider runs), so each is a one-line swap at the application's own entry point — the heaviest opt-in, taken only if you want it:
// bootstrap/app.php — greased container (faster dependency resolution)
return Grease\Container\Application::configure(basePath: dirname(__DIR__))/* …->create() */;
// public/index.php — greased request (memoized input() / all())
$request = Grease\Http\Request::capture();
// bootstrap/app.php — greased router (cached middleware resolve+sort), before return
Grease\Routing\Router::swap($app);
The router additionally has an eager, opcache-interned middleware index — register
Grease\Routing\GreaseRoutingServiceProvider::class and deploy with php artisan grease:route-cache
(a route:cache twin) to make FPM middleware resolution ~free. That same provider also swaps in a
greased URL generator (no app-entry edit needed — url is resolved lazily): route() replays a
cached per-name [segments, params] shape instead of re-running the full assembly pipeline every
call, and grease:route-cache pre-seeds that shape index too. Byte-identical or it defers. The view
tier has the same: deploy
with php artisan grease:view-cache (a view:cache twin) and view resolution becomes an
opcache-interned lookup — no per-render filesystem stat-walk.
Each cache command also hooks php artisan optimize: with the provider registered, a standard
optimize runs the grease cache in its slot (and optimize:clear the matching clear twin), so your
deploy needs no grease-specific step.
See The Container, The Request, The Router, The URL Generator, and The View Cache.
Representative deltas, measured on Linux (reproduce on your own build — one command):
toArray −53%, set+dirty −62%, read −27%, enum −44%, date serialization −87%.belongsToMany hydrates per related row was the one model class the tiers couldn't reach — now greased too, −75% on a pivot-heavy get() (Linux). A custom using() pivot or a morphToMany pivot stays vanilla — unaccelerated, byte-identical.__call (HasGreasedQueries, a separate per-model opt-in — not bundled in HasGrease): memoizes the scope/passthru/forward dispatch verdict for forwarded query verbs. Deliberately à la carte: it swaps a custom builder in app-wide for a sub-0.1%-of-a-request gain, so it's for query-construction-heavy paths chasing every cycle, not a default. (wrapTable() identifier quoting is likewise memoized, riding the database-connection tier.)HasGreasedAcyclicSerialization, a separate per-model opt-in — not bundled in HasGrease): Eloquent runs a debug_backtrace circular-reference guard on every toArray/getQueueableRelations/touchOwners/push — a 100% tax for a <1% (self-referential-tree) problem ordinary eager loading can't even produce. One withoutRecursion() override drops it for a model that promises an acyclic graph; byte-identical for acyclic data (the guard only changes output on a real cycle). ~1,230 ns saved per relation-bearing model → −36% on a 100-row with() list response (Linux), the most common API shape. Leave it off self-referential tree models (it would recurse forever — your one responsibility). The broadest serialization win in Grease.HasGreasedDecimalCasts, a separate per-model opt-in — not bundled in HasGrease, because decimal means money): returns an already-canonical scaled string verbatim instead of round-tripping it through Brick\Math. It never rounds — anything off-scale/leading-zero/notation/non-string defers to vanilla, so the worst case is "no speedup", never a wrong number (proven over 1.1M fuzzed cases vs the Brick oracle). The win is driver-gated: MySQL/PostgreSQL return canonical decimal strings (it fires), SQLite returns floats (it defers, byte-identical). On a greased transaction model — several decimals, no JSON — ~−3.7% (Linux), ~260 ns per decimal column; nothing on decimal-free models. A narrow tier for decimal-dense financial tables, not a default.$loop-heavy table, −20.3% a layout — byte-identical HTML.config() reads, app-wide): a memoized read path (−65% on a repeat-heavy mix), and an opcache-interned flat index via grease:config-cache that cuts ~88% of config-read time — a per-request win that scales with how many reads your app makes (real apps make thousands).grease:route-cache index takes it to ~−96% (FPM ≈ Octane steady-state). Compounds with request volume.route() assembly, provider opt-in): −93% per route() call — a cached per-name shape replayed as a concat, skipping the formatParameters/replace/query pipeline (the Symfony compile is already cached, so it was never the cost). A thin slice of a vanilla response, but a compounding one: on an API-Resource payload where the model tiers already shrank everything else, it's ~−26% of the response (500 rows × ~5 absolute links → −84.7% full-stack vs vanilla). Byte-identical; domain / optional / scoped / query-string / signed URLs defer to vanilla.grease:view-cache, provider opt-in): the resolution view:cache throws away. An opcache-interned name→path index turns each view lookup from a filesystem stat-walk into an array hit (20 views: 0 file_exists calls vs 20), and permanently kills the never-memoized dynamic-view miss — even under Octane.These are :memory:/Linux figures — read them as Grease's share of the work, not your p99,
and reproduce on your target. The Benchmarks guide
has the methodology, the build-to-build variance, and the honest caveats.
Running Octane? The case is stronger still. A persistent worker amortizes the framework
boot that dilutes Grease under FPM, so the work it removes becomes a visible share of every
request — and HasGrease on your models alone is enough to justify it.
See Grease & Octane.
That promise is the whole product. Every cast type, edge value, null, and dirty-check is asserted equal to vanilla across PHP 8.2–8.5 and Laravel 12/13; the benchmarks run the same fixtures the parity tests prove identical. Where Grease can't guarantee byte-identity for an exotic case, it defers to vanilla — correct, just unaccelerated.
composer test # the byte-identical contract
composer bench # phpbench per-op A/B + the SQL suite
PHP 8.2+, Laravel 12/13.
Released under the MIT License — Copyright © 2026 One Learning Community LTD.
Grease was built proudly in collaboration with Claude — a small proof of what a strong engineering mindset and AI can do together: measure first, keep the parity spine honest, and ship wins that compound.
How can I help you explore Laravel packages today?