feat: framework refactor + decouple from Hyperf#349
feat: framework refactor + decouple from Hyperf#349binaryfire wants to merge 1964 commits intohypervel:0.4from
Conversation
|
@albertcht To illustrate how much easier it will be to keep Hypervel in sync with Laravel after this refactor, I asked Claude how long it would take to merge laravel/framework#58461 (as an example) into this branch. This is what it said: So just 5-10 minutes of work with the help of AI tooling! Merging individual PRs is inefficient - merging releases would be better. I can set up a Discord channel where new releases are automatically posted via webhooks. Maybe someone in your team can be responsible for monitoring that channel's notifications and merging updates ever week or 2? I'll only be 1-2 hours of work once the codebases are 1:1. We should be diligent about staying on top of merging updates. Otherwise we'll end up in in the same as Hyperf - i.e. the codebase being completely out of date with the current Laravel API. |
|
Hi @binaryfire , Thank you for submitting this PR and for the detailed explanation of the refactor. After reading through it, I strongly agree that this is the best long-term direction for Hypervel. Refactoring Hypervel into a standalone framework and striving for 1:1 parity with Laravel will indeed solve the current issues regarding deep coupling with Hyperf, maintenance difficulties, outdated versions, and inefficient AI assistance. While this is a difficult step, it is absolutely necessary for the future of the project. Regarding this refactor and the planning for the v0.4 branch, I have a few thoughts to verify with you:
Thank you again for dedicating so much effort to driving this forward; this is a massive undertaking. Let's move forward gradually on this branch with ongoing Code Reviews. |
|
Hi @albertcht Thanks for the detailed response! I'm glad we're aligned on the direction. Let me address each point:
Let me know your thoughts! |
8cec3bf to
bfffa6f
Compare
|
Hi @albertcht. The All the Laravel tests have been ported over and are passing (the unit tests, as well as the integration tests for MySQL, MariaDB, Postgres and SQLite). I've implemented Context-based coroutine safety, static caching for performance and modernised all the types. The code passes PHPStan level 5. Let me know if there's anything I've missed, if you have any ideas or you have any questions. The other packages aren't ready for review yet - many of them are mid-migration and contain temporary code. So please don't review the others yet :) I'll let you know when each one is ready. A few points:
|
80f3ef2 to
94c115e
Compare
|
@albertcht The following packages are ready for review. I've modernised typing, optimised the code, added more tests (including integration tests) and fixed several bugs.
I've also ported https://github.com/friendsofhyperf/redis-subscriber into the Redis package. The subscription methods were all blocking - now they're coroutine friendly. With the previous implementation, if you wrapped
The approach follows the same pattern suggested in hyperf/hyperf#4775 (https://github.com/mix-php/redis-subscriber, which Deeka ported to https://github.com/friendsofhyperf/components). I.e. a dedicated raw socket connection with This is a good article re: this issue for reference: https://openswoole.com/article/redis-swoole-pubsub |
|
Hi @albertcht! The new This is Swoole-optimised version of Laravel's IoC Container, replacing Hyperf's container. The goal: give Hypervel the complete Laravel container API while maintaining performance parity with Hyperf's container and full coroutine safety for Swoole's long-running process model. Why replace Hyperf's container?Hyperf's container is minimal. It exposes
Also, the API is very different to Laravel's. This makes it difficult to port Laravel code or use Laravel's service provider patterns without shimming everything. The new container closes that gap completely and makes interacting with the container much more familiar to Laravel devs. It also means that our package and test code will be closer to 1:1 with Laravel now. APIThe new container implements the full Laravel container contract:
It also supports closure return-type bindings (register a binding by returning a typed value from a closure, including union types), Key API difference from HyperfLike Hyperf's Auto-singletoned instances are stored in a separate Attribute-based injection16 contextual attributes are included, providing declarative dependency injection:
Example: class OrderService
{
public function __construct(
#[Config('orders.tax_rate')] private float $taxRate,
#[Tag('payment-processors')] private array $processors,
#[Authenticated] private User $user,
) {}
}PerformanceBuild recipe cachingConstructor parameters are analyzed via reflection once per class and cached as Method parameter caching
Reflection caching
Hot-path optimizations
Performance vs HyperfThe singleton cache-hit path does marginally more work than Hyperf's single Coroutine safetyAll per-request state is stored in coroutine-local
Circular dependency detection uses two complementary mechanisms:
All transient Context state is cleaned up in Scoped instance cleanup is handled consistently across all invalidation paths. Tests~220 tests:
Everything passes at PHPStan level 5. Let me know what you think |
| */ | ||
| protected function prepareDatabase(): void | ||
| { | ||
| if (! $this->migrator->repositoryExists()) { |
There was a problem hiding this comment.
Hi @binaryfire , do we consider porting handleMissingDatabase function from Laravel as well?
https://github.com/laravel/framework/blob/12.x/src/Illuminate/Database/Console/Migrations/MigrateCommand.php#L179
There was a problem hiding this comment.
Hi @albertcht. I haven’t ported the Laravel commands because I want to port illuminate/console first.
So all these commands will be fully refactored later (and the Laravel features will be ported at that time).
| use Hypervel\Console\Prohibitable; | ||
| use Hypervel\Database\ConnectionResolverInterface; | ||
|
|
||
| class WipeCommand extends Command |
There was a problem hiding this comment.
@binaryfire will we also port DbCommand, DumpCommand, MonitorCommand, PruneCommand, ShowCommand and TableCommand from Laravel?
There was a problem hiding this comment.
@albertcht Please see my comment here: #349 (comment). The current commands are just refactored versions of the old commands. I'll be doing fresh ports of the Laravel commands after I port illuminate/console. Otherwise I'd have to do the work twice. So could you skip reviewing the commands for now? It will be better to review the new ones after I've replaced them. That won't be for a while though - there are several other things I need to refactor before I do illuminate/console.
Update namespace, Cookie import, add type hints to PSR-7 proxy methods, add void return to setResponse, add mixed return to getTrailer, add docblocks, fix grammar.
Update namespace, add docblock.
Update namespace, add native property types, add type hints to seek/write/ read/getMetadata params, add mixed return to detach/getMetadata, add docblocks, fix grammar.
Update namespace, FileException import (Hyperf\HttpServer\Exception\Http to Hypervel\HttpServer\Exceptions\Http), add union type to constructor, add type hints to seek/write/read/getMetadata, add mixed return to detach/getMetadata, add docblocks, preserve behavioral descriptions and @see links.
Update namespace, StandardStream import, add native type to $mimeType, add type hints to moveTo/isStringNotEmpty/validateActive, fix loose comparisons, add strict flag to in_array, replace empty() with strict comparison, add docblocks, preserve all behavioral descriptions.
…es and composer.json files - Port Uri/Uri.php: namespace update, fix loose comparisons, add type hints, docblocks - Create ConfigProvider.php registering RequestParserInterface -> Parser - Update root composer.json: PSR-4 autoload, replace, config discovery - Update all 59 consumer files (41 src/ + 18 tests/) from Hyperf\HttpMessage to Hypervel\HttpMessage - Update exception namespace: Exception\ -> Exceptions\ (plural) - Consolidate HyperfHttpException alias in foundation Handler - Update cookie contracts/extension: HyperfCookie -> BaseCookie - Update sub-package composer.json: core (remove), cookie, http-server (add), websocket-server, engine (suggest -> require) - Drop psr/http-message ^1.0 support, require ^2.0 only across all packages - Update testbench ConfigProviderRegister
Replaces Swow\Psr7\Message\MessagePlusInterface with Hypervel\Contracts\Http\MessagePlusInterface. Types modernized to match PSR-7 v2: all with* params typed, set*/addHeader $value typed as string|array.
Replaces Swow\Psr7\Message\RequestPlusInterface with Hypervel\Contracts\Http\RequestPlusInterface. All with* params typed to match PSR-7 v2 (withMethod(string), withUri(bool $preserveHost), withRequestTarget(string)).
Replaces Swow\Psr7\Message\ResponsePlusInterface with Hypervel\Contracts\Http\ResponsePlusInterface. withStatus() now properly typed with int $code, string $reasonPhrase matching PSR-7 v2.
…hSignals Merge Laravel improvements into supporting console classes. Parser gets cleaner regex handling. ConfirmableTrait gains components integration.
Add shouldUseLocks() check to use lock-based mutexes when the cache store implements LockProvider, falling back to add/has/forget.
Add shouldUseLocks() check to use lock-based mutexes when the cache store implements LockProvider, falling back to add/has.
Add isDue maintenance mode guard, output append/email methods, and timezone handling improvements.
Merge Laravel improvements into Schedule, CallbackEvent, CacheEventMutex, and ManagesFrequencies. Update interface imports for new locations.
Port Laravel's JSON output mode for machine-readable schedule listing.
ScheduleRunCommand: add ScheduledBackgroundTaskFinished dispatch. ScheduleTestCommand: strip php artisan prefix for name matching. Update interface imports across all scheduling commands.
Add rerouteSymfonyCommandEvents() which hooks into Symfony's ConsoleEvents::COMMAND and ConsoleEvents::TERMINATE to dispatch Hypervel CommandStarting and CommandFinished events. Wire the Symfony dispatcher in getArtisan(). Skipped in test environments, opt-in via WithConsoleEvents trait (matching Laravel).
Resolves the application namespace from the composer.json PSR-4 autoload configuration. Used by GeneratorCommand for class generation.
Update Artisan facade accessor. Register TwoColumnDetail import alias in DatabaseServiceProvider for Migrator/Seeder compatibility.
Remove friendsofhyperf/pretty-console from root and foundation composer.json. Replaced by ported Laravel console view components.
Replace process-global static properties with coroutine Context storage for output, interactivity, fallback state, and validation callbacks. Each coroutine gets isolated prompt state, preventing cross-contamination when commands run concurrently in Swoole workers.
Verify that prompt output, interactivity, fallback, and validation state is isolated per-coroutine via Context.
Mock OutputStyle (not SymfonyStyle) and bind via OutputStyle::class (not OutputInterface::class) to match the narrowed property type on Command. Aligns with Laravel's PendingCommand pattern.
Update OutputInterface mocks to OutputStyle. Replace setUpPrettyable with direct Factory initialization. Add rootNamespace override to GeneratorCommandStub. Add getStore mock to CacheSchedulingMutexTest. Update interface imports in scheduling tests.
…Hypervel View Components - Migrator: change imports from FriendsOfHyperf\PrettyConsole to Hypervel\Console\View\Components - Migrator: narrow $output type from OutputInterface to OutputStyle (matches Command's type) - Migrator: fix pretendToRun() Collection→array type mismatch for BulletList::render() - Seeder: change TwoColumnDetail import from PrettyConsole to Hypervel - composer.json: add hyperf/di and vlucas/phpdotenv dependencies
The 'start' name was a Hyperf convention. Now that we control this command directly, rename it to 'serve' matching Laravel's convention. Also capitalize 'Hypervel' in the description.
CommandReplacer was a static utility that intercepted command registration to rename Hyperf commands (gen:* -> make:*, start -> serve) or suppress them. Now that all relevant packages are ported with correct names, every mapping was dead code. Remove it entirely and clean up the stale gen:* pattern from sentry's ignore_commands config.
Remove CommandReplacer integration from resolve(), reducing alias registration to a single foreach loop matching Laravel. Fix a pre-existing bug where passing a command instance to resolve() would TypeError on extractCommandName(string) by adding an early instance check. Also widen call()/parseCommand() to accept SymfonyCommand|string, matching Laravel's signatures.
Matches the implementation change in Application.php and Laravel's contract signature.
Major refactor of the Foundation Kernel to match Laravel's patterns: - registerCommand() now takes a SymfonyCommand instance instead of a class string, matching Laravel's signature - command() uses Artisan::starting() callbacks with a dual path: if commands are already loaded, adds directly to artisan; otherwise defers via starting() callback - load() now does inline command discovery with starting() callbacks, matching Laravel's implementation using WeakMap for class name tracking - Removed collectCommands() and loadCommands() which used Hyperf-style container roundtrips and Hyperf-first sorting - Removed getLoadedPaths() from Kernel, contract, and facade (internal property, no public getter needed — matches Laravel) - Reordered getArtisan() so bootstrap() runs before Application construction, ensuring starting() callbacks registered during bootstrap fire when the Application constructor calls bootstrap()
SupervisorCommandTest and CommandWatcherTest now pass a resolved command instance to registerCommand() instead of a class string.
Widen the parameter type to SymfonyCommand|string, extracting the class name from instances before resolution. Also add is_string() guards to compileArrayInput() to prevent TypeError when called with integer keys under strict_types.
Change Component base class from OutputStyle to OutputInterface for flexibility. Line component now checks instanceof NewLineAware before calling newLinesWritten(), matching Laravel's implementation. Add phpstan baseline for interactive subcomponents that call OutputStyle methods via the Factory.
Align with the make: prefix convention now that all gen: commands have been ported or removed.
Remove CommandReplacer integration tests (suppression and renaming). Add tests for: command instance resolution, container propagation via addCommand(), alias resolution via AsCommand attribute and $aliases property, Application::call() string vs array input parity, PromptsForMissingInput behavior, and calling commands by class object.
Add missing test methods from Laravel's test suite into existing Hypervel test files: CommandTest (isolation, promptable trait), CacheSchedulingMutexTest (store configuration), EventTest (timezone, maintenance mode, output handling), FrequencyTest (additional frequency methods), ScheduleTest (exec parameters, command instances, mutex cache store, job scheduling).
Add test files that had no existing Hypervel equivalent: CacheCommandMutexTest, CommandMutexTest, InteractsWithIOTest, ConfiguresPromptsTest, OutputStyleTest, ComponentsTest, and their required test fixtures (FakeCommandWithInputPrompting, FakeCommandWithArrayInputPrompting).
New tests covering Hypervel-only functionality with no Laravel equivalent: ErrorRendererTest (Collision-based error rendering), ConsoleServiceProviderTest (provider registration and bindings), EventsTest (all console event classes carry correct data), ClosureCommandTest (closure command handle/purpose/describe/schedule), ScheduleRunCommandTest (coroutine-based background task execution).
CallCommandsTest: tests calling commands by name, class, and instance with proper command registration in defineEnvironment(). CallbackSchedulingTest: tests callback event execution order, background restriction, and exception handling with mutex cleanup and ScheduledTaskFailed event dispatch. Uses null log channel to suppress expected error output during exception handling test.
Hi @albertcht. This isn't ready yet but I'm opening it as a draft so we can begin discussions and code reviews. The goal of this PR is to refactor Hypervel to be a fully standalone framework that is as close to 1:1 parity with Laravel as possible.
Why one large PR
Sorry about the size of this PR. I tried spreading things across multiple branches but it made my work a lot more difficult. This is effectively a framework refactor - the database package is tightly coupled to many other packages (collections, pagination, pool) as well as several support classes, so all these things need to be updated together. Splitting it across branches would mean each branch needs multiple temporary workarounds + would have failing tests until merged together, making review and CI impractical.
A single large, reviewable PR is less risky than a stack of dependent branches that can't pass CI independently.
Reasons for the refactor
1. Outdated Hyperf packages
It's been difficult to migrate existing Laravel projects to Hypervel because Hyperf's database packages are quite outdated. There are almost 100 missing methods, missing traits, it doesn't support nested transactions, there are old Laravel bugs which haven't been fixed (eg. JSON indices aren't handled correctly), coroutine safety issues (eg. model
unguard(),withoutTouching()). Other packages like pagination, collections and support are outdated too.Stringablewas missing a bunch of methods and traits, for example. There are just too many to PR to Hyperf at this point.2. Faster framework development
We need to be able to move quickly and waiting for Hyperf maintainers to merge things adds a lot of friction to framework development. Decoupling means we don't need to work around things like PHP 8.4 compatibility while waiting for it to be added upstream. Hyperf's testing package uses PHPUnit 10 so we can't update to PHPUnit 13 (and Pest 4 in the skeleton) when it releases in a couple of weeks. v13 has the fix that allows
RunTestsInCoroutineto work with newer PHPUnit versions. There are lots of examples like this.3. Parity with Laravel
We need to avoid the same drift from Laravel that's happened with Hyperf since 2019. If we're not proactive with regularly merging Laravel updates every week we'll end up in the same situation. Having a 1:1 directory and code structure to Laravel whenever possible will make this much easier. Especially when using AI tools.
Most importantly, we need to make it easier for Laravel developers to use and contribute to the framework. That means following the same APIs and directory structures and only modifying code when there's a good reason to (coroutine safety, performance, type modernisation etc).
Right now the Hypervel codebase is confusing for both Laravel developers and AI tools:
hypervel/contractspackage, the Hyperf database code is split across 3 packages, the Hyperf pagination package ishyperf/paginatorand nothyperf/pagination)static::registerCallback('creating')vsstatic::creating())ConfigProviderand LaravelServiceProviderpatterns across different packages is confusing for anyone who doesn't know HyperfThis makes it difficult for Laravel developers to port over apps and to contribute to the framework.
4. AI
The above issues mean that AI needs a lot of guidance to understand the Hypervel codebase and generate Hypervel boilerplate. A few examples:
hypervel/contractsfor contracts) and then have to spend a lot of time grepping for things to find them.And so on... This greatly limits the effectiveness of building Hypervel apps with AI. Unfortunately MCP docs servers and CLAUDE.md rules don't solve all these problems - LLMs aren't great at following instructions well and the sheer volume of Laravel data they've trained on means they always default to Laravel-style code. The only solution is 1:1 parity. Small improvements such as adding native type hints are fine - models can solve that kind of thing quickly from exception messages.
What changed so far
New packages
illuminate/databaseportilluminate/collectionsportilluminate/paginationportilluminate/contracts)hyperf/pool)Macroableto a separate package for Laravel parityRemoved Hyperf dependencies so far
Database package
The big task was porting the database package, making it coroutine safe, implementing performance improvements like static caching and modernising the types.
whereLike,whereNot,groupLimit,rawValue,soleValue, JSON operations, etc.Collections package
Contracts package
Support package
hyperf/tappable,hyperf/stringable,hyperf/macroable,hyperf/codecdependenciesStr,Envand helper classes from LaravelHypervel\Contextwrappers (will be portinghyperf/contextsoon)Number::useCurrency()wasn't actually setting the currency)Coroutine safety
withoutEvents(),withoutBroadcasting(),withoutTouching()now use Context instead of static propertiesUnsetContextInTaskWorkerListenerto clear database context in task workersConnection::resetForPool()to prevent state leaks between coroutinesDatabaseTransactionsManagercoroutine-safeBenefits
Testing status so far
What's left (WIP)
The refactor process
Hyperf's Swoole packages like
pool,coroutine,contextandhttp-serverhaven't changed in many years so porting these is straightforward. A lot of the code can be simplified since we don't need SWOW support. And we can still support the ecosystem by contributing any improvements we make back to Hyperf in separate PRs.Eventually I'll refactor the bigger pieces like the container (contextual binding would be nice!) and the config system (completely drop
ConfigProviderand move entirely to service providers). But those will be future PRs. For now the main refactors are the database layer, collections and support classes + the simple Hyperf packages. I'll just port the container and config packages as-is for now.Let me know if you have any feedback, questions or suggestions. I'm happy to make any changes you want. I suggest we just work through this gradually, as an ongoing task over the next month or so. I'll continue working in this branch and ping you each time I add something new.
EDIT: New comments are getting lost in the commit history so linking them here:
New
hypervel/containerpackage ready for reviewSee: #349 (comment)
New
hypervel/context,hypervel/coordinator,hypervel/coroutine,hypervel/engine,hypervel/pool&hypervel/redispackages ready for reviewSee: #349 (comment)
New
hypervel/databasepackage ready for reviewSee: #349 (comment)