onelearningcommunity / grease
Opt-in performance optimizations for Eloquent's hot path, built from optimizations declined upstream. Zero core changes, byte-identical output.
Requires
- php: ^8.2
- illuminate/database: ^12.0 || ^13.0
- illuminate/events: ^12.0 || ^13.0
- illuminate/support: ^12.0 || ^13.0
- illuminate/view: ^12.0 || ^13.0
Requires (Dev)
- laravel/pint: ^1.29
- orchestra/testbench: ^10.0 || ^11.0
- phpbench/phpbench: ^1.2
- phpunit/phpunit: ^11.0 || ^12.0
README
Opt-in performance across Laravel's hot paths β a menu of byte-identical tiers, built from optimizations declined upstream.
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.
Install
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. 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.
See The Container, The Request, The Router, and The View Cache.
What you get
Representative deltas, measured on Linux (reproduce on your own build β one command):
- End-to-end requests (incl. SQL): β87% list-100-users, β84% eager-load, β53% show, β20% bulk write.
- Per operation: hydrate β54%,
toArrayβ53%, set+dirty β62%, read β27%, enum β44%, date serialization β87%. - 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.
Byte-identical, or it's a failing test
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
Learn more
- Getting Started β install, the Γ -la-carte tiers, the optional providers
- Why Grease β the "marginal in isolation" story and the declined core PRs
- 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
Requirements
PHP 8.2+, Laravel 12/13.
License
Released under the MIT License β Copyright Β© 2026 One Learning Community LTD.
Built with Claude
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 the wins core couldn't.