Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Core Laravel Package

flightphp/core

Flight is a fast, simple, extensible PHP micro-framework. It offers straightforward routing, RESTful API support, middleware, view rendering, and a lightweight core that integrates easily with existing PHP libraries. Great for small apps, APIs, and prototypes.

View on GitHub
Deep Wiki
Context7

Getting Started

Flight is a single-purpose micro-framework: one composer package, no service container you didn't write, no ORM, no console. Install it as flightphp/core (the Packagist name matches the repo, not the flight/... namespace you might guess):

composer require flightphp/core

A complete app is three files and about fifteen lines. public/index.php:

<?php
require __DIR__ . '/../vendor/autoload.php';

Flight::route('GET /health', function () {
    Flight::json(['ok' => true, 'ts' => time()]);
});

Flight::route('POST /webhooks/stripe', function () {
    $payload = Flight::request()->getBody();
    // verify HMAC, enqueue work, etc.
    Flight::json(['received' => true], 202);
});

Flight::start();

Plus a rewrite so everything funnels through index.php — Apache .htaccess (RewriteCond %{REQUEST_FILENAME} !-f → index.php) or nginx try_files $uri $uri/ /index.php?$query_string. Dev server: php -S localhost:8000 -t public (the package ships composer test-server for its own suite, which is a fine reference for a working router setup).

Where to look first, in order:

  1. The learn/docs site (flightphp.com/learn) — the API surface is small enough to read end-to-end in an hour.
  2. flightphp/skeleton — an opinionated starter with a real app/ layout, config, and DI wiring. Read this before inventing your own structure; it saves you from reinventing Laravel badly.
  3. tests/ in the repo — the authoritative answer for edge cases (route matching, request parsing, halt semantics) when the docs are vague.
  4. The plugins directory — the ecosystem is deliberately thin (Tracy debugger extension, permissions, skeleton). Assume anything Laravel gives you for free is "bring your own package" here.

Your first real use case should not be "replace a Laravel app." It should be the endpoint that doesn't deserve a full framework boot: a Stripe/GitHub webhook receiver, a /health and /metrics sidecar, an internal one-off tool behind basic auth, a tiny JSON API in front of an existing database. That's where Flight earns its place in a Laravel developer's day — 40ms → 3ms bootstrap, no bootstrap/app.php, no container compilation, no config cache warmup.

Mental model translation:

Laravel Flight
routes/api.php Flight::route('GET /x', $callable) calls in a required file
Service provider bind/singleton Flight::register('name', Class::class, $args, $afterCb) (lazy)
app()->make() Flight::name() or Flight::get('name')
config/*.php + config() plain array + Flight::set('config', $array) / Flight::get('config')
Middleware classes route middleware arrays ([fn, 'middleware']) + before/after filters
Blade native PHP views via Flight::render(), or register BladeOne/Latte/Twig
Eloquent bring illuminate/database yourself (see below)
php artisan nothing — write scripts

Implementation Patterns

Keep index.php a bootstrap, not an application. Mirror Laravel's discipline even though nothing forces it:

// public/index.php
require __DIR__ . '/../app/bootstrap.php';   // autoload, config, container bindings, error handlers
require __DIR__ . '/../app/routes.php';       // route table only
Flight::start();

Use Flight::register as a lazy container. It is not a DI container — it's a named-factory registry that only instantiates on first access, which is exactly the performance property you want (no PDO connection on a route that never touches the DB):

Flight::register('db', PDO::class, [$dsn, $user, $pass], function (PDO $pdo) {
    $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
    $pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
});

Flight::route('GET /users/@id:[0-9]+', function ($id) {
    $stmt = Flight::db()->prepare('SELECT * FROM users WHERE id = ?');
    $stmt->execute([$id]);
    $user = $stmt->fetch() ?: null;
    $user ? Flight::json($user) : Flight::json(['error' => 'not found'], 404);
});

Controllers stay plain classes. Flight accepts array callables, so your handler logic is ordinary testable PHP — same instinct as Laravel's invokable controllers, minus the framework base class:

final class UserController
{
    public function __construct(private PDO $db) {}

    public function show(string $id): void
    {
        // ...
        Flight::json($user);
    }
}

Flight::route('GET /users/@id', [UserController::class, 'show']);

If you want constructor injection without hand-writing every new, register a tiny reflection-based resolver via Flight::map('make', ...) and call Flight::make(UserController::class). Ten lines, no league/container dependency. That is the spirit of the framework: compose, don't pull in a container.

The biggest leverage point for a Laravel dev: share Eloquent. illuminate/database works standalone, so a Flight service can read/write the same models, migrations, and schema as your Laravel app rather than duplicating a second data layer:

use Illuminate\Database\Capsule\Manager as Capsule;

$laravelDb = require '/var/www/laravel/config/database.php';   // plain arrays, require-able
$capsule = new Capsule;
$capsule->addConnection($laravelDb['connections']['mysql']);
$capsule->setAsGlobal();
$capsule->bootEloquent();

Point Composer's autoload at the shared App\Models namespace and User::find($id) behaves identically on both sides. Same trick for config: Laravel's config/*.php files return plain arrays, so require them from Flight's bootstrap and you get zero config drift — just strip Laravel-only keys (e.g. search_path) if Capsule rejects a connection array.

Hand work back to Laravel instead of reimplementing it. For anything async, write a row into Laravel's jobs table (or push the same payload to the same Redis queue) with the App\Jobs\... class name Laravel expects, and let your existing worker fleet do the processing. Flight stays a dumb, fast front door; Laravel stays the brain. Same logic for mail, PDF generation, and anything needing heavy dependencies.

Middleware is array-shaped, not class-shaped. Auth on a group:

Flight::route('GET /admin/*', [function () {
    // ... verify token
    if (!$ok) { Flight::json(['error' => 'unauthorized'], 401); Flight::stop(); }
    return true;   // continue
}, 'requireAuth']);

Wire the named function with Flight::map('requireAuth', fn() => ...) or pass closures directly. Flight::group('/api', function () { ... }) nests route definitions for prefixing.

Normalize the error envelope so clients can't tell which app answered:

Flight::map('error', function (Throwable $e) {
    error_log((string) $e);
    Flight::json(['error' => 'server_error'], 500);
});
Flight::map('notFound', fn() => Flight::json(['error' => 'not_found'], 404));

Views: default is native PHP templates (Flight::render('user', ['user' => $u]) with Flight::set('flight.views.path', __DIR__.'/views')). If Blade muscle memory matters more than dependency purity, register eftec/bladeone (single file, no Illuminate) through Flight::register('view', ...) and keep Flight::render() calls unchanged.

Testing: because handlers are plain classes, ~90% of your tests are ordinary PHPUnit unit tests with a PDO mock or SQLite. For HTTP-level tests, bootstrap Flight inside PHPUnit and capture output with ob_start(), or hit composer test-server with a real client. There's no RefreshDatabase/actingAs — you reset global state manually (next section).

Gotchas and Tips

  • Everything is static global state. Flight::set(), Flight::register(), and Flight::map() persist for the process lifetime. Harmless under PHP-FPM (process dies at request end), a landmine in PHPUnit (call Flight::set() cleanups in tearDown) and in long-running workers (RoadRunner/Swoole/FrankenPHP: you must re-run your bootstrap and clear the registry per request or state bleeds between users).
  • Flight::json() and Flight::render() do not terminate the request. A later echo, or an after filter, corrupts the body — and it's silent until a client complains about trailing garbage. Always follow with return and Flight::stop() if there's any code path after it. Flight::halt($code) exists for the same job.
  • Route order is matching order. First match wins, no route cache, no route:cache equivalent — every request re-evaluates your route table. Keep it flat; if you're at 200 routes, you probably wanted Laravel.
  • Path matching is literal. Trailing slashes, case, and URL-encoding are not normalized the way you might expect; a GET /users route does not serve /users/. Register both or normalize in the rewrite.
  • Flight::request()->data is not a body-parser you can trust blindly. It handles form-encoded and JSON, but the shape (array vs stdClass) and content-type sniffing vary — for webhooks, always use Flight::request()->getBody() and json_decode(..., true) yourself, and verify the raw bytes for HMAC signatures before any decoding.
  • Middleware's continue signal is return true. Anything falsy stops the chain in some versions; don't rely on implicit returns. If a middleware 401s, call Flight::json(..., 401) then Flight::stop() — don't exit, or your error/logging hooks never run.
  • No auto-escaping in views. Native PHP views extract() your data and echo it raw. Every output needs htmlspecialchars($x, ENT_QUOTES). This is the single easiest way to ship an XSS bug when coming from Blade.
  • Ship nothing security-related for free. No CSRF, no validation layer, no signed cookies, no session config, no rate limiting, no auth scaffolding. If the endpoint mutates data from a browser, either put it behind a token/HMAC, use SameSite=Lax/Strict on your own session_start() cookies, or keep it non-browser.
  • Production error behavior is opt-in. Enable Flight::set('flight.log_errors', true), register the error handler, and confirm display_errors is off in the php.ini/FPM pool — otherwise stack traces go to clients because there's no debug/production toggle.
  • Subdirectory deploys need Flight::set('flight.base_url', '/tools') or Flight::url() and redirects will point at the domain root and land you in 404s that look like routing bugs.
  • Streaming responses hang without help. Flight sets Content-Length for buffered output; for SSE, chunked downloads, or long polls, disable that behavior (Flight::set('flight.content_length', false)) and flush explicitly, or the browser waits for a length it will never get.
  • Match the project's own bar if you publish a plugin: PHP 7.4 syntax (no enums, no readonly, no union types in signatures), PSR-12, PHPStan level 6, strict comparisons. The core is deliberately dependency-free and fast — a plugin that drags in four packages isn't in the spirit and probably shouldn't exist.
  • Extension points are Flight::map() and Flight::register(). Override or wrap core methods (a Flight::render that injects a layout, a Flight::json that adds an envelope) rather than forking. A "plugin" is just a required file that calls register/map — there's no provider lifecycle, which is the point and also why you must be disciplined about where those files run.
  • Don't grow it. The moment you need queues, scheduling, migrations, broadcasting, an admin panel, or artisan, you've left Flight's design envelope. Keep the Laravel app for that and let Flight own the one endpoint that benefits from a 3ms boot.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource