Repository navigation
Add PHPStan rules for Hypervel's long-lived workers - #742
Conversation
Include optional Unsupported guidance in the main diagnostic so terminal and CI formatters retain it. Specify reflection-based argument reading and focused coverage for bare markers and supplied messages, and update the documentation requirements.
Expose resolvedShared() on the container contract so facades can distinguish worker-held instances from fresh resolutions. Retain worker caching for shared roots, keep fresh roots per coroutine, and leave scoped and uncached facades resolving through the container. Generation changes invalidate cached coroutine roots across live contexts, while copied child contexts resolve their own roots. Register the date factory during foundation bootstrap without replacing earlier bindings. Standalone Date facade fallback no longer writes a container binding. Document the supported Date subclass adaptation and cover concurrent resolution, singleton-selecting factories, copied contexts, invalidation and date binding precedence.
Add return templates to filesystem borrowing, pinned Redis connections and SQS connection callbacks without changing runtime behavior. Let filesystem callback parameter templates accept the concrete client, adapter or driver selected by the caller while preserving useful Flysystem bounds for untyped callbacks. Cover typed SDK callbacks, untyped clients and concrete or inferred Flysystem parameters in the max-level type fixtures.
Record the merged attributes prerequisite and completed runtime section. Describe callback parameter templates and their type coverage, remove stale prerequisite gates and instruction line numbers, and keep the remaining implementation at the shared analysis infrastructure.
…d facades Resolve facade accessors and exact root classes from declarations and booted bindings without executing accessors or factories. Respect overrides and unknown construction paths, and include actionable Unsupported attribute guidance in diagnostic messages. Track constants referenced by method attributes and parameter defaults through the existing source dependency helper. Cover native and nullsafe calls, facade factories and binding chains, supported overrides, and uncertain roots.
Share the collected worker-state model across rules and recognize listener, observer, and server-operation contexts. Preserve per-method receiver evidence through inherited handlers and helpers, distinguishing container-built queued listeners from restored job payloads. Report boot-only and test-only calls with their parameter conditions and proven shared receivers. Merge injection evidence across routes, jobs, and constructors; recognize singleton factories and readonly promoted dependencies; and give configuration writes one diagnostic. Add malformed-attribute checks, isolated enablement coverage, and warm-cache regressions for registrations, facade accessors, and constant-backed defaults. Preserve unknown receiver and binding paths without executing application code.
Record the implemented facade resolution, observer discovery, merged injection evidence, and source dependencies. Clarify the bounded receiver and reference-alias analysis and mark section 3.3 as the next implementation step.
Plan removal of the global mutable-date opt-out while preserving useful customization and explicit interoperability conversions. Include stronger return types and PHPStan diagnostics once runtime immutability is guaranteed.
Align resolver, manager, Capsule, testing and queue accessor return types with the Connection base class already required by Eloquent and migrations. Keep custom non-PDO drivers supported and retain ConnectionInterface at independently supplied connection boundaries. Remove redundant type guards and analysis suppressions. Update resolver-backed test doubles without changing their behavioral assertions, including non-PDO rejection coverage and both queue skip-locked outcomes. Document the custom resolver and mock requirements. Expand the native request API porting guidance and remove the ineffective HTTP status assignment during CLI environment bootstrap. Database, queue, foundation, facade metadata and affected integration tests pass; unavailable database drivers remain skipped.
Default LOG_USER facility configuration to the equivalent user string in the framework and both application skeletons. Monolog resolves named string facilities but rejects numeric strings from the environment, so the default now reflects the supported environment value. Preserve the existing LogManager fallback and facility behavior. Static analysis and formatting pass for the combined change.
Detect handles escaping lending callbacks, premature completion of managed transactions, native request APIs, discarded immutable date results and explicit mutable date creation. Match framework declarations and preserve user overrides; share connection inspection with the queue transaction rule. Add configuration environment-cast checks, typed getter guidance, observer registration conventions and application binding-cycle detection. Keep migration column policy disabled by default. Restrict configuration value inference to proven application repository reads so independent repositories retain their declared types. Reuse runtime dependency tracking and configuration source discovery, remove redundant callable guards, and document legitimate framework exceptions with local reasons. Add behavioral rule fixtures and update type expectations. The PHPStan suite, static analysis and formatting pass.
Record the implemented resolver contract, configuration ownership boundary and declaration-aware rules. Describe supported borrowed-resource and transaction identities, regression coverage and the required consumer mock updates. Mark sections 3.3 through 3.12 complete and retain section 3.13 as the next implementation step.
Move exact-catch inspection into the coroutine helper so cancellation checks can share the same verdict without duplicate reports. Resolve receivers assigned inside try blocks while discarding stale input facts and preserving argument-dependent return types through PHPStan inference. Expose source method lookup for inherited and aliased trait methods while keeping constructor ownership intact. Cover a channel assigned from a typed parameter in the existing exact-catch fixture.
Report swallowed cancellation around recognized synchronous database, Redis, HTTP and wait operations in request, job and operation handlers. Follow forwarded methods, model traits and exception causes while respecting overrides, deferred work and schema defaults that only throw. Track exception constructor files for warm result-cache invalidation. Add default-on checks for raw coroutine spawning, direct Socialite context-property access, Swoole tables allocated in provider bodies and request-dependent BindWhen conditions. Inspect condition callables in their own source namespace and retain explicit reasons for framework-owned raw coroutine use. Add focused rule fixtures for accepted alternatives, distinct dispatch paths and verified regressions. Validation includes formatting, framework analysis, targeted rule and cache tests, and the PHPStan test suite.
Record the implemented receiver and callable analysis, supported synchronous operation families, exception propagation checks and cache dependencies. Describe the verified exclusions and regression coverage, and move remaining work to documentation, final verification and approved guidance updates.
Explain the new checks with corrective examples, configuration switches and supported exceptions. Describe deferred execution and receiver ownership boundaries without promising analysis of unknown callbacks or repositories. Update the rule index, method-attribute guidance and worker-context coverage. Record the config-repository typing difference from Larastan.
Explain that PHPStan checks restricted and unsupported methods. Replace the blanket redundant-singleton example with guidance that preserves intentional binding, optional injection and rebinding behavior, and correct the alias-registration description. Record documentation completion and the remaining benchmark and final review. Hold PR creation until the owner has reviewed completion and discussed next steps.
Capture the vendor directory chosen when creating a disposable skeleton copy and expose it alongside the existing skeleton source path. Preserve its lexical spelling so consumers can observe later symlink changes, and reset it with the runtime ownership state. Extend the runtime-copy test to verify the recorded path matches the generated link and restore the field with the other isolated test state.
Track Composer metadata through the skeleton and working vendor paths instead of the disposable runtime directory. Record skeleton vendor directory presence separately because even an empty directory suppresses the working vendor fallback. Preserve ordinary file and symlink dependency handling. Cover unchanged cache reuse, working vendor symlink changes, empty skeleton vendor transitions and newly supplied skeleton metadata. Targeted cache and bootstrap tests, framework analysis, the PHPStan suite and Testbench suite pass. Complete the rules plan and final integration review. Warm analysis now reuses results across runtime copies; retain the owner discussion gate before PR creation.
Lead new diagnostic sections with recommended framework usage, shorten dense coverage explanations and remove repeated diagnostic transcripts. Preserve configuration switches, inference limits and legitimate exceptions. Clarify facade lifetimes, configuration repository ownership and native request API adaptations. Review the wording against the Processes documentation and verify links, anchors and code fences.
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the
|
|
@cubic-dev-ai review |
@binaryfire I have started the AI code review. It will take a few minutes to complete. |
PR Summary by QodoAdd PHPStan checks for long-lived worker hazards
AI Description
Diagram
High-Level Assessment
Files changed (196)
|
Code Review by Qodo
1.
|
There was a problem hiding this comment.
21 issues found across 196 files
Confidence score: 3/5
NoPipeSeparatedValidationRulesRule.phpcan throw aTypeErrorwhen PHPStan analyzes a first-class callable. Restore the early return for variadic placeholders.NoMutableDatesRule.phpmisses aliased or imported Carbon classes because it matches the source spelling. Resolve the class throughScopebefore matching.WorkerStateCallCollector.phptreats first-class callables as executed calls, which can produce incorrect helper-call or config-write records. Skip those callables.InteractsWithPooledFilesystem.phplets callers choose concrete callback subtypes that runtime does not guarantee. Tighten the templates to the types the pooled filesystem actually provides.
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="tests/PHPStan/Rules/NoNativeRequestApisRuleTest.php">
<violation number="1" location="tests/PHPStan/Rules/NoNativeRequestApisRuleTest.php:38">
P3: The fixture and assertions cover only 7 of the 16 native APIs and superglobals the rule maps. Add fixture lines for the remaining entries (e.g. `setcookie`, `headers_list`, `connection_status`, `$_COOKIE`, `$_REQUEST`) so each tip mapping is asserted.</violation>
</file>
<file name="tests/PHPStan/WorkerStateDependencyTest.php">
<violation number="1" location="tests/PHPStan/WorkerStateDependencyTest.php:75">
P2: This facade test runs with `reuseCollectedDataRuleErrors` at its default `false`, so the warm-cache path its docblock describes is never exercised. Pass `reuseCollectedDataRuleErrors: true` to both `analyse()` calls, as the other cache-recheck tests do, or parametrize it like the first two tests.</violation>
</file>
<file name="src/testbench/src/Bootstrapper.php">
<violation number="1" location="src/testbench/src/Bootstrapper.php:136">
P3: The `runtimeVendorPath()` docblock says this path is linked "when its skeleton has none", but the value is stored on every copy, including when the skeleton supplies its own `vendor` link. Reword it to say it is the working package's vendor directory, which is tracked for Composer metadata whether or not the copy links it.</violation>
</file>
<file name="src/foundation/src/Testing/Concerns/RunTestsInCoroutine.php">
<violation number="1" location="src/foundation/src/Testing/Concerns/RunTestsInCoroutine.php:155">
P2: This suppression's reason does not meet the documented bar for raw coroutines, which requires the callback to catch every exception itself. The watchdog only catches `TimeoutException`, so any other Throwable escaping it would stop the worker. Either make the callback catch every Throwable, or state the actual reason the escape cannot happen.</violation>
</file>
<file name="src/phpstan/src/Rules/NoMutableDatesRule.php">
<violation number="1" location="src/phpstan/src/Rules/NoMutableDatesRule.php:57">
P2: This lookup uses the source spelling instead of the resolved class name, so imported or aliased `Carbon` constructions and static calls are silently missed. Resolve `$node->class` through `Scope` before matching `MUTABLE_CLASSES` and formatting the subject.</violation>
</file>
<file name="src/phpstan/src/Rules/Coroutine/NoCoroutinesInTransactionsRule.php">
<violation number="1" location="src/phpstan/src/Rules/Coroutine/NoCoroutinesInTransactionsRule.php:134">
P2: This removal makes the rule report first-class callable expressions as if they started a coroutine, although `Coroutine::create(...)` only creates a callable and does not execute it. Restore the first-class-callable guard to avoid a false positive (and an extra diagnostic for the existing line-31 fixture).</violation>
</file>
<file name="src/phpstan/src/Rules/UnsupportedCallRule.php">
<violation number="1" location="src/phpstan/src/Rules/UnsupportedCallRule.php:70">
P2: This reports first-class callables like `$service->bare(...)`, which only create a Closure and never call the method. Skip them in the early return, using `$node->isFirstClassCallable()`, as the sibling rules do.</violation>
</file>
<file name="src/phpstan/src/Rules/Validation/NoPipeSeparatedValidationRulesRule.php">
<violation number="1" location="src/phpstan/src/Rules/Validation/NoPipeSeparatedValidationRulesRule.php:130">
P1: Removing this early return makes first-class callable calls reach `getArgumentValues()`, where `array_any` type-hints `Arg` and throws a TypeError on the `VariadicPlaceholder`. Restore the guard so `validator(...)` and `$request->validate(...)` return early.</violation>
</file>
<file name="tests/PHPStan/RuntimeInputsPackageDependencyTest.php">
<violation number="1" location="tests/PHPStan/RuntimeInputsPackageDependencyTest.php:73">
P3: The test name says 'VendorSourceChanges', but it exercises only Composer metadata (`installed.json`) and vendor-link repointing. Rename it to something like `testVendorMetadataChangesReanalyzeTheirConsumers` so the name matches what is covered.</violation>
</file>
<file name="src/phpstan/src/Collectors/WorkerStateCallCollector.php">
<violation number="1" location="src/phpstan/src/Collectors/WorkerStateCallCollector.php:83">
P2: Removing `$node->isFirstClassCallable()` here lets `$this->helper(...)` and `$config->set(...)` through. Both create a Closure and run nothing, but they are now recorded as a helper call or a config write, which can produce false positives. Restore the guard. Resolution and route-action lookups already ignore first-class callables, so restoring it does not affect them.</violation>
</file>
<file name="tests/PHPStan/Rules/UnsupportedCallRuleTest.php">
<violation number="1" location="tests/PHPStan/Rules/UnsupportedCallRuleTest.php:73">
P3: The comment says the extender can return another object, but this closure returns its argument unchanged. Return a different `Service` so the test covers the case the comment describes.</violation>
</file>
<file name="tests/Support/SupportFacadeNonCoroutineTest.php">
<violation number="1" location="tests/Support/SupportFacadeNonCoroutineTest.php:23">
P2: This test replaces the global `Facade` application with a bare container and never restores it. Add a `tearDown()` that calls `Facade::clearResolvedInstances()` and `NonCoroutineFacadeStub::setFacadeApplication(null)` before `parent::tearDown()`, so later teardown and tests do not resolve facades against this container.</violation>
</file>
<file name="tests/PHPStan/Rules/UncastEnvValueRuleTest.php">
<violation number="1" location="tests/PHPStan/Rules/UncastEnvValueRuleTest.php:28">
P3: `testConfigFile()` never asserts the alias and case-insensitive reader paths. `Environment::GET('foo')` lives in `env-readers.php`, which is not a config file, so it never reaches the rule's reporting. Add an uncast alias read such as `Environment::GET('ALIAS_PORT', 8080)` to `data/config/uncast-env.php` and expect its error here.</violation>
</file>
<file name="tests/PHPStan/Rules/TypedConfigGetterRuleTest.php">
<violation number="1" location="tests/PHPStan/Rules/TypedConfigGetterRuleTest.php:50">
P2: `testPackageApplication` does not set the package flag itself, so it depends on the ambient bootstrap state. Set it to `true` explicitly and restore the previous value, as `testApplication` does.</violation>
</file>
<file name="src/phpstan/src/Rules/MigrationColumnTypeRule.php">
<violation number="1" location="src/phpstan/src/Rules/MigrationColumnTypeRule.php:41">
P2: The `json` branch fires on every database driver, but the message and tip only hold on PostgreSQL. On MySQL, `jsonb()` produces the same `json` column, and on SQLite both produce `text` by default, so the advice changes nothing there. Scope this check to PostgreSQL in the message or in the rule's configuration.</violation>
</file>
<file name="src/phpstan/src/Rules/Coroutine/NoRawCoroutinesRule.php">
<violation number="1" location="src/phpstan/src/Rules/Coroutine/NoRawCoroutinesRule.php:91">
P3: Nullsafe calls such as `$coroutine?->execute()` are not checked. The comment assumes PHPStan treats them as `MethodCall`, but the sibling rules in this repo handle `NullsafeMethodCall` as a separate class. Either add `NullsafeMethodCall` to the check and to `FacadeHelper::getCalledMethods()`, or add a fixture that proves the current behavior.</violation>
<violation number="2" location="src/phpstan/src/Rules/Coroutine/NoRawCoroutinesRule.php:96">
P2: This branch also reports first-class callable syntax such as `EngineCoroutine::create(...)`. That form only creates a Closure and starts no coroutine, so skip it when `isFirstClassCallable()` is true.</violation>
</file>
<file name="src/phpstan/src/RuntimeInputsResultCacheValueExtension.php">
<violation number="1" location="src/phpstan/src/RuntimeInputsResultCacheValueExtension.php:92">
P3: This inserts an optional parameter before the existing `$directories` argument, breaking callers that use `createKey()` positionally. Append `$presenceDirectories` after the existing parameters, or require named arguments without changing the established positional order.</violation>
</file>
<file name="src/docs/porting-from-laravel.md">
<violation number="1" location="src/docs/porting-from-laravel.md:550">
P3: Renaming this published fragment breaks existing links to the porting guide. Retain `streamed-responses` as a legacy alias before the new anchor.</violation>
</file>
<file name="src/phpstan/src/Collectors/RestrictedCallCollector.php">
<violation number="1" location="src/phpstan/src/Collectors/RestrictedCallCollector.php:82">
P2: This collector treats a first-class callable as an executed restricted call. Exclude `isFirstClassCallable()` nodes; creating `$registry->fake(...)` does not invoke `fake()`.</violation>
</file>
<file name="src/filesystem/src/Concerns/InteractsWithPooledFilesystem.php">
<violation number="1" location="src/filesystem/src/Concerns/InteractsWithPooledFilesystem.php:542">
P2: These method-level input templates let each caller choose `TDriver`, `TAdapter`, or `TClient`, so PHPStan accepts callbacks requiring arbitrary concrete subtypes that runtime does not guarantee. For example, the added type test accepts `AwsS3V3Adapter` for any `FilesystemAdapter`, although `withAdapter()` invokes the callback with `$this->adapter`, and `ClientPooledFilesystem` is used for both S3 and GCS stacks. Keep only the return template and type each callback parameter as its declared runtime type, or bind a concrete type at the class level.</violation>
</file>
Reply with feedback, questions, or to request a fix.
View guided diff | Turn on auto-fix | Re-trigger cubic
| */ | ||
| public function processNode(Node $node, Scope $scope): array | ||
| { | ||
| if ($node->isFirstClassCallable()) { |
There was a problem hiding this comment.
P1: Removing this early return makes first-class callable calls reach getArgumentValues(), where array_any type-hints Arg and throws a TypeError on the VariadicPlaceholder. Restore the guard so validator(...) and $request->validate(...) return early.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At src/phpstan/src/Rules/Validation/NoPipeSeparatedValidationRulesRule.php, line 130:
<comment>Removing this early return makes first-class callable calls reach `getArgumentValues()`, where `array_any` type-hints `Arg` and throws a TypeError on the `VariadicPlaceholder`. Restore the guard so `validator(...)` and `$request->validate(...)` return early.</comment>
<file context>
@@ -127,10 +127,6 @@ public function getNodeType(): string
- return [];
- }
-
$entry = match (true) {
$node instanceof FuncCall => $this->matchFunction($node, $scope),
$node instanceof New_ => $this->matchConstructor($node, $scope),
</file context>
There was a problem hiding this comment.
This can't happen. PHPStan never passes a first-class callable to a rule as a FuncCall, MethodCall or New_. Before rules and collectors run, it replaces validator(...) and $request->validate(...) with its own callable nodes (FunctionCallableNode, MethodCallableNode and so on), so getArgumentValues() never sees a VariadicPlaceholder. The old guard could never be true, which is why it was removed.
The fixture covers this: $request->validate(...) at line 39 of correct-validation-rules.php passes without an error.
|
|
||
| $this->edit($applicationPath . '/app/ReportFacade.php', 'return SupportedReporter::class;', 'return UnsupportedReporter::class;'); | ||
|
|
||
| $this->assertSame(['Reporting.php:11 hypervel.unsupportedCall'], $this->diagnostics($this->analyse($applicationPath), 1)); |
There was a problem hiding this comment.
P2: This facade test runs with reuseCollectedDataRuleErrors at its default false, so the warm-cache path its docblock describes is never exercised. Pass reuseCollectedDataRuleErrors: true to both analyse() calls, as the other cache-recheck tests do, or parametrize it like the first two tests.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At tests/PHPStan/WorkerStateDependencyTest.php, line 75:
<comment>This facade test runs with `reuseCollectedDataRuleErrors` at its default `false`, so the warm-cache path its docblock describes is never exercised. Pass `reuseCollectedDataRuleErrors: true` to both `analyse()` calls, as the other cache-recheck tests do, or parametrize it like the first two tests.</comment>
<file context>
@@ -39,4 +39,73 @@ public function testChangedLifetimesRecheckUnchangedWrites(bool $reuseCollectedD
+
+ $this->edit($applicationPath . '/app/ReportFacade.php', 'return SupportedReporter::class;', 'return UnsupportedReporter::class;');
+
+ $this->assertSame(['Reporting.php:11 hypervel.unsupportedCall'], $this->diagnostics($this->analyse($applicationPath), 1));
+ }
+
</file context>
| $this->assertSame(['Reporting.php:11 hypervel.unsupportedCall'], $this->diagnostics($this->analyse($applicationPath), 1)); | |
| $this->assertSame(['Reporting.php:11 hypervel.unsupportedCall'], $this->diagnostics($this->analyse($applicationPath, reuseCollectedDataRuleErrors: true), 1)); |
There was a problem hiding this comment.
Leaving this as is. reuseCollectedDataRuleErrors only changes how rules that read collected data behave on a warm cache. The unsupported call rule is a plain rule, so the flag makes no difference to it. The second run still goes through the result cache, which is the path this test checks.
| $completed->push(true); | ||
| }); | ||
|
|
||
| // @phpstan-ignore hypervel.rawCoroutine (the test deadline watchdog runs no application code, catches the timeout it throws, and nothing cancels it) |
There was a problem hiding this comment.
P2: This suppression's reason does not meet the documented bar for raw coroutines, which requires the callback to catch every exception itself. The watchdog only catches TimeoutException, so any other Throwable escaping it would stop the worker. Either make the callback catch every Throwable, or state the actual reason the escape cannot happen.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At src/foundation/src/Testing/Concerns/RunTestsInCoroutine.php, line 155:
<comment>This suppression's reason does not meet the documented bar for raw coroutines, which requires the callback to catch every exception itself. The watchdog only catches `TimeoutException`, so any other Throwable escaping it would stop the worker. Either make the callback catch every Throwable, or state the actual reason the escape cannot happen.</comment>
<file context>
@@ -152,6 +152,7 @@ protected function enforceCoroutineTestTimeLimit(?TimeoutException &$timeoutExce
$completed->push(true);
});
+ // @phpstan-ignore hypervel.rawCoroutine (the test deadline watchdog runs no application code, catches the timeout it throws, and nothing cancels it)
Coroutine::create(static function () use ($root, $deadline, $completed, &$timeoutException, &$rootCompleted): void {
$current = Coroutine::getCid();
</file context>
There was a problem hiding this comment.
Leaving this as is. Apart from native calls, the only thing in the watchdog that can throw is pcntl_signal_dispatch(), which runs PHPUnit's alarm handler. That handler throws the same TimeoutException the callback catches. Nothing else in the callback runs application code, and nothing cancels the watchdog, so nothing can escape it.
| if (! ($node instanceof New_ || $node instanceof StaticCall) | ||
| || ! $node->class instanceof Name | ||
| || $node->class->isSpecialClassName() | ||
| || ! isset(self::MUTABLE_CLASSES[strtolower($node->class->toString())]) |
There was a problem hiding this comment.
P2: This lookup uses the source spelling instead of the resolved class name, so imported or aliased Carbon constructions and static calls are silently missed. Resolve $node->class through Scope before matching MUTABLE_CLASSES and formatting the subject.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At src/phpstan/src/Rules/NoMutableDatesRule.php, line 57:
<comment>This lookup uses the source spelling instead of the resolved class name, so imported or aliased `Carbon` constructions and static calls are silently missed. Resolve `$node->class` through `Scope` before matching `MUTABLE_CLASSES` and formatting the subject.</comment>
<file context>
@@ -0,0 +1,87 @@
+ if (! ($node instanceof New_ || $node instanceof StaticCall)
+ || ! $node->class instanceof Name
+ || $node->class->isSpecialClassName()
+ || ! isset(self::MUTABLE_CLASSES[strtolower($node->class->toString())])
+ ) {
+ return [];
</file context>
There was a problem hiding this comment.
Names are already resolved here. PHPStan runs its name resolver before any rule, so $node->class holds the fully qualified name, not the spelling in the source. The fixture checks this: it imports Carbon\Carbon as BaseCarbon, and new BaseCarbon is reported as new Carbon\Carbon.
|
|
||
| // An extender can return another object. | ||
| $container->singleton('unsupported.extended', static fn (): Service => new Service); | ||
| $container->extend('unsupported.extended', static fn (Service $service): Service => $service); |
There was a problem hiding this comment.
P3: The comment says the extender can return another object, but this closure returns its argument unchanged. Return a different Service so the test covers the case the comment describes.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At tests/PHPStan/Rules/UnsupportedCallRuleTest.php, line 73:
<comment>The comment says the extender can return another object, but this closure returns its argument unchanged. Return a different `Service` so the test covers the case the comment describes.</comment>
<file context>
@@ -0,0 +1,81 @@
+
+ // An extender can return another object.
+ $container->singleton('unsupported.extended', static fn (): Service => new Service);
+ $container->extend('unsupported.extended', static fn (Service $service): Service => $service);
+
+ // Two factories on one line, which reflection's line numbers cannot tell apart.
</file context>
| $container->extend('unsupported.extended', static fn (Service $service): Service => $service); | |
| $container->extend('unsupported.extended', static fn (Service $service): Service => new Service); |
There was a problem hiding this comment.
The comment was wrong, not the fixture. The rule can't know what an extender returns, so it doesn't report calls through an extended binding, whatever the extender's body does. The comment now says that. Fixed in 3027aa5.
| ]; | ||
|
|
||
| // The readers fixture declares an Env subclass with its own get(). | ||
| $this->analyse([__DIR__ . '/data/config/uncast-env.php', __DIR__ . '/data/env-readers.php'], [ |
There was a problem hiding this comment.
P3: testConfigFile() never asserts the alias and case-insensitive reader paths. Environment::GET('foo') lives in env-readers.php, which is not a config file, so it never reaches the rule's reporting. Add an uncast alias read such as Environment::GET('ALIAS_PORT', 8080) to data/config/uncast-env.php and expect its error here.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At tests/PHPStan/Rules/UncastEnvValueRuleTest.php, line 28:
<comment>`testConfigFile()` never asserts the alias and case-insensitive reader paths. `Environment::GET('foo')` lives in `env-readers.php`, which is not a config file, so it never reaches the rule's reporting. Add an uncast alias read such as `Environment::GET('ALIAS_PORT', 8080)` to `data/config/uncast-env.php` and expect its error here.</comment>
<file context>
@@ -0,0 +1,51 @@
+ ];
+
+ // The readers fixture declares an Env subclass with its own get().
+ $this->analyse([__DIR__ . '/data/config/uncast-env.php', __DIR__ . '/data/env-readers.php'], [
+ $error("env('DB_PORT')", 'an int', 14),
+ $error("Hypervel\\Support\\Env::get('APP_DEBUG')", 'a bool', 15),
</file context>
There was a problem hiding this comment.
Leaving this as is. Import aliases are already covered here: TestbenchEnv::get() at line 30 of the config fixture goes through an aliased import, and the test expects its error. The method name check is a plain lowercase comparison, so an uppercase GET doesn't need a case of its own.
| if ($function !== null && strtolower($function) === self::FUNCTION) { | ||
| $spawn = $function; | ||
| } | ||
| } elseif (($node instanceof MethodCall || $node instanceof StaticCall) |
There was a problem hiding this comment.
P3: Nullsafe calls such as $coroutine?->execute() are not checked. The comment assumes PHPStan treats them as MethodCall, but the sibling rules in this repo handle NullsafeMethodCall as a separate class. Either add NullsafeMethodCall to the check and to FacadeHelper::getCalledMethods(), or add a fixture that proves the current behavior.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At src/phpstan/src/Rules/Coroutine/NoRawCoroutinesRule.php, line 91:
<comment>Nullsafe calls such as `$coroutine?->execute()` are not checked. The comment assumes PHPStan treats them as `MethodCall`, but the sibling rules in this repo handle `NullsafeMethodCall` as a separate class. Either add `NullsafeMethodCall` to the check and to `FacadeHelper::getCalledMethods()`, or add a fixture that proves the current behavior.</comment>
<file context>
@@ -0,0 +1,118 @@
+ if ($function !== null && strtolower($function) === self::FUNCTION) {
+ $spawn = $function;
+ }
+ } elseif (($node instanceof MethodCall || $node instanceof StaticCall)
+ && $node->name instanceof Identifier
+ && isset(self::METHODS[strtolower($node->name->toString())])
</file context>
There was a problem hiding this comment.
PHPStan already covers this. When it analyzes $coroutine?->execute(), it builds a plain MethodCall from the nullsafe call and runs method call rules on that, so this branch reports it. I checked it with ?->execute() on a nullable Hypervel\Engine\Coroutine, and it's reported once.
| public function createKey( | ||
| array $files = [], | ||
| array $presenceFiles = [], | ||
| array $presenceDirectories = [], |
There was a problem hiding this comment.
P3: This inserts an optional parameter before the existing $directories argument, breaking callers that use createKey() positionally. Append $presenceDirectories after the existing parameters, or require named arguments without changing the established positional order.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At src/phpstan/src/RuntimeInputsResultCacheValueExtension.php, line 92:
<comment>This inserts an optional parameter before the existing `$directories` argument, breaking callers that use `createKey()` positionally. Append `$presenceDirectories` after the existing parameters, or require named arguments without changing the established positional order.</comment>
<file context>
@@ -86,6 +89,7 @@ public function __construct(
public function createKey(
array $files = [],
array $presenceFiles = [],
+ array $presenceDirectories = [],
array $directories = [],
array $linkedDirectories = [],
</file context>
There was a problem hiding this comment.
Leaving this as is. Every caller passes named arguments, and the new parameter sits next to $presenceFiles, which it pairs with. This is unreleased 0.4 code, so there are no outside callers passing positional arguments.
|
|
||
| <a name="streamed-responses"></a> | ||
| ### Streamed Responses | ||
| <a name="native-request-functions"></a> |
There was a problem hiding this comment.
P3: Renaming this published fragment breaks existing links to the porting guide. Retain streamed-responses as a legacy alias before the new anchor.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At src/docs/porting-from-laravel.md, line 550:
<comment>Renaming this published fragment breaks existing links to the porting guide. Retain `streamed-responses` as a legacy alias before the new anchor.</comment>
<file context>
@@ -549,8 +547,10 @@ Hypervel installs `hypervel/guzzle`, a Guzzle 8 fork that replaces `guzzlehttp/g
-<a name="streamed-responses"></a>
-### Streamed Responses
+<a name="native-request-functions"></a>
+### Native Request Functions
+
</file context>
| <a name="native-request-functions"></a> | |
| <a name="streamed-responses"></a> | |
| <a name="native-request-functions"></a> |
There was a problem hiding this comment.
Leaving this as is. Nothing links to the old anchor, and 0.4 hasn't been released, so there are no published links to keep working.
[High impact] The PR appears safe to merge, with one non-blocking missed warning for reference destructuring.
|
Apply nested detachments after earlier escapes in the same statement, rebuilding only the names an unset removed. Follow detached closure references without revisiting cycles, and invalidate both sides of foreach and destructuring reference bindings while preserving earlier value copies. Recognize external storage aliases, conditional expression results, coalescing assignments and exact positional destructuring stores. Leave unknown array elements and property aliases unclassified rather than treating every value as borrowed. Extend regression coverage for return and spawn ordering, detached captures, reference source writes, external stores and corresponding local values that remain safe.
Collect foreach targets, catch bindings and writes through aliases of global storage. Distinguish reference rebinding from mutation so detached local names do not retain global ownership. Match call arguments to PHPStan write events by source position and function identity. This catches reference outputs without duplicating callee resolution, while excluding ordinary arguments and first-class callable creation. Add regression cases for global aliases, reference outputs, nullsafe calls and detached bindings. Update the analysis plan with the implemented boundaries and completed review state.
|
Macroscope skipped reviewing this pull request. Per-PR cost limit exceeded (workspace setting). Reviews on this PR have cost $58.29 so far. This review would add an estimated $10.82, bringing the total to $69.12 — above your per-PR limit of $60.00. Tip To get this pull request reviewed, you can:
|
|
@cubic-dev-ai review |
@binaryfire I have started the AI code review. It will take a few minutes to complete. |
There was a problem hiding this comment.
No issues found across 222 files
Confidence score: 5/5
- Automated review surfaced no issues in the provided summaries.
- No files require special attention.
Note: This PR contains a large number of files. cubic selects up to 200 of the highest-priority eligible files for this review, so some files may not have been reviewed.
You've manually re-run cubic several times on this PR. Each manual re-review checks the full PR again and counts toward your usage quota. To preserve your usage limits, we recommend letting cubic automatically review new commits.
View guided diff | Turn on auto-fix | Re-trigger cubic
A nested unset rebuilt only its own variable, leaving later copies with stale borrowed values from the full-statement pass. Carry a separate environment through successive detachments and rebuild later destinations from it. Retain earlier detachments and removed live-reference names while preserving fixed captures and copies made before the unset. Apply each detaching node’s own write in the following segment so rebinding cannot restore an old value. Add regressions for cleared arrays, consecutive unsets, copied reference captures and reference rebinding, alongside an escaping copy after an unrelated unset. Update the analysis plan to describe the bounded traversal.
Replace timer-based ordering with channels so both readers resolve their initial roots before clearing, and neither resolves again until clearing finishes. Keep the identity assertions that verify invalidation in live coroutines. Check bounded channel waits so a failed child cannot leave the other callbacks waiting indefinitely. This removes scheduler timing from the test without changing facade behavior.
Rebuilding every later destination discarded independent facts carried between loop iterations. Limit rebuilding to unset names and later writes that read those names, following transitive copies with the existing expression visitor. Preserve other facts from the full-statement pass while retaining the ordered detachment handling for affected values. Share the expression reader with arrow-function capture analysis without changing capture boundaries. Cover the restored loop-carried escape and safe cleared-array and consecutive-unset cases. Update the plan to describe the targeted rebuild and its remaining syntactic limits.
Avoid carrying later writes backward through simple branches. Classify statements in one preorder pass, retaining repeated propagation for loops, jumps and nested assignments without inspecting nested function or class bodies. Evaluate rebuilt values from their ordered inputs and merge carried facts when repeated propagation is needed. Merge fixed captures and live reference names for each closure identity so later iterations cannot lose evidence behind an earlier empty capture. Add regression coverage for safe branch copies, nested writes and returned closures, including closures carried through arrays and loop iterations. Update the analysis plan with the transfer rules and precision boundaries.
|
@cubic-dev-ai review |
@binaryfire I have started the AI code review. It will take a few minutes to complete. |
There was a problem hiding this comment.
No issues found across 222 files
Confidence score: 5/5
- Automated review surfaced no issues in the provided summaries.
- No files require special attention.
Note: This PR contains a large number of files. cubic selects up to 200 of the highest-priority eligible files for this review, so some files may not have been reviewed.
You've manually re-run cubic several times on this PR. Each manual re-review checks the full PR again and counts toward your usage quota. To preserve your usage limits, we recommend letting cubic automatically review new commits.
View guided diff | Turn on auto-fix | Re-trigger cubic
Replace statement-wide write replay with ordered expression and statement transfers. Keep branch, loop and finally completion states separate, preserve captures at creation, and distinguish initial loop entry from later iterations. Retain bounded null and nonempty-array facts while invalidating them at calls and mutations. Detect borrowed-resource use in generator callbacks and cover ordinary escapes, resets, completion paths and the established reference limits.
A generator callback returns before its body runs, so transaction() completes before the deferred database work executes. Report literal generator callbacks while respecting transaction overrides and excluding nested generator bodies. Document generator callback guidance and reference-tracking limits, and replace the borrowed-resource plan narrative with the final control-flow design and focused verification categories.
|
@cubic-dev-ai review |
@binaryfire I have started the AI code review. It will take a few minutes to complete. |
There was a problem hiding this comment.
2 issues found across 222 files
Confidence score: 3/5
ManagedTransactionRule::matchOwner()misses direct aliases of the callback connection, so$alias->commit()can evade the rule. Propagate owner facts through unambiguous assignments and invalidate them on reassignment.BorrowedResourceEscapeRule::findLender()does not recognizeFilesystemAdapter, so unpooledwithDriver()andwithAdapter()callbacks are not checked. AddFilesystemAdaptertoLENDERS.
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="src/phpstan/src/Rules/ManagedTransactionRule.php">
<violation number="1" location="src/phpstan/src/Rules/ManagedTransactionRule.php:377">
P2: `matchOwner()` misses direct aliases of the callback connection, allowing `$alias = $db; $alias->commit()` to evade the rule. Propagate owner facts through unambiguous assignments and invalidate them when reassigned.</violation>
</file>
<file name="src/phpstan/src/Rules/Coroutine/BorrowedResourceEscapeRule.php">
<violation number="1" location="src/phpstan/src/Rules/Coroutine/BorrowedResourceEscapeRule.php:173">
P2: `findLender()` matches the declaring class against `LENDERS`, but `FilesystemAdapter` is absent, so unpooled `withDriver()` and `withAdapter()` callbacks are never checked. Add `FilesystemAdapter` to the map.</violation>
</file>
Note: This PR contains a large number of files. cubic selects up to 200 of the highest-priority eligible files for this review, so some files may not have been reviewed.
You've manually re-run cubic several times on this PR. Each manual re-review checks the full PR again and counts toward your usage quota. To preserve your usage limits, we recommend letting cubic automatically review new commits.
View guided diff | Turn on auto-fix | Re-trigger cubic
| $receiver = $call->var; | ||
|
|
||
| if ($receiver instanceof Variable && is_string($receiver->name)) { | ||
| if (isset($owners[$receiver->name]) || $owner === '$' . $receiver->name) { |
There was a problem hiding this comment.
P2: matchOwner() misses direct aliases of the callback connection, allowing $alias = $db; $alias->commit() to evade the rule. Propagate owner facts through unambiguous assignments and invalidate them when reassigned.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At src/phpstan/src/Rules/ManagedTransactionRule.php, line 377:
<comment>`matchOwner()` misses direct aliases of the callback connection, allowing `$alias = $db; $alias->commit()` to evade the rule. Propagate owner facts through unambiguous assignments and invalidate them when reassigned.</comment>
<file context>
@@ -0,0 +1,424 @@
+ $receiver = $call->var;
+
+ if ($receiver instanceof Variable && is_string($receiver->name)) {
+ if (isset($owners[$receiver->name]) || $owner === '$' . $receiver->name) {
+ return $this->runsConnectionMethod($ownerType, $method, $scope);
+ }
</file context>
There was a problem hiding this comment.
This is missed coverage rather than a false report. The rule follows the connection forms it can identify: the callback's parameter, a captured variable, a $this property, DB:: and a literal DB::connection() name. A local alias is an owner it doesn't know, so it stops checking instead of guessing. We're keeping that boundary for now. Committing through a fresh alias inside transaction() is uncommon, and following aliases would mean tracking assignments through the whole callback.
| QueuePoolProxy::class => ['withconnection' => 'sqs'], | ||
| // The classes using InteractsWithPooledFilesystem; an unpooled disk lends its own driver. | ||
| FilesystemPoolProxy::class => ['withdriver' => 'driver', 'withadapter' => 'adapter', 'withclient' => 'client'], | ||
| ClientPooledFilesystem::class => ['withdriver' => 'driver', 'withadapter' => 'adapter', 'withclient' => 'client'], |
There was a problem hiding this comment.
P2: findLender() matches the declaring class against LENDERS, but FilesystemAdapter is absent, so unpooled withDriver() and withAdapter() callbacks are never checked. Add FilesystemAdapter to the map.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At src/phpstan/src/Rules/Coroutine/BorrowedResourceEscapeRule.php, line 173:
<comment>`findLender()` matches the declaring class against `LENDERS`, but `FilesystemAdapter` is absent, so unpooled `withDriver()` and `withAdapter()` callbacks are never checked. Add `FilesystemAdapter` to the map.</comment>
<file context>
@@ -0,0 +1,2439 @@
+ QueuePoolProxy::class => ['withconnection' => 'sqs'],
+ // The classes using InteractsWithPooledFilesystem; an unpooled disk lends its own driver.
+ FilesystemPoolProxy::class => ['withdriver' => 'driver', 'withadapter' => 'adapter', 'withclient' => 'client'],
+ ClientPooledFilesystem::class => ['withdriver' => 'driver', 'withadapter' => 'adapter', 'withclient' => 'client'],
+ Connection::class => ['withpinnedsession' => 'session'],
+ DB::class => ['withpinnedsession' => 'session'],
</file context>
There was a problem hiding this comment.
This is on purpose. An unpooled FilesystemAdapter passes its own driver and adapter to the callback, so there's no pool for them to go back to and nothing that can be used after a borrow ends. Only the pooled disks lend something they reclaim when the callback returns, which is why they're the only filesystem classes in the map.
Use one exception path for explicit throws and calls, and exclude unmatched exceptions when a Throwable catch handles them. Preserve exceptions raised by catch bodies and run finally with the resulting completion state. Visit static initializers, computed method names and destructuring or foreach targets through the existing evaluator. Preserve skipped initialization and nullsafe paths, and cover the restored escape reports and catch-all cleanup behavior.
Replace separate target evaluation and write passes with one ordered assignment routine. Destructuring applies each target before the next, foreach assigns its value before its key, and unset detaches names in order. Apply reference invalidation at the affected target without introducing alias tracking. Preserve earlier escape reports when a later target throws and cover ordering across ordinary, reference and foreach targets.
| if ($byReference) { | ||
| if ($target instanceof Variable && is_string($target->name)) { | ||
| $environment = $this->detach($environment, [$target->name], 'reference'); | ||
| } | ||
|
|
||
| return $this->bindReferences([$target, $node->expr], $environment); |
There was a problem hiding this comment.
Reference stores lose warnings
The new by-reference branch returns before checkStore(), so [&$this->client] = [$connection]; inside withConnection() no longer reports hypervel.borrowedResourceEscape. The property still retains the borrowed connection after the callback ends. Previously, every known literal item was checked before reference facts were cleared.
Check property, static-property and global-offset stores before returning from this branch, while keeping plain-variable rebinding exempt.
There was a problem hiding this comment.
This one can't happen in code PHP will run. When a destructuring target is by-reference, the right-hand side has to be a variable or a call, so [&$this->client] = [$connection]; fails to compile with "Cannot assign reference to non referenceable value". The same goes for a literal holding &$connection and for nested lists. The form that does compile, [&$this->client] = $source;, reads an element out of an array, which the rule treats as unknown, the same as before this change.
There was a problem hiding this comment.
You're right—the literal-array example is not executable PHP. By-reference destructuring requires a referenceable RHS, so [&$this->client] = [$connection]; (and the nested/literal variants) cannot run. The valid variable-RHS form is handled as unknown by this rule, as you noted. I'll withdraw the finding; the proposed checkStore() change isn't warranted.
|
@cubic-dev-ai review |
@binaryfire I have started the AI code review. It will take a few minutes to complete. |
There was a problem hiding this comment.
No issues found across 222 files
Confidence score: 5/5
- Automated review surfaced no issues in the provided summaries.
- No files require special attention.
Note: This PR contains a large number of files. cubic selects up to 200 of the highest-priority eligible files for this review, so some files may not have been reviewed.
You've manually re-run cubic several times on this PR. Each manual re-review checks the full PR again and counts toward your usage quota. To preserve your usage limits, we recommend letting cubic automatically review new commits.
View guided diff | Turn on auto-fix | Re-trigger cubic
This adds a set of PHPStan rules for mistakes that are easy to make in a long-lived Swoole worker and hard to see in review: calling a boot-only method while handling a request, letting a pooled connection escape the callback that borrowed it, swallowing a coroutine cancellation, creating a Swoole table in every process, and similar. It builds on the method attributes from #741, which mark the methods these rules check.
Laravel code mostly runs unchanged on Hypervel, but a worker serves many requests and coroutines at once, so some patterns that are harmless under PHP-FPM leak state or misbehave here. The framework already documents these. These rules turn that guidance into diagnostics, each with a fix and, where there is one, the legitimate exception that should be ignored with a reason.
A few runtime changes came out of the work, so they're described first. They're in their own commits.
Runtime changes
bind()orTransientservice therefore kept the first request's object for every later request. The container gainsresolvedShared(), which reports whether it holds a worker-shared instance, including auto-singletons. Facades cache those for the worker as before. Other roots are kept per coroutine, so each request gets its own and concurrent requests never share one. Clearing a facade's resolved instance now makes later facade calls resolve again in every coroutine.dateis bound by the framework. TheDatefacade used to callswap()on first use whendatewasn't bound, which registered a container instance in the middle of a request.FoundationServiceProvidernow bindsdatetoDateFactorywithsingletonIf(). ADatefacade subclass'sDEFAULT_FACADEtherefore only applies whiledateis unbound; an application should binddateto its factory instead. This is listed in the Support README's differences.Connection.ConnectionResolverInterface::connection()and the resolvers implementing it now declare the driver-neutralConnection, notConnectionInterface. Every built-in andDB::extend()connection already is one, and Eloquent and the migrator already required it, so a resolver returning anything else was already broken. The change removes several ignores andinstanceofchecks. Laravel's contract documentsConnectionInterface, so this is listed in the Database README and the porting guide.withDriver(),withAdapter()andwithClient(), the RediswithPinnedConnection()methods and the SQS pool'swithConnection()are now generic over their callback's return type. The filesystem methods also accept a callback that narrows its parameter to the disk's concrete driver, adapter or client.logging.syslog.facilityreadLOG_SYSLOG_FACILITYwith the integerLOG_USERas its default. An environment variable is always a string, and Monolog only accepts facility names as strings, so the variable has to hold a name such aslocal0. The default is now'user', which Monolog maps toLOG_USER, in the framework config and both skeletons.LoadEnvironmentVariablesno longer callshttp_response_code(). It was left over from Laravel's FPM bootstrap and does nothing in Hypervel, which bootstraps from the CLI before any response exists.The rules
Each rule has its own section in the static analysis docs, with an example, the fix and its exceptions. Every rule except unsupported calls can be turned off with a parameter.
BootOnly,TestsOnlyorBootOrTestsOnlyfrom request, job or operation code, applying each marker's conditions. Also markers whose arguments would misstate the restriction.checkRestrictedCallsUnsupported, including its message.checkBorrowedResourcescommit()orrollBack()that ends a transactiontransaction()manages before its closure returns, androllBack(0).checkManagedTransactionsheader(),setcookie(),session_*(),$_GET,$_SESSIONand the other PHP functions and superglobals that act on the process instead of the request.noNativeRequestApischeckDiscardedDatesCarbon.noMutableDatesenv()read with a boolean, integer or float default that isn't cast.checkConfigEnvCastsconfig('key')orget()for a key that always holds one type, which should usestring(),integer()and so on. Only runs when analyzing an application.checkTypedConfigGettersobserve()in a service provider for a model the application owns, which should useObservedBy.checkObserverAttributesjson(),enum()andset()columns. Off by default, since existing migrations shouldn't be edited.checkMigrationColumnTypescheckBindingCyclesnoRawCoroutinescheckSocialiteProvidersnew Swoole\Tablein a service provider'sregister()orboot(), which every process runs.checkSwooleTablesBindWhencondition that reads the current request or user, which would bind that request's choice for the worker's lifetime.checkBindWhenConditionscheckCancellationHandlingThe framework's own reports are fixed or, where the code is intentional, ignored with a reason: for example the coroutine primitives themselves, Horizon's
ignore_user_abort(), and Sentry'sspotlightsetting, which accepts a boolean or a URL.Shared analysis
Several rules need to know where code runs and whether a value is the worker's shared instance.
OnReceiveandOnClose, and Eloquent observers registered withobserve()orObservedBy, are analyzed as code that runs during an operation. The existing worker-state checks use these contexts too.whenSharedrestriction is only reported when the call is known to reach the shared instance: through a facade whose service the container shares, a named application-container resolution such asapp('router'), or a dependency the container injects into a controller action, a job handler or a readonly promoted constructor property. A factory you create withnewis not reported.Configuration types only for the application repository
Configuration value types now apply only to reads from the application's repository: the
config()helper, theConfigfacade, and calls such asapp('config')->get(...). A repository received as a parameter or property, or built withnew Repository($items), may hold other values, so it keeps its declared return types. Larastan applies the application's configuration types toget()on any repository. This is listed in the PHPStan README's differences.Result cache for package analysis
Package analysis boots a Testbench copy of the skeleton that is deleted when the process exits. The result cache recorded the copy's Composer metadata by its path inside that copy, so every warm run saw it as missing and reanalyzed everything that depends on the booted application, about a third of this repository. The cache now records the metadata where Testbench takes it from: the skeleton's
vendordirectory and the working package's, by their own paths, including whether the skeleton'svendordirectory exists. Testbench'sBootstrapper::runtimeVendorPath()returns the working vendor directory for this. A warmcomposer analyseof this repository now reanalyzes nothing and takes a few seconds instead of about forty.Docs
AGENTS.mdsays PHPStan checks the lifetime attributes, rewords binding pattern 3 to say what explicit registration changes, and corrects the description of binding an alias.Validation
Formatting, PHPStan, the analyzer test suite, the Testbench suite and the Support tests pass. Each rule has tests for what it reports, the allowed alternatives and its documented exceptions, and the result cache changes have warm-cache tests. The skeleton was installed the way CI installs it and analyzed with these rules, with no reports.
Compared with
0.4, a coldcomposer analyseof this repository is a few percent slower. Turning the new rules off didn't measurably change that, and warm runs are much faster because of the result cache fix.Note
Add PHPStan rules for Hypervel's long-lived workers
WorkerLifetimesandWorkerStateHelperanalysis engine grows observer, listener, injection, and copy tracking to power these rulesFacade::resolveFacadeInstancenow keeps worker-shared roots worker-wide while other roots stay per-coroutine with generation-based invalidation onclearResolvedInstance(s);Container::resolvedShared()is added to the container contract;FoundationServiceProviderbindsdatetoDateFactory; the gRPC receiver andSafeSocketnow report cleanup throwables instead of swallowing them; the default syslog facility becomes the stringuserConnectionResolverInterfaceand implementations) now return the concreteConnectionclass instead ofConnectionInterface; tests and docs are updated to mockConnectionFacade::resolveFacadeInstancereturns null when no facade application is set, and non-shared facade roots are retained per coroutine rather than worker-wide — checkFacade.phpandFacadeState.phpconsumers;Datefacade fallback caches its default factory without binding it into the container; env-file errors no longer set an HTTP 500 statusMacroscope summarized c819511.