Skip to content

One-Learning-Community/grease

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

150 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Grease

tests Latest Version Total Downloads License

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.

📖 Full documentation →

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.

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.

What you get

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 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.
  • Eloquent builder __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.)
  • 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 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).
  • 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-cache index takes it to ~−96% (FPM ≈ Octane steady-state). Compounds with request volume.
  • View cache (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.

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

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 wins that compound.

About

Opt-in performance for Laravel's hot paths. One trait, byte-identical output — and it really shines under Octane. Get greased.

Topics

Resources

License

Contributing

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages