Skip to content

Add PHPStan rules for Hypervel's long-lived workers - #742

Open
binaryfire wants to merge 22 commits into
0.4from
feature/phpstan-hypervel-rules
Open

binaryfire wants to merge 22 commits into
0.4from
feature/phpstan-hypervel-rules

Conversation

@binaryfire

@binaryfire binaryfire commented Nov 13, 2026 •

Copy link
Copy Markdown
Member

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

  • Facade roots follow the container's lifetimes. A facade used to cache whatever it resolved for the whole worker, unless the service was scoped. A facade over a bind() or Transient service therefore kept the first request's object for every later request. The container gains resolvedShared(), 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.
  • date is bound by the framework. The Date facade used to call swap() on first use when date wasn't bound, which registered a container instance in the middle of a request. FoundationServiceProvider now binds date to DateFactory with singletonIf(). A Date facade subclass's DEFAULT_FACADE therefore only applies while date is unbound; an application should bind date to its factory instead. This is listed in the Support README's differences.
  • Database resolvers return Connection. ConnectionResolverInterface::connection() and the resolvers implementing it now declare the driver-neutral Connection, not ConnectionInterface. Every built-in and DB::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 and instanceof checks. Laravel's contract documents ConnectionInterface, so this is listed in the Database README and the porting guide.
  • Callback types. The pooled filesystem's withDriver(), withAdapter() and withClient(), the Redis withPinnedConnection() methods and the SQS pool's withConnection() 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.
  • Shipped logging config uses a named syslog facility. logging.syslog.facility read LOG_SYSLOG_FACILITY with the integer LOG_USER as 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 as local0. The default is now 'user', which Monolog maps to LOG_USER, in the framework config and both skeletons.
  • LoadEnvironmentVariables no longer calls http_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.
  • Testbench exposes the vendor directory its runtime copy links. See the result cache section below.

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.

Check Reports Parameter
Restricted calls Calls to methods marked BootOnly, TestsOnly or BootOrTestsOnly from request, job or operation code, applying each marker's conditions. Also markers whose arguments would misstate the restriction. checkRestrictedCalls
Unsupported calls Calls that reach a method marked Unsupported, including its message. none
Borrowed resources A pooled Redis connection, SQS client, filesystem driver or pinned PDO that outlives the callback that borrowed it: returned, stored, or captured by a coroutine or timer. checkBorrowedResources
Managed transactions commit() or rollBack() that ends a transaction transaction() manages before its closure returns, and rollBack(0). checkManagedTransactions
Native request APIs header(), setcookie(), session_*(), $_GET, $_SESSION and the other PHP functions and superglobals that act on the process instead of the request. noNativeRequestApis
Discarded dates An immutable date modifier whose result is thrown away. checkDiscardedDates
Mutable dates Creating a mutable Carbon. noMutableDates
Environment casts A config file env() read with a boolean, integer or float default that isn't cast. checkConfigEnvCasts
Typed config getters config('key') or get() for a key that always holds one type, which should use string(), integer() and so on. Only runs when analyzing an application. checkTypedConfigGetters
Model observers observe() in a service provider for a model the application owns, which should use ObservedBy. checkObserverAttributes
Migration column types json(), enum() and set() columns. Off by default, since existing migrations shouldn't be edited. checkMigrationColumnTypes
Binding cycles Binding a container key to a class that is an alias of that same key, which never finishes resolving. checkBindingCycles
Raw coroutines Coroutines started directly through Swoole or the engine layer, where an uncaught exception or cancellation stops the worker. noRawCoroutines
Socialite providers Reading or writing a provider property whose per-request value lives in coroutine context. checkSocialiteProviders
Swoole tables new Swoole\Table in a service provider's register() or boot(), which every process runs. checkSwooleTables
Conditional bindings A BindWhen condition that reads the current request or user, which would bind that request's choice for the worker's lifetime. checkBindWhenConditions
Swallowed cancellation A catch that handles a cancellation as an ordinary failure around a database query, Redis command, HTTP request or wait in request, job or operation code. checkCancellationHandling

The 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's spotlight setting, 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.

  • More runtime contexts. Request and job contexts now include listeners for the framework's request, routing and queue job events, and queued listeners and observers, which run as jobs. Listeners for server events such as OnReceive and OnClose, and Eloquent observers registered with observe() or ObservedBy, are analyzed as code that runs during an operation. The existing worker-state checks use these contexts too.
  • Shared receivers. A whenShared restriction 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 as app('router'), or a dependency the container injects into a controller action, a job handler or a readonly promoted constructor property. A factory you create with new is not reported.
  • Facade methods without running application code. The rules find the method a facade call reaches from its accessor and the booted container's registration, without resolving the facade or running factories.

Configuration types only for the application repository

Configuration value types now apply only to reads from the application's repository: the config() helper, the Config facade, and calls such as app('config')->get(...). A repository received as a parameter or property, or built with new Repository($items), may hold other values, so it keeps its declared return types. Larastan applies the application's configuration types to get() 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 vendor directory and the working package's, by their own paths, including whether the skeleton's vendor directory exists. Testbench's Bootstrapper::runtimeVendorPath() returns the working vendor directory for this. A warm composer analyse of this repository now reanalyzes nothing and takes a few seconds instead of about forty.

Docs

  • The static analysis docs have a section for each rule and an entry in the configuration and identifier tables. The Method Attributes section links to the restricted and unsupported method sections.
  • The facades docs describe the new lifetimes, and the porting guide covers the native request functions.
  • AGENTS.md says 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 cold composer analyse of 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.

View guided diff Turn on auto-fix

Note

Add PHPStan rules for Hypervel's long-lived workers and per-coroutine facade roots

  • Adds many PHPStan rules for worker safety: restricted calls (RestrictedCallRule), swallowed cancellation (SwallowedCancellationRule), borrowed-resource escapes, raw coroutines, mutable/discarded dates, managed transactions, binding cycles, request-dependent bindings, and more
  • Refactors worker-state analysis into a shared WorkerStateHelper/WorkerStateAnalysis model with a rewritten WorkerLifetimes that tracks event, listener, observer, and injection contexts
  • Changes Facade::resolveFacadeInstance to keep cached roots per coroutine for non-shared, non-scoped services, and adds Container::resolvedShared() (contract included) so the facade knows when a root is worker-shared
  • Narrows the database connection contract: resolvers and DatabaseQueue::getDatabase() now return Connection instead of ConnectionInterface (docs updated); also binds date to DateFactory in FoundationServiceProvider
  • Behavioral Change: check ConnectionResolverInterface::connection() return types (custom resolvers and test mocks must return Connection), facade root caching semantics in Facade.php, and Date::resolveFacadeInstance which now caches the default instance directly without swap()
📊 Macroscope summarized c329d6f. 99 files reviewed, 10 issues evaluated, 3 issues filtered, 7 comments posted

🗂️ Filtered Issues

src/phpstan/src/Collectors/WorkerStateClassCollector.php — 0 comments posted, 1 evaluated, 1 filtered
  • line 180: array_is_list($observables) unnecessarily drops every observer for models with keyed or non-contiguously indexed custom events, e.g. protected array $observables = [1 => 'archived'];. HasEvents::getObservableEvents() merges that array and registerObserver() iterates its values without requiring a list, so these observers really run. Returning [[], []] here suppresses analysis even of their built-in created/updated handlers. Accept string-valued arrays and normalize their values instead. [ Out of scope (post-validation triage) ]
src/phpstan/src/Rules/Coroutine/RestrictedAttributeRule.php — 0 comments posted, 1 evaluated, 1 filtered
  • line 100: The no-default check incorrectly rejects a variadic whenProvided condition. For example, #[BootOnly(whenProvided: 'items')] public function configure(string ...$items) can legitimately be called without items, but this emits “every call is restricted” and recommends removing the condition. PHPStan 2.3.5's PhpFunctionFromParserNodeReflection::getParameters() explicitly treats variadics as optional, while PhpParameterFromParserNodeReflection::getDefaultValue() returns the declared default (null here). The collector also skips the restriction when no argument is supplied. Exempt variadic parameters for whenProvided, or check requiredness rather than default presence for that condition. [ Out of scope (post-validation triage) ]
src/phpstan/src/Rules/Coroutine/WorkerStateHelper.php — 0 comments posted, 1 evaluated, 1 filtered
  • line 259: isConfigWrite() treats first-class method callables as writes. For example, $setter = config()->set(...); inside a queued job's handle() now produces hypervel.runtimeConfigMutation, even if $setter is never invoked: the collector no longer filters first-class callables, this method returns true, and WorkerStateRule reports the collected write. PHP's first-class callable documentation states that this syntax creates a Closure, rather than calling the method (https://www.php.net/manual/en/functions.first_class_callable_syntax.php; the project requires PHP ^8.4). Restore an isFirstClassCallable() exclusion here so acquiring a callable isn't reported as mutating configuration. [ Out of scope (triage) ]

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.
@coderabbitai

coderabbitai Bot commented Nov 13, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

Run configuration

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • Trigger review
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

Share

@binaryfire

Copy link
Copy Markdown
Member Author

@cubic-dev-ai review

@cubic-dev-ai

cubic-dev-ai Bot commented Nov 13, 2026

Copy link
Copy Markdown

@cubic-dev-ai review

@binaryfire I have started the AI code review. It will take a few minutes to complete.

@qodo-free-for-open-source-projects

Copy link
Copy Markdown

PR Summary by Qodo

Add PHPStan checks for long-lived worker hazards

✨ Enhancement 🐞 Bug fix 🧪 Tests 📝 Documentation 🕐 40+ Minutes

Grey Divider

AI Description

• Add PHPStan diagnostics for unsafe worker lifetimes, borrowed resources, cancellation, and
 framework-specific APIs.
• Fix facade caching and related runtime contracts so analyzed behavior matches container lifetimes.
• Expand context-aware analysis, stabilize Testbench result caching, and document and test the
 rules.
Diagram

graph TD
  A["Application code"] --> B["PHPStan collectors"] --> C["Lifetime analysis"] --> D["Worker safety rules"] --> E["Diagnostics"]
  F["Booted container"] --> C
  G["Facade bindings"] --> C
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Ship rules as a separate PHPStan extension
  • ➕ Independent releases and adoption
  • ➕ Smaller framework PRs
  • ➖ Requires close coordination with evolving container and attribute behavior
  • ➖ Makes runtime fixes and analyzer coverage harder to validate together

Recommendation: Keep the rules alongside Hypervel: their accuracy depends on framework-specific booted bindings, coroutine contexts, and method attributes. Consider extraction only if an independently versioned analyzer becomes a product requirement.

Files changed (196) +12733 / -972

Enhancement (39) +6025 / -124
RedisStore.phpClarify borrowed Redis return types +5/-0

Clarify borrowed Redis return types

• Adds callback type information for borrowed Redis operations.

src/cache/src/RedisStore.php

Container.phpDeclare resolvedShared() +5/-0

Declare resolvedShared()

• Adds the shared-instance query to the container contract.

src/contracts/src/Container/Container.php

ConnectionResolverInterface.phpRequire concrete Connection returns +4/-1

Require concrete Connection returns

• Narrows connection() to the driver-neutral Connection already required by consumers.

src/database/src/ConnectionResolverInterface.php

InteractsWithPooledFilesystem.phpPreserve pooled callback results +19/-0

Preserve pooled callback results

• Adds generic callback return types and supports concrete driver, adapter, and client parameter types.

src/filesystem/src/Concerns/InteractsWithPooledFilesystem.php

FilesystemAdapter.phpExpose typed filesystem callbacks +12/-0

Expose typed filesystem callbacks

• Propagates callback result and concrete borrowed-resource types through adapter methods.

src/filesystem/src/FilesystemAdapter.php

RestrictedCallCollector.phpCollect attributed method calls +270/-0

Collect attributed method calls

• Captures restricted call sites and receiver evidence for context-aware reporting.

src/phpstan/src/Collectors/RestrictedCallCollector.php

SwallowedCancellationCollector.phpCollect cancellation-sensitive catches +821/-0

Collect cancellation-sensitive catches

• Identifies catches around cancellable operations for worker-context analysis.

src/phpstan/src/Collectors/SwallowedCancellationCollector.php

WorkerStateClassCollector.phpCollect more execution contexts +103/-10

Collect more execution contexts

• Records listener, observer, and class evidence needed for lifetime inference.

src/phpstan/src/Collectors/WorkerStateClassCollector.php

WorkerStateParameterCollector.phpCapture injected parameter evidence +5/-2

Capture injected parameter evidence

• Extends parameter collection for shared-receiver and worker-state checks.

src/phpstan/src/Collectors/WorkerStateParameterCollector.php

WorkerStateWriteCollector.phpTrack relevant writes +9/-1

Track relevant writes

• Expands assignment evidence used to determine whether a shared receiver remains valid.

src/phpstan/src/Collectors/WorkerStateWriteCollector.php

ReflectionHelper.phpExtend reflection utilities +19/-0

Extend reflection utilities

• Adds reflection support used by the new source and method analyses.

src/phpstan/src/Reflection/ReflectionHelper.php

SourceFiles.phpTrack attributed source dependencies +31/-3

Track attributed source dependencies

• Locates files containing method attribute arguments so cached diagnostics invalidate correctly.

src/phpstan/src/Reflection/SourceFiles.php

BindingCycleRule.phpDetect container alias cycles +143/-0

Detect container alias cycles

• Reports bindings whose concrete class aliases back to the same container key.

src/phpstan/src/Rules/BindingCycleRule.php

BorrowedResourceEscapeRule.phpDetect borrowed resource escapes +865/-0

Detect borrowed resource escapes

• Reports pooled or pinned resources returned, stored, or captured beyond their borrowing callback.

src/phpstan/src/Rules/Coroutine/BorrowedResourceEscapeRule.php

NoRawCoroutinesRule.phpReject unprotected coroutine starts +118/-0

Reject unprotected coroutine starts

• Reports direct Swoole or engine coroutine creation outside managed framework APIs.

src/phpstan/src/Rules/Coroutine/NoRawCoroutinesRule.php

RestrictedAttributeRule.phpValidate lifetime attributes +117/-0

Validate lifetime attributes

• Rejects marker arguments that misrepresent the intended method restriction.

src/phpstan/src/Rules/Coroutine/RestrictedAttributeRule.php

RestrictedCallRule.phpReport restricted runtime calls +161/-0

Report restricted runtime calls

• Uses worker contexts and shared-receiver evidence to diagnose attributed method calls.

src/phpstan/src/Rules/Coroutine/RestrictedCallRule.php

SwallowedCancellationRule.phpReport swallowed cancellation +93/-0

Report swallowed cancellation

• Flags catches that treat cancellation of a request, job, or operation as ordinary failure.

src/phpstan/src/Rules/Coroutine/SwallowedCancellationRule.php

SwooleTableInProviderRule.phpDetect per-process table creation +85/-0

Detect per-process table creation

• Reports Swoole tables constructed in provider register() or boot() methods.

src/phpstan/src/Rules/Coroutine/SwooleTableInProviderRule.php

WorkerLifetimes.phpExpand worker execution inference +303/-66

Expand worker execution inference

• Recognizes event listeners, queued listeners, observers, injected receivers, and additional operation contexts.

src/phpstan/src/Rules/Coroutine/WorkerLifetimes.php

WorkerStateHelper.phpBuild shared lifetime analysis +205/-24

Build shared lifetime analysis

• Coordinates collected worker-state evidence and booted-container inference.

src/phpstan/src/Rules/Coroutine/WorkerStateHelper.php

DiscardedDateRule.phpDetect discarded immutable dates +223/-0

Detect discarded immutable dates

• Reports date modifiers whose returned immutable value is ignored.

src/phpstan/src/Rules/DiscardedDateRule.php

ManagedTransactionRule.phpProtect managed transactions +363/-0

Protect managed transactions

• Reports premature commit or rollback inside transaction callbacks and rollBack(0).

src/phpstan/src/Rules/ManagedTransactionRule.php

MigrationColumnTypeRule.phpFlag selected migration types +82/-0

Flag selected migration types

• Optionally reports json(), enum(), and set() migration columns.

src/phpstan/src/Rules/MigrationColumnTypeRule.php

NoMutableDatesRule.phpDetect mutable Carbon creation +87/-0

Detect mutable Carbon creation

• Discourages mutable dates in concurrent worker code.

src/phpstan/src/Rules/NoMutableDatesRule.php

NoNativeRequestApisRule.phpReject process-level request APIs +147/-0

Reject process-level request APIs

• Flags native request functions and superglobals that bypass coroutine-local requests.

src/phpstan/src/Rules/NoNativeRequestApisRule.php

ObserverAttributeRule.phpRecommend model observer attributes +195/-0

Recommend model observer attributes

• Reports provider observe() registration for application-owned models.

src/phpstan/src/Rules/ObserverAttributeRule.php

RequestDependentBindingRule.phpDetect request-dependent binding conditions +473/-0

Detect request-dependent binding conditions

• Reports BindWhen conditions that would freeze a request or user choice for a worker.

src/phpstan/src/Rules/RequestDependentBindingRule.php

SocialitePropertyRule.phpProtect Socialite provider state +105/-0

Protect Socialite provider state

• Flags direct access to provider properties whose values belong to coroutine context.

src/phpstan/src/Rules/SocialitePropertyRule.php

TypedConfigGetterRule.phpRecommend typed config getters +142/-0

Recommend typed config getters

• Suggests typed accessors for application config keys with consistently known types.

src/phpstan/src/Rules/TypedConfigGetterRule.php

UncastEnvValueRule.phpDetect uncast config environment reads +200/-0

Detect uncast config environment reads

• Reports env() reads whose numeric or boolean defaults require explicit conversion.

src/phpstan/src/Rules/UncastEnvValueRule.php

UnsupportedCallRule.phpReport unsupported method calls +118/-0

Report unsupported method calls

• Resolves direct and facade calls to Unsupported methods and includes attribute guidance in the diagnostic.

src/phpstan/src/Rules/UnsupportedCallRule.php

ConfigSourceResolver.phpRecognize framework config sources +39/-0

Recognize framework config sources

• Identifies application, framework, package, and editor-substituted config files.

src/phpstan/src/Support/ConfigSourceResolver.php

ConstructorParser.phpImprove constructor binding inspection +31/-14

Improve constructor binding inspection

• Expands parsing used to infer container-created classes and injected dependencies.

src/phpstan/src/Support/ConstructorParser.php

FacadeHelper.phpResolve facade methods without execution +388/-0

Resolve facade methods without execution

• Infers facade accessors and root methods from declarations and booted bindings without invoking factories.

src/phpstan/src/Support/FacadeHelper.php

QueuePoolProxy.phpPreserve SQS callback return types +4/-1

Preserve SQS callback return types

• Adds a return template to pooled withConnection() callbacks.

src/queue/src/QueuePoolProxy.php

RedisProxy.phpPreserve pinned Redis callback types +4/-0

Preserve pinned Redis callback types

• Makes pinned connection methods generic over callback results.

src/redis/src/RedisProxy.php

Attribute.phpRefine cast attribute types +2/-2

Refine cast attribute types

• Adjusts Eloquent cast type declarations for updated analysis.

types/Database/Eloquent/Casts/Attribute.php

Filesystem.phpDescribe pooled filesystem callback types +29/-0

Describe pooled filesystem callback types

• Adds generic return and concrete borrowed-resource parameter annotations to filesystem types.

types/Filesystem/Filesystem.php

Bug fix (15) +199 / -42
logging.phpUse named syslog facility +1/-1

Use named syslog facility

• Changes the default syslog facility from an integer constant to the supported name.

skeletons/hypervel/config/logging.php

logging.phpUse named syslog facility +1/-1

Use named syslog facility

• Sets the starter kit's default facility to the Monolog-compatible name.

skeletons/react-starter-kit/config/logging.php

Container.phpExpose resolved shared lifetime +12/-0

Expose resolved shared lifetime

• Adds resolvedShared() to distinguish worker-held instances from fresh resolutions, including auto-singletons.

src/container/src/Container.php

logging.phpCorrect framework syslog default +1/-1

Correct framework syslog default

• Uses the facility name accepted by Monolog.

src/foundation/config/logging.php

LoadEnvironmentVariables.phpRemove FPM response bootstrap call +1/-2

Remove FPM response bootstrap call

• Stops setting an HTTP response code while bootstrapping CLI workers.

src/foundation/src/Bootstrap/LoadEnvironmentVariables.php

FoundationServiceProvider.phpBind the date factory at boot +3/-0

Bind the date factory at boot

• Registers date with singletonIf() instead of allowing request-time facade registration.

src/foundation/src/Providers/FoundationServiceProvider.php

ConfigHelper.phpRestrict inferred config types +51/-4

Restrict inferred config types

• Limits application config value inference to known application repository sources.

src/phpstan/src/Internal/ConfigHelper.php

ConfigCollectionRule.phpUse application-only config inference +1/-1

Use application-only config inference

• Adapts collection checks to the narrowed config-source model.

src/phpstan/src/Rules/ConfigCollectionRule.php

ConfigTypeRule.phpUse application-only config inference +1/-1

Use application-only config inference

• Avoids applying application config types to unrelated repositories.

src/phpstan/src/Rules/ConfigTypeRule.php

RuntimeInputsResultCacheValueExtension.phpTrack vendor directory presence +8/-2

Track vendor directory presence

• Adds directory-existence entries to result-cache keys.

src/phpstan/src/RuntimeInputsResultCacheValueExtension.php

RuntimeInputs.phpStabilize Testbench cache inputs +26/-7

Stabilize Testbench cache inputs

• Tracks source vendor metadata instead of temporary runtime-copy paths.

src/phpstan/src/Support/RuntimeInputs.php

Date.phpStop registering dates on first use +3/-3

Stop registering dates on first use

• Removes request-time container swapping from the Date facade fallback.

src/support/src/Facades/Date.php

Facade.phpCache roots according to lifetime +53/-18

Cache roots according to lifetime

• Retains worker-shared roots globally, caches other eligible roots per coroutine, and invalidates both on clearing.

src/support/src/Facades/Facade.php

FacadeState.phpHold coroutine-local facade roots +17/-0

Hold coroutine-local facade roots

• Introduces context state for cached roots and their invalidation generations.

src/support/src/Facades/FacadeState.php

Bootstrapper.phpExpose runtime vendor source +20/-1

Expose runtime vendor source

• Provides runtimeVendorPath() so cache inputs reference the working vendor directory.

src/testbench/src/Bootstrapper.php

Refactor (24) +845 / -644
CachedConnectionResolver.phpNarrow cached connection type +1/-1

Narrow cached connection type

• Declares cached resolutions as concrete database connections.

src/database/src/CachedConnectionResolver.php

Manager.phpAlign capsule connection types +3/-3

Align capsule connection types

• Uses the concrete Connection type in capsule resolution paths.

src/database/src/Capsule/Manager.php

ConnectionResolver.phpAlign connection resolver contract +9/-10

Align connection resolver contract

• Returns concrete connections and removes redundant interface-era checks.

src/database/src/ConnectionResolver.php

DatabaseManager.phpSimplify concrete connection handling +7/-14

Simplify concrete connection handling

• Narrows returned connection types and removes unnecessary casts and guards.

src/database/src/DatabaseManager.php

Model.phpRemove obsolete connection suppression +0/-1

Remove obsolete connection suppression

• Drops an ignore made unnecessary by the resolver's narrowed return type.

src/database/src/Eloquent/Model.php

Migrator.phpUse concrete migration connection +1/-1

Use concrete migration connection

• Aligns migrator connection typing with the resolver contract.

src/database/src/Migrations/Migrator.php

SimpleConnectionResolver.phpNarrow simple resolver return +1/-1

Narrow simple resolver return

• Declares the concrete connection returned by direct resolution.

src/database/src/SimpleConnectionResolver.php

DatabaseConnectionResolver.phpAlign testing resolver connection types +17/-20

Align testing resolver connection types

• Returns concrete connections and simplifies test connection bookkeeping.

src/foundation/src/Testing/DatabaseConnectionResolver.php

DatabaseTransactions.phpRemove obsolete resolver guards +1/-4

Remove obsolete resolver guards

• Uses the narrowed connection contract in transaction test support.

src/foundation/src/Testing/DatabaseTransactions.php

RefreshDatabase.phpSimplify refresh connection access +1/-5

Simplify refresh connection access

• Removes checks made unnecessary by concrete resolver returns.

src/foundation/src/Testing/RefreshDatabase.php

WorkerStateCallCollector.phpShare worker-state call analysis +4/-38

Share worker-state call analysis

• Moves common context and call reasoning into shared analysis support.

src/phpstan/src/Collectors/WorkerStateCallCollector.php

InspectsConnections.phpCentralize connection inspection +100/-0

Centralize connection inspection

• Shares recognition of database connection operations across analysis rules.

src/phpstan/src/Concerns/InspectsConnections.php

InspectsQueuedJobs.phpReuse queued-job inspection +2/-18

Reuse queued-job inspection

• Moves duplicated job-dispatch reasoning into common helpers.

src/phpstan/src/Concerns/InspectsQueuedJobs.php

CoroutineRuleHelper.phpShare coroutine rule inspection +391/-0

Share coroutine rule inspection

• Centralizes coroutine-related call and syntax reasoning.

src/phpstan/src/Rules/Coroutine/CoroutineRuleHelper.php

ExactCancellationCatchRule.phpSimplify exact cancellation checks +1/-247

Simplify exact cancellation checks

• Reuses shared coroutine inspection rather than maintaining duplicate wait recognition.

src/phpstan/src/Rules/Coroutine/ExactCancellationCatchRule.php

NoCoroutinesInTransactionsRule.phpReuse coroutine call recognition +1/-3

Reuse coroutine call recognition

• Aligns transaction coroutine detection with shared helper behavior.

src/phpstan/src/Rules/Coroutine/NoCoroutinesInTransactionsRule.php

WorkerStateAnalysis.phpShare worker-state results +267/-0

Share worker-state results

• Packages context, receiver, and dependency evidence for multiple context-aware rules.

src/phpstan/src/Rules/Coroutine/WorkerStateAnalysis.php

WorkerStateRule.phpConsume common worker analysis +20/-181

Consume common worker analysis

• Replaces duplicated worker-state reasoning with the shared analysis object.

src/phpstan/src/Rules/Coroutine/WorkerStateRule.php

NoEnvCallsOutsideOfConfigRule.phpReuse config-file discovery +5/-59

Reuse config-file discovery

• Delegates config location recognition to the shared source resolver.

src/phpstan/src/Rules/NoEnvCallsOutsideOfConfigRule.php

JobDispatchedInTransactionUsesAfterCommitRule.phpReuse queued-job analysis +7/-26

Reuse queued-job analysis

• Consolidates job inspection used by after-commit diagnostics.

src/phpstan/src/Rules/Queue/JobDispatchedInTransactionUsesAfterCommitRule.php

NoPipeSeparatedValidationRulesRule.phpSimplify validation rule implementation +0/-4

Simplify validation rule implementation

• Removes redundant handling while accommodating shared analysis changes.

src/phpstan/src/Rules/Validation/NoPipeSeparatedValidationRulesRule.php

DatabaseQueue.phpUse concrete queue connections +2/-4

Use concrete queue connections

• Removes connection guards made redundant by the resolver contract.

src/queue/src/DatabaseQueue.php

DatabaseOutboxRepository.phpAlign outbox connection assumptions +2/-2

Align outbox connection assumptions

• Uses concrete database connection typing in outbox persistence.

src/queue/src/Outbox/DatabaseOutboxRepository.php

DB.phpUse concrete facade connection types +2/-2

Use concrete facade connection types

• Aligns DB facade annotations with the narrowed connection contract.

src/support/src/Facades/DB.php

Tests (97) +4529 / -137
AuthDatabaseTokenRepositoryTest.phpAdapt token repository mocks +5/-4

Adapt token repository mocks

• Updates connection expectations for concrete resolver returns.

tests/Auth/AuthDatabaseTokenRepositoryTest.php

AuthDatabaseUserProviderTest.phpAdapt user provider mocks +7/-6

Adapt user provider mocks

• Uses concrete database connections in provider tests.

tests/Auth/AuthDatabaseUserProviderTest.php

AuthManagerTest.phpAdapt auth manager fixtures +3/-3

Adapt auth manager fixtures

• Aligns mocked connections with the narrowed contract.

tests/Auth/AuthManagerTest.php

AuthPasswordBrokerManagerTest.phpAdapt broker fixtures +3/-3

Adapt broker fixtures

• Uses concrete connections for resolver expectations.

tests/Auth/AuthPasswordBrokerManagerTest.php

CacheDatabaseLockTest.phpAdapt database lock tests +2/-2

Adapt database lock tests

• Aligns connection mocks with concrete returns.

tests/Cache/CacheDatabaseLockTest.php

CacheDatabaseStoreTest.phpAdapt database cache tests +4/-4

Adapt database cache tests

• Uses concrete database connection fixtures.

tests/Cache/CacheDatabaseStoreTest.php

ContainerTest.phpTest shared-resolution reporting +47/-0

Test shared-resolution reporting

• Covers resolvedShared() across worker-held, fresh, and auto-singleton resolutions.

tests/Container/ContainerTest.php

DatabaseMigratorTest.phpAdapt migrator resolver test +1/-2

Adapt migrator resolver test

• Updates connection fixtures for concrete returns.

tests/Database/DatabaseMigratorTest.php

DatabaseMonitorCommandTest.phpAdapt monitor connection tests +4/-4

Adapt monitor connection tests

• Aligns database monitor mocks with concrete Connection typing.

tests/Database/DatabaseMonitorCommandTest.php

FoundationServiceProviderTest.phpTest date factory registration +10/-0

Test date factory registration

• Checks framework date binding and precedence over existing bindings.

tests/Foundation/Providers/FoundationServiceProviderTest.php

InteractsWithParallelDatabaseTest.phpAdapt parallel database tests +3/-3

Adapt parallel database tests

• Uses concrete connection fixtures for test helpers.

tests/Foundation/Testing/Concerns/InteractsWithParallelDatabaseTest.php

DatabaseTransactionsTest.phpAdapt transaction testing fixtures +2/-3

Adapt transaction testing fixtures

• Aligns tests with the concrete resolver contract.

tests/Foundation/Testing/DatabaseTransactionsTest.php

RefreshDatabaseTest.phpAdapt database refresh tests +6/-5

Adapt database refresh tests

• Updates refresh expectations for concrete connections.

tests/Foundation/Testing/RefreshDatabaseTest.php

ProviderSources.phpAdjust config composition fixture +2/-3

Adjust config composition fixture

• Updates provider fixture behavior for application-only config inference.

tests/PHPStan/Fixtures/ConfigComposition/application/app/ProviderSources.php

FrenchGreeterServiceProvider.phpAdd package dependency fixture +18/-0

Add package dependency fixture

• Provides a provider for runtime-input and result-cache dependency tests.

tests/PHPStan/Fixtures/RuntimeInputs/Package/FrenchGreeterServiceProvider.php

MarkedMethod.phpAdd attributed method fixture +15/-0

Add attributed method fixture

• Supplies method attributes for source-file dependency tests.

tests/PHPStan/Fixtures/SourceFiles/MarkedMethod.php

Defaults.phpAdd lifetime defaults fixture +10/-0

Add lifetime defaults fixture

• Supplies application classes for worker-state dependency invalidation tests.

tests/PHPStan/Fixtures/WorkerStateDependency/application/app/Defaults.php

ImportFailed.phpAdd import event fixture +16/-0

Add import event fixture

• Exercises listener-context inference in dependency tests.

tests/PHPStan/Fixtures/WorkerStateDependency/application/app/ImportFailed.php

ImportJob.phpAdd import job fixture +21/-0

Add import job fixture

• Exercises job context and dependency tracking.

tests/PHPStan/Fixtures/WorkerStateDependency/application/app/ImportJob.php

RebuildReport.phpAdd reporting job fixture +15/-0

Add reporting job fixture

• Provides worker-lifetime job call paths.

tests/PHPStan/Fixtures/WorkerStateDependency/application/app/RebuildReport.php

Registry.phpAdd registry fixture +18/-0

Add registry fixture

• Supplies shared binding behavior for dependency analysis.

tests/PHPStan/Fixtures/WorkerStateDependency/application/app/Registry.php

ReportFacade.phpAdd facade dependency fixture +15/-0

Add facade dependency fixture

• Exercises facade-to-binding inference.

tests/PHPStan/Fixtures/WorkerStateDependency/application/app/ReportFacade.php

Reporting.phpAdd reporting contract fixture +13/-0

Add reporting contract fixture

• Defines an abstraction for shared-receiver analysis.

tests/PHPStan/Fixtures/WorkerStateDependency/application/app/Reporting.php

SupportedReporter.phpAdd supported reporter fixture +12/-0

Add supported reporter fixture

• Supplies an allowed target for method resolution.

tests/PHPStan/Fixtures/WorkerStateDependency/application/app/SupportedReporter.php

UnsupportedReporter.phpAdd unsupported reporter fixture +17/-0

Add unsupported reporter fixture

• Supplies an attributed target for invalidation and diagnostics.

tests/PHPStan/Fixtures/WorkerStateDependency/application/app/UnsupportedReporter.php

IntegrationTest.phpExtend analyzer integration coverage +7/-2

Extend analyzer integration coverage

• Checks new rules in end-to-end analysis.

tests/PHPStan/Integration/IntegrationTest.php

ResultCacheTestCase.phpSupport cache regression tests +2/-0

Support cache regression tests

• Extends reusable warm-cache test setup.

tests/PHPStan/ResultCacheTestCase.php

BindingCycleRuleTest.phpTest binding-cycle diagnostics +40/-0

Test binding-cycle diagnostics

• Checks cyclic aliases and noncyclic registrations.

tests/PHPStan/Rules/BindingCycleRuleTest.php

ConfigCollectionRuleTest.phpAdjust config collection assertions +4/-4

Adjust config collection assertions

• Tests application-only repository inference.

tests/PHPStan/Rules/ConfigCollectionRuleTest.php

BorrowedResourceEscapeRuleTest.phpTest borrowed-resource diagnostics +78/-0

Test borrowed-resource diagnostics

• Covers escapes and safe uses of pooled and pinned resources.

tests/PHPStan/Rules/Coroutine/BorrowedResourceEscapeRuleTest.php

NoRawCoroutinesRuleTest.phpTest coroutine start diagnostics +40/-0

Test coroutine start diagnostics

• Checks raw starts against supported coroutine entry points.

tests/PHPStan/Rules/Coroutine/NoRawCoroutinesRuleTest.php

RestrictedAttributeRuleTest.phpTest marker validation +57/-0

Test marker validation

• Covers invalid restriction arguments and accepted markers.

tests/PHPStan/Rules/Coroutine/RestrictedAttributeRuleTest.php

RestrictedCallRuleTest.phpTest context-aware restricted calls +240/-0

Test context-aware restricted calls

• Checks boot, test, shared-receiver, and runtime-context distinctions.

tests/PHPStan/Rules/Coroutine/RestrictedCallRuleTest.php

RestrictedCallsOnlyTest.phpTest isolated restricted-call configuration +103/-0

Test isolated restricted-call configuration

• Verifies restricted-call checks independently of other optional rules.

tests/PHPStan/Rules/Coroutine/RestrictedCallsOnlyTest.php

SwallowedCancellationRuleTest.phpTest cancellation diagnostics +95/-0

Test cancellation diagnostics

• Covers cancellable operations, catch forms, and safe rethrows.

tests/PHPStan/Rules/Coroutine/SwallowedCancellationRuleTest.php

SwooleTableInProviderRuleTest.phpTest table placement checks +33/-0

Test table placement checks

• Distinguishes provider-time table construction from safe placement.

tests/PHPStan/Rules/Coroutine/SwooleTableInProviderRuleTest.php

WorkerStateRuleTest.phpExtend worker-state contexts +43/-4

Extend worker-state contexts

• Tests additional listeners and observers as worker operations.

tests/PHPStan/Rules/Coroutine/WorkerStateRuleTest.php

borrowed-resources.phpFixture borrowed-resource flows +226/-0

Fixture borrowed-resource flows

• Provides escapes, aliases, captures, and allowed callback usage.

tests/PHPStan/Rules/Coroutine/data/borrowed-resources.php

exact-cancellation-catches.phpFixture exact cancellation catches +13/-0

Fixture exact cancellation catches

• Covers cancellation catch handling after helper extraction.

tests/PHPStan/Rules/Coroutine/data/exact-cancellation-catches.php

raw-coroutines.phpFixture raw coroutine calls +46/-0

Fixture raw coroutine calls

• Provides direct and managed coroutine start cases.

tests/PHPStan/Rules/Coroutine/data/raw-coroutines.php

restricted-attributes.phpFixture restriction markers +47/-0

Fixture restriction markers

• Supplies valid and invalid attribute argument combinations.

tests/PHPStan/Rules/Coroutine/data/restricted-attributes.php

restricted-calls-only.neonConfigure isolated restriction fixture +9/-0

Configure isolated restriction fixture

• Enables restricted-call analysis in a focused test configuration.

tests/PHPStan/Rules/Coroutine/data/restricted-calls-only.neon

restricted-calls-only.phpFixture isolated restricted calls +31/-0

Fixture isolated restricted calls

• Supplies calls for the focused configuration test.

tests/PHPStan/Rules/Coroutine/data/restricted-calls-only.php

restricted-calls.phpFixture runtime-restricted calls +321/-0

Fixture runtime-restricted calls

• Exercises attributes across contexts, receivers, and facade paths.

tests/PHPStan/Rules/Coroutine/data/restricted-calls.php

swallowed-cancellations.phpFixture cancellation control flow +496/-0

Fixture cancellation control flow

• Exercises cancellable operations, broad catches, exceptions, and rethrow paths.

tests/PHPStan/Rules/Coroutine/data/swallowed-cancellations.php

swoole-tables.phpFixture Swoole table placement +40/-0

Fixture Swoole table placement

• Distinguishes provider lifecycle allocation from permitted uses.

tests/PHPStan/Rules/Coroutine/data/swoole-tables.php

worker-state-listeners.phpFixture listener execution contexts +170/-0

Fixture listener execution contexts

• Supplies request, job, and server event listener cases.

tests/PHPStan/Rules/Coroutine/data/worker-state-listeners.php

DiscardedDateRuleTest.phpTest immutable-date results +72/-0

Test immutable-date results

• Checks discarded modifiers and intentional uses.

tests/PHPStan/Rules/DiscardedDateRuleTest.php

ManagedTransactionRuleTest.phpTest managed transaction boundaries +51/-0

Test managed transaction boundaries

• Checks premature commit, rollback, and valid transaction handling.

tests/PHPStan/Rules/ManagedTransactionRuleTest.php

MigrationColumnTypeRuleTest.phpTest migration column option +32/-0

Test migration column option

• Checks flagged types and opt-in configuration.

tests/PHPStan/Rules/MigrationColumnTypeRuleTest.php

NoEnvCallsOutsideOfConfigRuleTest.phpTest broader config-file detection +11/-5

Test broader config-file detection

• Checks legitimate env() use in recognized config locations.

tests/PHPStan/Rules/NoEnvCallsOutsideOfConfigRuleTest.php

NoMutableDatesRuleTest.phpTest mutable-date diagnostics +41/-0

Test mutable-date diagnostics

• Covers mutable Carbon construction and safe alternatives.

tests/PHPStan/Rules/NoMutableDatesRuleTest.php

NoNativeRequestApisRuleTest.phpTest native request API checks +53/-0

Test native request API checks

• Covers process-global request functions and superglobals.

tests/PHPStan/Rules/NoNativeRequestApisRuleTest.php

ObserverAttributeRuleTest.phpTest observer registration checks +51/-0

Test observer registration checks

• Distinguishes owned models from package and vendor models.

tests/PHPStan/Rules/ObserverAttributeRuleTest.php

RequestDependentBindingRuleTest.phpTest conditional binding safety +47/-0

Test conditional binding safety

• Checks request-dependent conditions and safe boot-time conditions.

tests/PHPStan/Rules/RequestDependentBindingRuleTest.php

SocialitePropertyRuleTest.phpTest Socialite state access +39/-0

Test Socialite state access

• Checks coroutine-backed provider properties against allowed accesses.

tests/PHPStan/Rules/SocialitePropertyRuleTest.php

TypedConfigGetterRuleTest.phpTest typed config recommendations +70/-0

Test typed config recommendations

• Covers known application keys and non-application repositories.

tests/PHPStan/Rules/TypedConfigGetterRuleTest.php

UncastEnvValueRuleTest.phpTest config env casts +51/-0

Test config env casts

• Checks scalar defaults, conversions, and permitted reads.

tests/PHPStan/Rules/UncastEnvValueRuleTest.php

UnsupportedCallRuleTest.phpTest unsupported-call diagnostics +81/-0

Test unsupported-call diagnostics

• Covers direct and facade calls, marker messages, and allowed exceptions.

tests/PHPStan/Rules/UnsupportedCallRuleTest.php

binding-cycles.phpFixture alias cycles +41/-0

Fixture alias cycles

• Provides cyclic and valid container registrations.

tests/PHPStan/Rules/data/binding-cycles.php

config-collection-rule.phpAdjust config collection fixture +2/-3

$reflection = $this->getClass($receiver);

return $reflection !== null
&& (! $reflection->is(ShouldQueue::class) || isset($this->queuedListeners[strtolower($reflection->getName())]));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Medium Coroutine/WorkerLifetimes.php:341

isContainerBuilt() returns true for a ShouldQueue class registered as a listener even when that class is also dispatched as a standalone job. Those direct jobs are restored from the payload with unserialize(), so promoted properties are misclassified as container-injected and trigger false runtime-config and restricted-call diagnostics. Track construction provenance per execution context, or keep the result false when the class also runs as a standalone job.

🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @src/phpstan/src/Rules/Coroutine/WorkerLifetimes.php around line 341:

`isContainerBuilt()` returns `true` for a `ShouldQueue` class registered as a listener even when that class is also dispatched as a standalone job. Those direct jobs are restored from the payload with `unserialize()`, so promoted properties are misclassified as container-injected and trigger false runtime-config and restricted-call diagnostics. Track construction provenance per execution context, or keep the result false when the class also runs as a standalone job.

Evidence trail:
Reviewed commit c329d6f.
src/phpstan/src/Rules/Coroutine/WorkerLifetimes.php:333-342, 633-711, 720-754
src/phpstan/src/Rules/Coroutine/WorkerStateAnalysis.php:209-233
src/phpstan/src/Rules/Coroutine/WorkerStateRule.php:134-153
src/phpstan/src/Rules/Coroutine/RestrictedCallRule.php:107-134
src/queue/src/Queue.php:282-285
src/queue/src/CallQueuedHandler.php:98-111

*/
private function getStore(Expr $target, array $byReference): ?string
{
while ($target instanceof ArrayDimFetch || $target instanceof PropertyFetch) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Medium Coroutine/BorrowedResourceEscapeRule.php:582

A borrowed connection stored on a captured object escapes the callback without being reported; for example, $holder->value = $connection leaves the released connection reachable through $holder. getStore() only treats $this properties and static properties as escapes, but capturing $holder by value still shares the same object. Recognize property writes through captured or otherwise external objects as escapes.

🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @src/phpstan/src/Rules/Coroutine/BorrowedResourceEscapeRule.php around line 582:

A borrowed connection stored on a captured object escapes the callback without being reported; for example, `$holder->value = $connection` leaves the released connection reachable through `$holder`. `getStore()` only treats `$this` properties and static properties as escapes, but capturing `$holder` by value still shares the same object. Recognize property writes through captured or otherwise external objects as escapes.

Evidence trail:
e5e6002996: src/phpstan/src/Rules/Coroutine/BorrowedResourceEscapeRule.php:320-335,347-350,580-594; src/redis/src/RedisProxy.php:495-526; src/docs/static-analysis.md:954-974

return is_string($expr->name) ? $tracked[$expr->name] ?? null : null;
}

if (! ($expr instanceof MethodCall || $expr instanceof NullsafeMethodCall || $expr instanceof StaticCall)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Medium Coroutine/BorrowedResourceEscapeRule.php:547

Returning or storing a closure that captures the borrowed resource is not reported, so the closure can use a resource after its lending callback releases it. getBorrowedKind() rejects closure expressions, and the later nested-function analysis treats them as non-escaping; detect captured borrowed values in escaping closures, including closures reached through aliases.

🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @src/phpstan/src/Rules/Coroutine/BorrowedResourceEscapeRule.php around line 547:

Returning or storing a closure that captures the borrowed resource is not reported, so the closure can use a resource after its lending callback releases it. `getBorrowedKind()` rejects closure expressions, and the later nested-function analysis treats them as non-escaping; detect captured borrowed values in escaping closures, including closures reached through aliases.

Evidence trail:
src/phpstan/src/Rules/Coroutine/BorrowedResourceEscapeRule.php:344-377, 537-551, 707-735, commit e5e6002996
src/redis/src/RedisProxy.php:491-535, commit 89f062c536
src/docs/static-analysis.md:951-974

{
$last = end($statements);

if (! $last instanceof Expression || ! $last->expr instanceof Throw_ || $this->leaves($statements, 0)) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Medium Collectors/SwallowedCancellationCollector.php:476

rethrows() classifies the catch as propagating when an earlier branch throws a replacement exception, so cancellation is swallowed on that branch and the collector misses it. leaves() does not detect that throw path; check earlier escaping throws as well as the final throw $e.

🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @src/phpstan/src/Collectors/SwallowedCancellationCollector.php around line 476:

`rethrows()` classifies the catch as propagating when an earlier branch throws a replacement exception, so cancellation is swallowed on that branch and the collector misses it. `leaves()` does not detect that throw path; check earlier escaping throws as well as the final `throw $e`.

Evidence trail:
src/phpstan/src/Collectors/SwallowedCancellationCollector.php:394-415, 442-463, 472-499, 527-565 at reviewed commit 81e4571


$polarity = $this->guardPolarity($first->cond, $variable);

return $polarity !== null

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Medium Coroutine/CoroutineRuleHelper.php:368

A catch with if (! Coroutine::causedByCancellation($e)) { report($e); return; } throw $e; is reported as handling cancellation as an ordinary failure, even though cancellation skips the failure branch and is rethrown. keepsCancellationApart() only accepts a terminal throw inside a positive guard (or a one-statement catch); it should also recognize a negated guard whose branch returns followed by a catch-level rethrow.

🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @src/phpstan/src/Rules/Coroutine/CoroutineRuleHelper.php around line 368:

A catch with `if (! Coroutine::causedByCancellation($e)) { report($e); return; } throw $e;` is reported as handling cancellation as an ordinary failure, even though cancellation skips the failure branch and is rethrown. `keepsCancellationApart()` only accepts a terminal throw inside a positive guard (or a one-statement catch); it should also recognize a negated guard whose branch returns followed by a catch-level rethrow.

Evidence trail:
Reviewed commit c329d6f. src/phpstan/src/Rules/Coroutine/CoroutineRuleHelper.php:273-290, 342-371. src/phpstan/src/Collectors/SwallowedCancellationCollector.php:435-463. src/phpstan/src/Rules/Coroutine/ExactCancellationCatchRule.php:56-64.

default => [],
};

foreach ($targets as $target) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Medium Rules/ManagedTransactionRule.php:258

Reassigning $connection through array destructuring leaves it marked as the managed connection, so the later commit() is incorrectly reported as ending the transaction. List_ targets are not Variable nodes, so recursively collect variables from destructuring targets before retaining owner identities.

🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @src/phpstan/src/Rules/ManagedTransactionRule.php around line 258:

Reassigning `$connection` through array destructuring leaves it marked as the managed connection, so the later `commit()` is incorrectly reported as ending the transaction. `List_` targets are not `Variable` nodes, so recursively collect variables from destructuring targets before retaining owner identities.

Evidence trail:
Reviewed commit e5e6002: src/phpstan/src/Rules/ManagedTransactionRule.php:123-160, 252-262, 315-320; src/phpstan/src/Concerns/InspectsConnections.php:33-53, 90-98; PHP-Parser node definition: https://github.com/nikic/PHP-Parser/blob/v5.0.0/lib/PhpParser/Node/Expr/List_.php


$declaration = match (true) {
$condition instanceof FuncCall && $condition->isFirstClassCallable() => $this->findFunction($condition, $scope),
$condition instanceof StaticCall && $condition->isFirstClassCallable() => $this->findStaticMethod($condition, $scope),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Medium Rules/RequestDependentBindingRule.php:184

#[BindWhen(..., RequestContext::has(...))] produces no diagnostic, so a request-dependent implementation can be bound permanently for the worker. This branch scans only the callable's declaration, where RequestContext::has() delegates to CoroutineContext::has(), which readsRequest() does not recognize. Check whether the first-class callable target itself is a recognized request or user read before scanning its body.

🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @src/phpstan/src/Rules/RequestDependentBindingRule.php around line 184:

`#[BindWhen(..., RequestContext::has(...))]` produces no diagnostic, so a request-dependent implementation can be bound permanently for the worker. This branch scans only the callable's declaration, where `RequestContext::has()` delegates to `CoroutineContext::has()`, which `readsRequest()` does not recognize. Check whether the first-class callable target itself is a recognized request or user read before scanning its body.

Evidence trail:
Reviewed commit c329d6f. Inspect `src/phpstan/src/Rules/RequestDependentBindingRule.php:182-207` (first-class callable declaration scanning), `:303-320` and `:377-396` (recognized static reads), `src/context/src/RequestContext.php:39-44` (`has()` delegates to `CoroutineContext::has()`), `src/context/src/CoroutineContext.php:75-85`, and `src/container/src/Attributes/BindWhen.php:11-15` (matching bindings are worker-lifetime).

@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (6) 📘 Rule violations (0) 📜 Skill insights (0)

Grey Divider


Remediation recommended

1. Wildcard listeners evade call checks 🐞 Bug ≡ Correctness
Description
discoverListeners() looks up a wildcard registration's pattern as though it were a concrete event
name and assigns no context unless the pattern starts with eloquent.. A listener registered for
Hypervel\HttpServer\Events\* can handle request events, but restricted calls inside it are skipped
because the call rule receives a null context.
Code

src/phpstan/src/Rules/Coroutine/WorkerLifetimes.php[R641-642]

+        foreach ([...$events->getRawListeners(), ...(fn (): array => $this->wildcards)->call($events)] as $event => $listeners) {
+            $context = self::EVENT_CONTEXTS[$event] ?? (is_string($event) && str_starts_with($event, 'eloquent.') ? 'operation' : null);
Evidence
The dispatcher matches wildcard patterns against dispatched event names; the new discovery code
instead queries its concrete-event map with the pattern, then passes null to context registration.
The restricted-call rule skips calls without a context.

src/events/src/Dispatcher.php[290-300]
src/phpstan/src/Rules/Coroutine/WorkerLifetimes.php[112-136]
src/phpstan/src/Rules/Coroutine/WorkerLifetimes.php[641-664]
src/phpstan/src/Rules/Coroutine/RestrictedCallRule.php[78-81]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Wildcard listener registrations lose the contexts of framework events they match, allowing restricted calls in those listeners to go undiagnosed.
## Fix Focus Areas
- src/phpstan/src/Rules/Coroutine/WorkerLifetimes.php[635-664]
## Recommended Fix
Match each wildcard registration against known framework event names using dispatcher-compatible pattern semantics, and assign the matched contexts to its handler. Preserve job context for queued listeners and test a wildcard request-event listener.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Some swallowed cancellations go undetected 🐞 Bug ≡ Correctness
Description
getReceiverType() recognizes a receiver assignment inside a try only when it is a plain
Assign, not an AssignOp such as ??= or an AssignRef. When a database connection is
initialized by $connection ??= DB::connection() before a query in that try, receiver inference
fails and the swallowing catch receives no diagnostic.
Code

src/phpstan/src/Rules/Coroutine/CoroutineRuleHelper.php[R224-226]

+                || self::getCalledVariable($node)?->name === $name
+                || ($node instanceof Assign && $node->var instanceof Variable && $node->var->name === $name),
+        );
Evidence
The helper's broader assignment predicate includes AssignOp and AssignRef, but the receiver scan
does not; an unmatched variable mention makes it return null. Operation detection requires that
receiver type, and the collector returns without a diagnostic when it finds no operation.

src/phpstan/src/Rules/Coroutine/CoroutineRuleHelper.php[139-147]
src/phpstan/src/Rules/Coroutine/CoroutineRuleHelper.php[221-263]
src/phpstan/src/Collectors/SwallowedCancellationCollector.php[409-414]
src/phpstan/src/Collectors/SwallowedCancellationCollector.php[703-707]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
A compound or reference assignment initializing an operation receiver prevents swallowed-cancellation detection.
## Fix Focus Areas
- src/phpstan/src/Rules/Coroutine/CoroutineRuleHelper.php[208-263]
## Recommended Fix
Classify compound and reference assignments consistently with `assigns()`, and derive the receiver type after supported initialization forms without treating the assignment's target as an unmatched mention. Test a connection initialized with `??=` inside a try.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Property-held pinned handles escape checks 🐞 Bug ≡ Correctness
Description
BorrowedResourceEscapeRule::getOwner() rejects a PropertyFetch receiver, and its
handle-recognition path also accepts only variable or facade owners. For
$this->connection->withPinnedSession(fn () => $this->connection->getPdo()), the rule exits before
checking that the callback returns the PDO borrowed from the pinned session.
Code

src/phpstan/src/Rules/Coroutine/BorrowedResourceEscapeRule.php[R283-289]

+    private function getOwner(MethodCall|StaticCall $call): ?string
+    {
+        if ($call instanceof StaticCall) {
+            return $call->class instanceof Name && ReflectionHelper::isFacade($call->class, DB::class, 'DB') ? DB::class : null;
+        }
+
+        return $call->var instanceof Variable && is_string($call->var->name) ? $call->var->name : null;
Evidence
The runtime pin applies to any connection receiver, but the rule immediately abandons property
receivers. Its later PDO recognition would also miss a property receiver, so both paths need
coverage.

src/database/src/Connection.php[1163-1180]
src/phpstan/src/Rules/Coroutine/BorrowedResourceEscapeRule.php[220-241]
src/phpstan/src/Rules/Coroutine/BorrowedResourceEscapeRule.php[283-289]
src/phpstan/src/Rules/Coroutine/BorrowedResourceEscapeRule.php[549-571]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The borrowed-resource rule misses pinned connections held on object properties, including their escaping PDO handles.
## Fix Focus Areas
- src/phpstan/src/Rules/Coroutine/BorrowedResourceEscapeRule.php[220-241]
- src/phpstan/src/Rules/Coroutine/BorrowedResourceEscapeRule.php[280-290]
- src/phpstan/src/Rules/Coroutine/BorrowedResourceEscapeRule.php[527-573]
## Recommended Fix
Represent a stable property receiver as an owner and recognize its getter calls in the borrowed-kind analysis. Account for property reassignment inside the callback and add a property-held pinned-session escape fixture.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View medium (3)
4. Property transactions evade early-end checks 🐞 Bug ≡ Correctness
Description
ManagedTransactionRule::getOwnerKey() cannot identify a property receiver as the connection whose
transaction is managed, and matchOwner() cannot equate that property's later control call with it.
A callback passed to $this->connection->transaction() can therefore call
$this->connection->commit() before returning without a diagnostic.
Code

src/phpstan/src/Rules/ManagedTransactionRule.php[R182-188]

+        $receiver = $call->var;
+
+        if ($receiver instanceof Variable && is_string($receiver->name) && ! isset($owners[$receiver->name])) {
+            return $this->receives($callback, $receiver->name) ? '$' . $receiver->name : null;
+        }
+
+        return $this->getConnectionName($receiver, $scope);
Evidence
The rule's owner key and matching logic handle variables, facades and named connection expressions
but do not establish identity for property fetches. The transaction implementation calls the
callback before performing its own commit, so committing the same connection inside it is an early
termination.

src/phpstan/src/Rules/ManagedTransactionRule.php[105-155]
src/phpstan/src/Rules/ManagedTransactionRule.php[176-188]
src/phpstan/src/Rules/ManagedTransactionRule.php[299-335]
src/database/src/Concerns/ManagesTransactions.php[30-43]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Early transaction control through a property-held connection is missed by the managed-transaction rule.
## Fix Focus Areas
- src/phpstan/src/Rules/ManagedTransactionRule.php[170-188]
- src/phpstan/src/Rules/ManagedTransactionRule.php[299-335]
## Recommended Fix
Identify stable property receivers on both the transaction call and callback control calls, while treating reassignment or uncertain aliases conservatively. Test a property-held connection committed inside its transaction callback.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


5. Mutable date subclasses escape warnings 🐞 Bug ≡ Correctness
Description
NoMutableDatesRule accepts only two exact class-name strings before checking a creation
expression's type. A subclass of Hypervel\Support\Carbon therefore avoids the diagnostic for both
new LocalCarbon and inherited LocalCarbon::now() despite producing mutable dates.
Code

src/phpstan/src/Rules/NoMutableDatesRule.php[R54-57]

+        if (! ($node instanceof New_ || $node instanceof StaticCall)
+            || ! $node->class instanceof Name
+            || $node->class->isSpecialClassName()
+            || ! isset(self::MUTABLE_CLASSES[strtolower($node->class->toString())])
Evidence
The new fixture defines a mutable Carbon subclass and calls both creation forms; the exact-name
guard excludes its name before the rule's type check.

src/phpstan/src/Rules/NoMutableDatesRule.php[32-39]
src/phpstan/src/Rules/NoMutableDatesRule.php[54-75]
tests/PHPStan/Rules/data/mutable-dates.php[12-34]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Creating a mutable Carbon subclass is omitted from the new mutable-date diagnostics.
## Fix Focus Areas
- src/phpstan/src/Rules/NoMutableDatesRule.php[54-75]
- tests/PHPStan/Rules/data/mutable-dates.php[12-34]
## Recommended Fix
Use resolved class inheritance or creation result types rather than an exact syntactic-name allowlist, while retaining the existing exclusions for non-date or non-creating static methods. Assert diagnostics for subclass construction and inherited creators.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


6. Filesystem callbacks can receive wrong types 🐞 Bug ≡ Correctness
Description
FilesystemAdapter::withDriver() and withAdapter() infer their callback parameter templates from
the callback rather than from the disk's stored driver and adapter. A callback requiring
Filesystem or AwsS3V3Adapter is consequently accepted for a general FilesystemAdapter even
when its runtime member is another implementation, making callback invocation fail with a type
error.
Code

src/filesystem/src/FilesystemAdapter.php[R1105-1108]

+     * @template TDriver of FilesystemOperator
+     * @template T
+     *
+     * @param Closure(TDriver): T $callback
Evidence
The constructor permits any implementations of the broad interfaces and the methods pass those
stored values directly into the callback. The new type fixture explicitly accepts concrete-only
callbacks even when the receiver is a general disk.

src/filesystem/src/FilesystemAdapter.php[112-116]
src/filesystem/src/FilesystemAdapter.php[1102-1127]
types/Filesystem/Filesystem.php[18-27]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The filesystem callback annotations allow a narrower argument type than the disk actually supplies.
## Fix Focus Areas
- src/filesystem/src/FilesystemAdapter.php[1102-1127]
- src/filesystem/src/Concerns/InteractsWithPooledFilesystem.php[1-40]
- types/Filesystem/Filesystem.php[18-27]
## Recommended Fix
Tie driver and adapter callback parameter types to types guaranteed by the receiving disk, rather than inferring unrestricted subtypes from the callback. Verify a general disk rejects an S3-only callback, while disks with a guaranteed concrete type retain appropriate inference.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Context sources
✅ Context preparation
  Commit messages: summarized; full text retained for this review
Review mode: Auto: 🧠 Deep: Numerous independent static-analysis rules and runtime changes create dense, subtle defect risk.

Grey Divider

Tip of the day
💡 Did you know, you can start a comment with 'qodo' or '@qodo' to chat about any finding

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

29 issues found across 196 files

Confidence score: 3/5

  • NoPipeSeparatedValidationRulesRule.php can throw a TypeError when it analyzes a first-class callable such as validator(...). Restore the early return for first-class callables.
  • SafeSocket.php suppresses the raw-coroutine warning even though critical() and close() can still throw outside the protective catch. Protect those calls or let the raw-coroutine check report the issue.
  • NoMutableDatesRule.php misses mutable-date constructions and static calls through imported or aliased class names. Resolve the class through Scope before matching it.
  • Connection.php's ignore does not cover exceptions from calls outside the inner try. Narrow the suppression or handle those exceptions explicitly.
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/grpc/src/Client/Connection.php">

<violation number="1" location="src/grpc/src/Client/Connection.php:460">
P2: This ignore says `receive()` contains all receiver failures, but it does not. Only `CanceledException` and the errors thrown inside the inner `try` are handled. Exceptions from `closeRetiredConnectionIfIdle()`, `auditStreams()`, or from `failConnection()` inside the `catch (Throwable)` handler escape the raw coroutine and stop the worker. Either wrap the callback body in a `catch (Throwable)` that handles the failure, or narrow the justification so it does not claim full containment.</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/engine/src/SafeSocket.php">

<violation number="1" location="src/engine/src/SafeSocket.php:159">
P2: This suppression hides a raw coroutine whose error handler can still throw: `critical()` and `close()` run outside a protective catch. Protect those cleanup/reporting calls (or let `hypervel.rawCoroutine` report the boundary) before suppressing this diagnostic.</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/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/ResultCacheTestCase.php">

<violation number="1" location="tests/PHPStan/ResultCacheTestCase.php:65">
P3: The comment says the fixtures read config through `get()`, but they also use the `config()` helper, which this rule checks as well. Mention both so the reason for disabling the check is accurate.</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="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="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/NoNativeRequestApisRule.php">

<violation number="1" location="src/phpstan/src/Rules/NoNativeRequestApisRule.php:40">
P2: This tip says both `response()->stream()` generators and `IterableStreamedResponse` stop on disconnect, but cancellation is opt-in and off by default. Reword it to say to call `cancelOnDisconnect()`, and update the matching expectation in `NoNativeRequestApisRuleTest`.</violation>
</file>

<file name="src/phpstan/src/Collectors/WorkerStateWriteCollector.php">

<violation number="1" location="src/phpstan/src/Collectors/WorkerStateWriteCollector.php:108">
P2: This early return drops offset writes that `WorkerStateConfigOffsetCollector` never sees. That collector only handles top-level `Expression` assignments and `Unset_` statements. A write nested in a `return`, an argument, or a nested dim is now silent in both rules. Either widen the config-offset collector to expression-level writes, or narrow this check to the cases that collector covers.</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>

<file name="src/phpstan/src/Rules/Coroutine/BorrowedResourceEscapeRule.php">

<violation number="1" location="src/phpstan/src/Rules/Coroutine/BorrowedResourceEscapeRule.php:547">
P2: Detect borrowed values captured by escaping closures, including closures returned or stored through aliases; otherwise the closure can use the resource after its lending callback releases it.</violation>

<violation number="2" location="src/phpstan/src/Rules/Coroutine/BorrowedResourceEscapeRule.php:583">
P2: Treat writes through captured or otherwise external object properties as escapes. `$holder->value = $connection` leaves the borrowed connection reachable after the callback.</violation>
</file>

<file name="src/phpstan/src/Collectors/SwallowedCancellationCollector.php">

<violation number="1" location="src/phpstan/src/Collectors/SwallowedCancellationCollector.php:476">
P2: Check every escaping throw path before treating the catch as propagating. An earlier branch that throws a replacement exception swallows cancellation even when the final statement rethrows `$e`.</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()) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>


$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));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>
Suggested change
$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));

$this->receiving = true;

try {
// @phpstan-ignore hypervel.rawCoroutine (receive() contains cancellation and receiver failures as the terminal owner)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: This ignore says receive() contains all receiver failures, but it does not. Only CanceledException and the errors thrown inside the inner try are handled. Exceptions from closeRetiredConnectionIfIdle(), auditStreams(), or from failConnection() inside the catch (Throwable) handler escape the raw coroutine and stop the worker. Either wrap the callback body in a catch (Throwable) that handles the failure, or narrow the justification so it does not claim full containment.

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/grpc/src/Client/Connection.php, line 460:

<comment>This ignore says `receive()` contains all receiver failures, but it does not. Only `CanceledException` and the errors thrown inside the inner `try` are handled. Exceptions from `closeRetiredConnectionIfIdle()`, `auditStreams()`, or from `failConnection()` inside the `catch (Throwable)` handler escape the raw coroutine and stop the worker. Either wrap the callback body in a `catch (Throwable)` that handles the failure, or narrow the justification so it does not claim full containment.</comment>

<file context>
@@ -457,6 +457,7 @@ private function startReceiver(): void
         $this->receiving = true;
 
         try {
+            // @phpstan-ignore hypervel.rawCoroutine (receive() contains cancellation and receiver failures as the terminal owner)
             Coroutine::create(function (): void {
                 $this->receiverCoroutineId = Coroutine::id();
</file context>

$completed->push(true);
});

// @phpstan-ignore hypervel.rawCoroutine (the test deadline watchdog runs no application code, catches the timeout it throws, and nothing cancels it)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>

if (! ($node instanceof New_ || $node instanceof StaticCall)
|| ! $node->class instanceof Name
|| $node->class->isSpecialClassName()
|| ! isset(self::MUTABLE_CLASSES[strtolower($node->class->toString())])

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>


// 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);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>
Suggested change
$container->extend('unsupported.extended', static fn (Service $service): Service => $service);
$container->extend('unsupported.extended', static fn (Service $service): Service => new Service);

];

// The readers fixture declares an Env subclass with its own get().
$this->analyse([__DIR__ . '/data/config/uncast-env.php', __DIR__ . '/data/env-readers.php'], [

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>

if ($function !== null && strtolower($function) === self::FUNCTION) {
$spawn = $function;
}
} elseif (($node instanceof MethodCall || $node instanceof StaticCall)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>

public function createKey(
array $files = [],
array $presenceFiles = [],
array $presenceDirectories = [],

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>


<a name="streamed-responses"></a>
### Streamed Responses
<a name="native-request-functions"></a>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>
Suggested change
<a name="native-request-functions"></a>
<a name="streamed-responses"></a>
<a name="native-request-functions"></a>

@greptile-apps

greptile-apps Bot commented Nov 13, 2026 •

Copy link
Copy Markdown

RetriggerView in GreptileConfidence Score: 4/5 Tier: plus

[High impact] The changes reach application-wide static analysis and shared facade behavior across request coroutines.

The PR appears safe to merge, with non-blocking fixes recommended for four gaps in the new checks.

Fix All in Claude CodeFindings

  1. P2 Task arrays escape the check ▶
  2. P2 Cloned listeners look shared ▶
  3. P2 Custom facade calls get misread ▶
  4. P2 Earlier throws hide cancellation ▶

Summary

Adds PHPStan checks for long-lived worker mistakes, shared context analysis, coroutine-local facade roots, and stable package-cache inputs.

  • PHPStan flags code patterns that can disrupt long-lived workers.
  • PHPStan catches more worker and framework mistakes in application code.
  • Facades keep non-shared service roots separate for each coroutine.
  • Database resolvers return Hypervel's driver-neutral connection class.
  • Analysis and runtime support now match Hypervel's worker and package setup.

Reviews (1) · Last reviewed commit: "Clarify PHPStan documentation with usage..." · Reviewed by Greptile

Comment on lines +454 to +455
if ($root instanceof Variable && is_string($root->name) && ($node->expr instanceof Closure || $node->expr instanceof ArrowFunction)) {
$closures[$root->name][] = $node->expr;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Task arrays escape the check

collectWrites() misses $tasks = [fn () => $connection->get('key')]; parallel($tasks); inside withConnection(). It records only assignments whose right side is a closure, so getPassedClosures() finds no tasks for this variable. Developers get no warning even though cancellation can leave a child using the connection after the borrow ends.

Collect closures inside assigned array literals too, and test this alongside the existing $tasks[] case.

Fix in Claude Code Fix in Codex

Comment on lines +709 to +710
if ($context !== null && $receiver->hasNativeMethod($method)) {
$this->addContext($receiver->getName(), $receiver->getNativeMethod($method)->getDeclaringClass()->getName(), $method, $context, $shared);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Cloned listeners look shared

addHandlerContext() keeps the container's $shared flag for queued listener methods. But CallQueuedListener clones listeners using InteractsWithQueue before calling them. A registered method such as record() that writes a scalar property now gets hypervel.workerSharedState, even though each job changes its own clone. Valid listeners therefore need unnecessary ignores.

Keep the job context, but distinguish the cloned receiver from the shared objects its properties may reference.

Fix in Claude Code Fix in Codex

Comment on lines +213 to +215
if ($accessor === null
|| ($resolver !== Facade::class && $resolver !== Date::class)
|| $facade->getNativeMethod('getFacadeRoot')->getDeclaringClass()->getName() !== Facade::class) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Custom facade calls get misread

resolveRootClass() checks overrides of resolveFacadeInstance() and getFacadeRoot(), but not __callStatic(). A facade subclass can override __callStatic() to handle a call itself or send it elsewhere. The new rules still inspect the registered root and can report Unsupported or lifetime restrictions on a method that never runs.

Treat unknown dispatch overrides as unknown, while preserving the known forwarding behavior of Hash and Crypt.

Fix in Claude Code Fix in Codex

Comment on lines +476 to +477
if (! $last instanceof Expression || ! $last->expr instanceof Throw_ || $this->leaves($statements, 0)) {
return false;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Earlier throws hide cancellation

rethrows() accepts a catch ending in throw $exception, but leaves() does not reject earlier throws that discard it. For example, if ($retryLimitReached) { throw new RuntimeException('Failed'); } throw $exception; passes the check around a query. The first branch turns cancellation into an ordinary error, so the new check misses it.

Check earlier throw paths too, and require them to preserve the caught exception.

Fix in Claude Code Fix in Codex

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant