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/greaseuse 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 parsingA 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. 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, and The View Cache.
Representative deltas, measured on Linux (reproduce on your own build — one command):
- End-to-end requests (incl. SQL): −86% list-100-users, −81% eager-load, −72% m2m eager-load, −51% show, −14% bulk write.
- Per operation: hydrate −54%,
toArray−53%, set+dirty −62%, read −27%, enum −44%, date serialization −87%. - Many-to-many pivots (model tier, automatic with the trait): the pivot a
belongsToManyhydrates per related row was the one model class the tiers couldn't reach — now greased too, −75% on a pivot-heavyget()(Linux). A customusing()pivot or amorphToManypivot stays vanilla — unaccelerated, byte-identical. - Eloquent builder
__call(HasGreasedQueries, a separate per-model opt-in — not bundled inHasGrease): 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.) - Event dispatcher (app-wide): −53% no-listener dispatch, ~halves a render-dense request's event overhead.
- Blade (render path, app-wide): −28.3% simple / −23.4% rich component renders, −26.8% a
$loop-heavy table, −20.3% a layout — byte-identical HTML. - Config (
config()reads, app-wide): a memoized read path (−65%on a repeat-heavy mix), and an opcache-interned flat index viagrease:config-cachethat cuts~88%of config-read time — a per-request win that scales with how many reads your app makes (real apps make thousands). - Foundation tiers (container & request, app-entry opt-in): −38.8% per container resolve, −41% per input-heavy request. Layered with everything above, a real mixed page-load (JSON + Blade) request suite stacks to ~−47% end-to-end for ~+2% retained memory — see the cumulative-stack table.
- Router (middleware resolve+sort, app-entry opt-in): once-per-request work, so small in isolation — but pure waste removed on every request. The lazy cache halves it; the eager
grease:route-cacheindex takes it to ~−96% (FPM ≈ Octane steady-state). Compounds with request volume. - View cache (
grease:view-cache, provider opt-in): the resolutionview:cachethrows away. An opcache-interned name→path index turns each view lookup from a filesystem stat-walk into an array hit (20 views: 0file_existscalls 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- Getting Started — install, the à-la-carte tiers, the optional providers
- Why Grease — the "marginal in isolation" story behind the package
- Grease & Octane — why a persistent worker amplifies every tier
- How It Works — the per-class blueprint and each tier
- Benchmarks — full numbers, methodology, and reproducing them on your build
- The Method — how a win is found and proven (and how the dead ends got rejected)
- The Event Dispatcher · Blade Components · The Container · The Request · The Config Repository · Validation · The Router · The View Cache — the beyond-Eloquent tiers
- Caveats & Narrowing — the two small, obscure things that change
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.