Lift v1.3.0
The 30-second case

Why Lift?

Every PHP framework has a story. Lift's is simple: modern PHP deserves a framework that isn't haunted by decisions made in 2010. No legacy baggage. No magic. No containers that weigh more than your app.

The problem landscape

Problem
Typical answer
Lift's answer
Heavy DI containers
Symfony: powerful but ~2 MB of compiled cache before your code runs
Autowiring with reflection cache. No compilation step, no XML.
Too much magic
Laravel: facades, helpers, macros — you never know what runs where
Explicit bindings. Every dependency is traceable in your IDE.
Legacy PHP support
Many frameworks drag PHP 7.x / global state / mixed paradigms along
PHP 8.1+ only. Named args, enums, fibers — use modern PHP everywhere.
FPM-only runtime
Traditional frameworks restart PHP on every request, waste warm state
First-class RoadRunner, Swoole & FrankenPHP adapters. Zero app changes.
Dependency sprawl
Slim pulls ~12 packages; Laravel bootstraps 80+ in a fresh install
Zero non-PSR runtime deps. You choose every package you add.
Hard to test
Globals, static facades, boot sequences make unit tests painful
App::handle($req) — no globals touched. Pure in/out testing.
Config file soup
config/app.php, config/services.yaml, .env, config/packages/…
One constructor, one DI file. ENV vars read directly.
Async is an afterthought
FPM-based frameworks bolt on async via queues or external processes
Persistent-worker adapters are a core primitive, not a package.

What makes Lift different

Runtime-native from day one

RoadRunner, Swoole, and FrankenPHP adapters are built into the framework — not community packages. Each runtime wraps the same $app in a tiny entry script; your routes, controllers, and middleware stay byte-for-byte identical.

# wrap the same $app, then run the server
(new RoadRunnerWorker($app))->serve(); → ./rr serve
(new SwooleServer($app))->start(); → php server.php
(new FrankenPhpWorker($app))->serve(); → ./frankenphp run

O(1) static route lookup

Static routes are stored in a hash map — resolution is a single array read, no regex scanning. Dynamic routes fall back to a compiled regex only when needed. This is measurable in benchmarks, not marketing copy.

See benchmark numbers →

Zero non-PSR runtime deps

composer require malinichevvv/lift-php installs exactly the PSR interfaces and Lift itself. No Guzzle, no Doctrine annotations, no abandoned package you can't update. You own your dependency tree.

Testable without tricks

$app->handle($request) returns a Response — no superglobals, no output buffering, no static state. Every handler is testable in isolation. Real integration tests, not mocked bootstrap chains.

Lift is right for you if…

  • You're building a REST API, microservice, or internal tool
  • You want a persistent-worker runtime (RoadRunner, Swoole, FrankenPHP)
  • You need predictable performance under load
  • You prefer explicit dependencies over framework magic
  • You want tests that actually test your code, not your framework setup
  • You pair-program with an AI assistant and want a framework it generates correctly
  • You're building a Telegram/Slack bot, webhook service, or AI gateway

Lift is not the right tool if…

  • You need scaffolded auth, Blade templates, or Admin UI out of the box
  • Your team expects a Laravel-style "everything included" experience
  • You need a large ecosystem of first-party packages (jobs, mail, notifications…)
  • You're building a traditional server-rendered HTML application with forms

Ready to try it?

A working JSON API in 8 lines. No config files. No service providers. Just PHP.