webpatser / fledge-framework
The Fledge Framework — PHP 8.5 optimized Laravel.
Requires
- php: ^8.5
- composer-runtime-api: ^2.2
- ext-ctype: *
- ext-filter: *
- ext-hash: *
- ext-mbstring: *
- ext-openssl: *
- ext-session: *
- ext-tokenizer: *
- amphp/amp: ^3.1
- brick/math: ^0.14.2 || ^0.15 || ^0.16 || ^0.17
- doctrine/inflector: ^2.0.5
- dragonmantank/cron-expression: ^3.4
- egulias/email-validator: ^4.0
- fruitcake/php-cors: ^1.3
- guzzlehttp/guzzle: ^7.8.2
- guzzlehttp/promises: ^2.0.3
- guzzlehttp/uri-template: ^1.0
- laravel/prompts: ^0.3.0
- laravel/serializable-closure: ^2.0.10
- league/commonmark: ^2.8.1
- league/flysystem: ^3.25.1
- league/flysystem-local: ^3.25.1
- monolog/monolog: ^3.0
- nesbot/carbon: ^3.8.4
- nunomaduro/termwind: ^2.0
- psr/container: ^1.1.1 || ^2.0.1
- psr/log: ^1.0 || ^2.0 || ^3.0
- psr/simple-cache: ^1.0 || ^2.0 || ^3.0
- ramsey/uuid: ^4.7
- revolt/event-loop: ^1.0
- symfony/console: ^7.4.0 || ^8.0.0
- symfony/error-handler: ^7.4.0 || ^8.0.0
- symfony/finder: ^7.4.0 || ^8.0.0
- symfony/http-foundation: ^7.4.0 || ^8.0.0
- symfony/http-kernel: ^7.4.0 || ^8.0.0
- symfony/mailer: ^7.4.0 || ^8.0.0
- symfony/mime: ^7.4.0 || ^8.0.0
- symfony/process: ^7.4.5 || ^8.0.5
- symfony/routing: ^7.4.0 || ^8.0.0
- symfony/uid: ^7.4.0 || ^8.0.0
- symfony/var-dumper: ^7.4.0 || ^8.0.0
- tijsverkoyen/css-to-inline-styles: ^2.2.5
- vlucas/phpdotenv: ^5.6.1
- voku/portable-ascii: ^2.0.2
Requires (Dev)
- ext-gmp: *
- ably/ably-php: ^1.0
- amphp/mysql: ^3.0
- aws/aws-sdk-php: ^3.322.9
- fakerphp/faker: ^1.24
- guzzlehttp/psr7: ^2.4
- laravel/pint: ^1.18
- league/flysystem-aws-s3-v3: ^3.25.1
- league/flysystem-ftp: ^3.25.1
- league/flysystem-path-prefixing: ^3.25.1
- league/flysystem-read-only: ^3.25.1
- league/flysystem-sftp-v3: ^3.25.1
- mockery/mockery: ^1.6.10
- opis/json-schema: ^2.4.1
- orchestra/testbench-core: ^11.0.0
- pda/pheanstalk: ^7.0.0 || ^8.0.0
- php-http/discovery: ^1.15
- phpstan/phpstan: ^2.0
- phpunit/phpunit: ^11.5.50 || ^12.5.8 || ^13.0.3
- predis/predis: ^2.3 || ^3.0
- rector/rector: ^2.3
- resend/resend-php: ^1.0
- symfony/cache: ^7.4.0 || ^8.0.0
- symfony/http-client: ^7.4.0 || ^8.0.0
- symfony/psr-http-message-bridge: ^7.4.0 || ^8.0.0
- symfony/translation: ^7.4.0 || ^8.0.0
Suggests
- ext-apcu: Required to use the APC cache driver.
- ext-fileinfo: Required to use the Filesystem class.
- ext-ftp: Required to use the Flysystem FTP driver.
- ext-gd: Required to use Illuminate\Http\Testing\FileFactory::image().
- ext-memcached: Required to use the memcache cache driver.
- ext-pcntl: Required to use all features of the queue worker and console signal trapping.
- ext-pdo: Required to use all database features.
- ext-posix: Required to use all features of the queue worker.
- ext-redis: Required to use the Redis cache and queue drivers (^4.0 || ^5.0 || ^6.0).
- ably/ably-php: Required to use the Ably broadcast driver (^1.0).
- aws/aws-sdk-php: Required to use the SQS queue driver, DynamoDb failed job storage, and SES mail driver (^3.322.9).
- brianium/paratest: Required to run tests in parallel (^7.0 || ^8.0).
- fakerphp/faker: Required to generate fake data using the fake() helper (^1.23).
- filp/whoops: Required for friendly error pages in development (^2.14.3).
- laravel/tinker: Required to use the tinker console command (^2.0).
- league/flysystem-aws-s3-v3: Required to use the Flysystem S3 driver (^3.25.1).
- league/flysystem-ftp: Required to use the Flysystem FTP driver (^3.25.1).
- league/flysystem-path-prefixing: Required to use the scoped driver (^3.25.1).
- league/flysystem-read-only: Required to use read-only disks (^3.25.1)
- league/flysystem-sftp-v3: Required to use the Flysystem SFTP driver (^3.25.1).
- mockery/mockery: Required to use mocking (^1.6).
- pda/pheanstalk: Required to use the beanstalk queue driver (^7.0 || ^8.0).
- php-http/discovery: Required to use PSR-7 bridging features (^1.15).
- phpunit/phpunit: Required to use assertions and run tests (^11.5.50 || ^12.5.8 || ^13.0.3).
- predis/predis: Required to use the predis connector (^2.3 || ^3.0).
- psr/http-message: Required to allow Storage::put to accept a StreamInterface (^1.0).
- pusher/pusher-php-server: Required to use the Pusher broadcast driver (^6.0 || ^7.0).
- resend/resend-php: Required to enable support for the Resend mail transport (^0.10.0 || ^1.0).
- symfony/cache: Required to PSR-6 cache bridge (^7.4 || ^8.0).
- symfony/filesystem: Required to enable support for relative symbolic links (^7.4 || ^8.0).
- symfony/http-client: Required to enable support for the Symfony API mail transports (^7.4 || ^8.0).
- symfony/mailgun-mailer: Required to enable support for the Mailgun mail transport (^7.4 || ^8.0).
- symfony/postmark-mailer: Required to enable support for the Postmark mail transport (^7.4 || ^8.0).
- symfony/psr-http-message-bridge: Required to use PSR-7 bridging features (^7.4 || ^8.0).
- webpatser/fledge-fiber-database: Non-blocking Fiber-based MySQL, MariaDB, and PostgreSQL drivers (^13.3).
- webpatser/fledge-fiber-http: Non-blocking Fiber-based HTTP handler replacing cURL (^13.3).
- webpatser/fledge-fiber-redis: Non-blocking Fiber-based Redis driver (^13.3).
- webpatser/torque: Fiber-based concurrent queue worker replacing Horizon.
Provides
- psr/container-implementation: 1.1 || 2.0
- psr/log-implementation: 1.0 || 2.0 || 3.0
- psr/simple-cache-implementation: 1.0 || 2.0 || 3.0
Conflicts
- tightenco/collect: <5.5.33
Replaces
- illuminate/auth: v13.3.0.6
- illuminate/broadcasting: v13.3.0.6
- illuminate/bus: v13.3.0.6
- illuminate/cache: v13.3.0.6
- illuminate/collections: v13.3.0.6
- illuminate/concurrency: v13.3.0.6
- illuminate/conditionable: v13.3.0.6
- illuminate/config: v13.3.0.6
- illuminate/console: v13.3.0.6
- illuminate/container: v13.3.0.6
- illuminate/contracts: v13.3.0.6
- illuminate/cookie: v13.3.0.6
- illuminate/database: v13.3.0.6
- illuminate/encryption: v13.3.0.6
- illuminate/events: v13.3.0.6
- illuminate/filesystem: v13.3.0.6
- illuminate/hashing: v13.3.0.6
- illuminate/http: v13.3.0.6
- illuminate/json-schema: v13.3.0.6
- illuminate/log: v13.3.0.6
- illuminate/macroable: v13.3.0.6
- illuminate/mail: v13.3.0.6
- illuminate/notifications: v13.3.0.6
- illuminate/pagination: v13.3.0.6
- illuminate/pipeline: v13.3.0.6
- illuminate/process: v13.3.0.6
- illuminate/queue: v13.3.0.6
- illuminate/redis: v13.3.0.6
- illuminate/reflection: v13.3.0.6
- illuminate/routing: v13.3.0.6
- illuminate/session: v13.3.0.6
- illuminate/support: v13.3.0.6
- illuminate/testing: v13.3.0.6
- illuminate/translation: v13.3.0.6
- illuminate/validation: v13.3.0.6
- illuminate/view: v13.3.0.6
- laravel/framework: v13.3.0.6
- spatie/once: *
README
Laravel 13, optimized for PHP 8.5
Named after Fledge from C.S. Lewis's Narnia — a horse transformed by Aslan into something faster and more capable. Laravel's name also comes from Narnia (Cair Paravel). Fledge transforms Laravel for PHP 8.5.
What is Fledge?
Fledge is a drop-in replacement for Laravel's illuminate/framework that requires PHP 8.5 and uses its native features for better performance. Same Illuminate\ namespace, same API, full ecosystem compatibility.
Laravel 13 supports PHP 8.3+ and ships polyfills so it can run on older versions. Fledge removes those polyfills and version checks, and replaces league/uri with PHP 8.5's native URI extension — the single biggest performance win.
52 files changed on top of Laravel 13.3.0. All 13,337 framework tests pass.
Why?
Laravel supports PHP 8.3+ because that's the right call for the ecosystem. But if you're already on PHP 8.5, you're paying for compatibility you don't need:
- 11,000 lines of
league/uriPHP code replaced by compiled C in the PHP runtime - Symfony polyfills for
array_first(),array_last(),array_all(),array_any()— functions that ship natively in PHP 8.5 version_compareguards that branch on every request to check if you're on 8.4+
Fledge strips all of that.
Performance
Real-World
| Metric | Laravel 13 | Fledge | Difference |
|---|---|---|---|
| Homepage render | 30ms | 25ms | 17% faster |
Where the Speed Comes From
The main improvement is URI handling. Every HTTP request parses URIs — $request->uri(), the url() helper, redirects, route generation. Laravel uses league/uri for this. Fledge uses PHP 8.5's native Uri\Rfc3986\Uri, which is compiled C:
| Operation | league/uri | PHP 8.5 native |
|---|---|---|
| Parse URI | 0.047 ms | 0.0005 ms |
| Modify URI | 0.24 ms | 0.0004 ms |
~100x faster for URI operations. Since URIs are touched on every request, it compounds into the 17% real-world improvement.
Other Gains
| Operation | Improvement |
|---|---|
Arr::hasAll / Arr::hasAny |
~6% faster (native array_all/array_any) |
| Collection chains | ~5% faster |
| String pipelines | ~9% faster (pipe operator) |
| No polyfill autoloading | Faster bootstrap |
Fiber-Based Concurrency
Fledge adds a FiberDriver to the Concurrency facade, powered by the Revolt event loop and amphp. Unlike the ProcessDriver (which spawns child processes) or the SyncDriver (sequential), the FiberDriver provides real cooperative async I/O within a single process:
use Illuminate\Support\Facades\Concurrency; // 3 HTTP requests run concurrently — total time ≈ slowest request $results = Concurrency::driver('fiber')->run([ fn () => $httpClient->request(new Request('https://api1.example.com'))->getBody()->buffer(), fn () => $httpClient->request(new Request('https://api2.example.com'))->getBody()->buffer(), fn () => $httpClient->request(new Request('https://api3.example.com'))->getBody()->buffer(), ]);
No background process needed — the Revolt event loop runs inline within the run() call. Tasks using amphp async drivers (HTTP, MySQL, Redis) genuinely interleave on I/O suspension. Shared memory, no serialization overhead, works in both web requests and CLI.
Also available as a standalone package for Laravel 11/12/13: webpatser/laravel-fiber
Non-Blocking Redis (amphp driver)
Fledge ships with amphp/redis as the default Redis driver. Every Redis call — cache reads, locks, queue operations, rate limiting — uses Fiber-based non-blocking I/O via the Revolt event loop.
// Every Cache::get() and Redis::get() is non-blocking by default. // No code changes needed — the amphp driver is transparent. Cache::get('key'); // non-blocking I/O under the hood Redis::set('key', 'val'); // same // Inside Concurrency::run(), multiple Redis calls parallelize automatically: Concurrency::driver('fiber')->run([ fn () => Cache::get('user:1'), fn () => Cache::get('user:2'), fn () => Cache::get('user:3'), ]); // all 3 reads happen concurrently
The amphp driver works from any context. From the main thread, each command still appears synchronous but uses non-blocking socket I/O. Inside a Fiber, multiple commands in separate Fibers execute in parallel.
To fall back to the synchronous phpredis C extension (e.g., for Redis Cluster, which amphp doesn't support):
REDIS_CLIENT=phpredis
The cache layer also includes Fiber-aware internals:
- Lock blocking suspends the Fiber instead of
usleep(), letting other Fibers run - Failover reads try all stores concurrently, returning the first success
- Cluster operations (
many()/putMany()) run concurrent reads/writes via Fibers - Tag operations flush chunks and write entries concurrently
What Changed
| Change | Files | Impact |
|---|---|---|
Native Uri\Rfc3986\Uri replacing league/uri |
3 | ~100x faster URI ops |
| RFC 3986 normalization layer (IDN, unicode, brackets) | 1 | Compatibility bridge |
Remove symfony/polyfill-php84 and polyfill-php85 |
7 | Cleaner autoloading |
Bump PHP to ^8.5 |
37 | Drop compatibility code |
Remove version_compare PHP 8.4 guards |
3 | No runtime branching |
array_all/array_any in Arr::hasAll/hasAny |
1 | Faster array checks |
Pipe operator in Pipeline::then() |
1 | Cleaner code |
#[\NoDiscard] on Pipeline, Cache, Container, Validation |
4 | Developer safety |
| Persistent cURL share manager | 3 | Connection pooling |
json_validate() fast path |
1 | Skip decode on invalid JSON |
| Fiber-based concurrency driver (Revolt + amphp) | 2 | Real async I/O in Concurrency facade |
| amphp/redis as default Redis driver | 5 | Non-blocking Redis I/O for all operations |
| Fiber-aware cache layer (locks, failover, tags) | 8 | Concurrent cache ops inside Fibers |
| Redis required dependency for cache package | 1 | Redis is a first-class citizen |
The RFC 3986 Problem (and How Fledge Solves It)
PHP 8.5's native URI parser is strictly RFC 3986 compliant — stricter than league/uri. It rejects:
- Internationalized domain names like
bébé.be - Unicode in paths like
/日本語/page - Unencoded brackets in query strings like
?filter[status]=active
An attempt to add native URI support to Laravel stalled because of this strictness gap.
Fledge solves it with a normalization layer (Uri::normalizeForRfc3986()) that transparently converts these inputs before passing them to the native parser:
| You write | Fledge normalizes to |
|---|---|
https://bébé.be |
https://xn--bb-bjab.be (punycode) |
https://example.com/日本語 |
https://example.com/%E6%97%A5... (percent-encoded) |
?filter[status]=active |
?filter%5Bstatus%5D=active (encoded brackets) |
This makes Fledge a true drop-in replacement — your existing URLs keep working.
Installation
In an existing Laravel 13 project
# Add Fledge as a path repository (if local) composer config repositories.fledge path /path/to/fledge/packages/framework # Or as a VCS repository (from GitHub) composer config repositories.fledge vcs https://github.com/webpatser/fledge # Replace Laravel's framework with Fledge composer require "laravel/framework:^13.3" -W
From scratch
# Clone the full project (includes test app + framework) git clone https://github.com/webpatser/fledge cd fledge composer install
Compatibility
- Same
Illuminate\namespace — all Laravel packages work unchanged - Same API — no code changes needed in your application
- 13,337 tests passing (4 known Predis failures exist on vanilla Laravel 13 + PHP 8.5 too)
Requirements
- PHP 8.5+
- intl extension (for IDN domain support)
- Composer 2.x
How This Project Works
Fledge tracks Laravel's 13.x branch. When Laravel releases a new version:
- Fetch the latest upstream tag
- Merge into the
fledge-13branch - Resolve any conflicts in the ~50 modified files
- Run the full test suite
- Tag a matching Fledge release
The goal is automated sync for clean merges (~70% of releases), with manual intervention only when upstream touches the same files Fledge modifies.
Versioning
Fledge uses a fourth version segment to track its own releases on top of Laravel's version:
| Laravel | Fledge | Meaning |
|---|---|---|
v13.3.0 |
v13.3.0.1 |
First Fledge release based on Laravel 13.3.0 |
v13.3.0 |
v13.3.0.2 |
Fledge-only fix on top of 13.3.0 |
v13.4.0 |
v13.4.0.1 |
Fledge synced to Laravel 13.4.0 |
The first three segments always match the upstream Laravel version. The fourth is Fledge's own patch counter, starting at .1 for each new Laravel release.
In your composer.json, "laravel/framework": "^13.3" will pull in the latest Fledge release.
Project Structure
packages/framework/ # The Fledge framework (forked illuminate/framework)
├── src/Illuminate/ # Modified Laravel source with PHP 8.5 optimizations
└── tests/ # Unmodified Laravel test suite (tests are the contract)
Rule: tests are never modified. If a test fails after a Fledge change, the change is wrong, not the test.
Known PHP 8.5 Test Failures
These 4 test failures exist on vanilla Laravel 13 running on PHP 8.5 — they are not caused by Fledge:
| Test | Root Cause |
|---|---|
RedisConnectionTest::testItScansForKeys |
Predis cursor format incompatibility |
RedisConnectionTest::testItHscansForKeys |
Predis cursor format incompatibility |
RedisConnectionTest::testItZscansForKeys |
Predis cursor format incompatibility |
RedisConnectionTest::testItSscansForKeys |
Predis cursor format incompatibility |
Credits
All credit goes to Taylor Otwell and the Laravel team. This project is built entirely on their work. Fledge is not a fork intended to compete with Laravel — it's an optimization layer for teams already running PHP 8.5.
License
MIT — same as Laravel.