Skip to content

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

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 lifetime and Unsupported method attributes, 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.


Summary by cubic

Adds 17 PHPStan rules that catch mistakes unique to Hypervel's long-lived Swoole workers: boot-only methods called while handling a request, pooled connections escaping the callback that borrowed them, and swallowed coroutine cancellations. A few runtime changes make the rules accurate, and a result-cache fix makes warm package analysis reanalyze nothing.

Runtime changes

  • Facade roots now follow container lifetimes: facades over bind() or transient services keep a per-coroutine root instead of caching the first request's object for the worker.
  • date is now bound in FoundationServiceProvider; a Date facade subclass's DEFAULT_FACADE only applies while date is unbound.
  • ConnectionResolverInterface::connection() and its implementations now return the driver-neutral Connection instead of ConnectionInterface, removing several instanceof checks and ignores.
  • Pooled filesystem, Redis, and SQS borrowing callbacks are generic over their return type.
  • Shipped logging config uses the named syslog facility 'user' instead of the integer LOG_USER.
  • LoadEnvironmentVariables no longer calls http_response_code().
  • Testbench's result cache tracks Composer metadata through the vendor directory it links, so warm package-analysis runs reuse results instead of reanalyzing about a third of the repository.

The rules

  • Also reports native request APIs, discarded and mutable dates, uncast config env() defaults, untyped config getters, observe() for owned models, binding cycles, raw coroutines, Socialite provider properties, Swoole tables created in providers, and request-dependent BindWhen conditions.
  • Every rule except unsupported calls has a configuration switch, and each documents its legitimate exception.
  • Rules judge code against the booted container's registrations and request, job, and operation contexts, and only flag worker-shared receivers with evidence the receiver is the shared instance.
  • Static analysis and porting docs cover each rule with an example, the fix, and its exceptions.

Written for commit c329d6f. Summary will update on new commits.

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.

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

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

Copy link
Copy Markdown

PR Summary by Qodo

Add PHPStan safeguards for long-lived Hypervel workers

✨ Enhancement 🐞 Bug fix 🧪 Tests 📝 Documentation ⚙️ Configuration changes 🕐 40+ Minutes

Grey Divider

AI Description

• Add PHPStan diagnostics for worker-lifetime, coroutine, resource, configuration, and framework API
 mistakes.
• Correct facade lifetimes, database resolver types, callback signatures, and package-analysis cache
 tracking.
• Document each rule and cover diagnostics, exceptions, and runtime behavior with tests.
Diagram

graph TD
  A["Application code"] --> B["PHPStan collectors"] --> C["Worker lifetime model"] --> D["Safety rules"] --> E["Actionable diagnostics"]
  F["Booted container"] --> C
  G["Source reflection"] --> B
  G --> D
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Resolve facade roots on every call
  • ➕ Avoids separate coroutine-local facade root storage and invalidation.
  • ➖ Changes existing caching semantics and repeatedly resolves transient services.
2. Discover facade targets by resolving them
  • ➕ Could identify targets of more dynamic factories.
  • ➖ Executes application code during analysis and risks side effects or misleading analysis state.

Recommendation: Keep the PR's lifetime-aware caching and reflection-based analysis. They preserve shared-service behavior while preventing cross-request root reuse, and avoid executing application factories. Review the conservative boundaries of receiver and facade inference particularly carefully.

Files changed (196) +12733 / -972

Enhancement (40) +5822 / -100
RedisStore.phpDocument Redis resource borrowing +5/-0

Document Redis resource borrowing

• Adds callback type information so analysis can identify borrowed connections and preserve return types.

src/cache/src/RedisStore.php

Container.phpDeclare resolvedShared() +5/-0

Declare resolvedShared()

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

src/contracts/src/Container/Container.php

ConnectionResolverInterface.phpRequire Connection from resolvers +4/-1

Require Connection from resolvers

• Changes the resolver return contract to the type already required by Eloquent and migrations.

src/database/src/ConnectionResolverInterface.php

HasAttributes.phpClarify date attribute typing +1/-0

Clarify date attribute typing

• Adds type information needed by the strengthened date analysis.

src/database/src/Eloquent/Concerns/HasAttributes.php

InteractsWithPooledFilesystem.phpPreserve pooled callback return types +19/-0

Preserve pooled callback return types

• Adds generic return and concrete resource-parameter annotations to filesystem borrowing methods.

src/filesystem/src/Concerns/InteractsWithPooledFilesystem.php

FilesystemAdapter.phpType filesystem borrowing callbacks +12/-0

Type filesystem borrowing callbacks

• Propagates callback return types and narrowed resource parameters through the adapter.

src/filesystem/src/FilesystemAdapter.php

RestrictedCallCollector.phpCollect attributed method calls +270/-0

Collect attributed method calls

• Records restricted calls, receivers, arguments, and source context for lifetime-aware reporting.

src/phpstan/src/Collectors/RestrictedCallCollector.php

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

Collect cancellation-sensitive catches

• Identifies catch blocks surrounding operations that can propagate cancellation.

src/phpstan/src/Collectors/SwallowedCancellationCollector.php

WorkerStateClassCollector.phpCollect listener and observer contexts +103/-10

Collect listener and observer contexts

• Records queued classes, observer registrations, and event associations for lifetime analysis.

src/phpstan/src/Collectors/WorkerStateClassCollector.php

WorkerStateParameterCollector.phpTrack injected parameter facts +5/-2

Track injected parameter facts

• Extends parameter collection to support shared-receiver inference.

src/phpstan/src/Collectors/WorkerStateParameterCollector.php

WorkerStateWriteCollector.phpTrack worker-state writes +9/-1

Track worker-state writes

• Expands recorded writes for contextual worker-state diagnostics.

src/phpstan/src/Collectors/WorkerStateWriteCollector.php

ReflectionHelper.phpImprove reflected attribute inspection +19/-0

Improve reflected attribute inspection

• Adds helpers for reading attribute arguments without running application code.

src/phpstan/src/Reflection/ReflectionHelper.php

SourceFiles.phpImprove source ownership detection +31/-3

Improve source ownership detection

• Extends source-file classification used by application-specific rules.

src/phpstan/src/Reflection/SourceFiles.php

BindingCycleRule.phpDetect container alias binding cycles +143/-0

Detect container alias binding cycles

• Reports bindings whose key resolves through an alias back to itself.

src/phpstan/src/Rules/BindingCycleRule.php

BorrowedResourceEscapeRule.phpDetect escaped borrowed resources +865/-0

Detect escaped borrowed resources

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

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

NoRawCoroutinesRule.phpReject unmanaged coroutine creation +118/-0

Reject unmanaged coroutine creation

• Reports direct Swoole or engine coroutine starts outside approved framework primitives.

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

RestrictedAttributeRule.phpValidate lifetime marker arguments +117/-0

Validate lifetime marker arguments

• Reports attribute arguments that misrepresent a method's execution restriction.

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

RestrictedCallRule.phpReport calls outside permitted lifetimes +161/-0

Report calls outside permitted lifetimes

• Checks collected BootOnly and test-only calls against operation context and proven shared receivers.

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

SwallowedCancellationRule.phpReport swallowed cancellation +93/-0

Report swallowed cancellation

• Evaluates collected catch behavior in request, job, and operation contexts.

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

SwooleTableInProviderRule.phpFlag provider-created Swoole tables +85/-0

Flag provider-created Swoole tables

• Reports tables constructed in provider registration or boot methods that each process executes.

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

WorkerLifetimes.phpModel more operation and shared-receiver contexts +303/-66

Model more operation and shared-receiver contexts

• Adds request, queue, server-event, and observer contexts plus injected-instance lifetime reasoning.

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

DiscardedDateRule.phpDetect discarded immutable date results +223/-0

Detect discarded immutable date results

• Reports immutable date modifications whose returned value is unused.

src/phpstan/src/Rules/DiscardedDateRule.php

ManagedTransactionRule.phpProtect managed transaction boundaries +363/-0

Protect managed transaction boundaries

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

src/phpstan/src/Rules/ManagedTransactionRule.php

MigrationColumnTypeRule.phpWarn about selected migration column types +82/-0

Warn about selected migration column types

• Optionally reports json, enum, and set columns without requiring changes to existing migrations.

src/phpstan/src/Rules/MigrationColumnTypeRule.php

NoMutableDatesRule.phpFlag mutable Carbon creation +87/-0

Flag mutable Carbon creation

• Reports mutable dates that risk state changes across worker requests.

src/phpstan/src/Rules/NoMutableDatesRule.php

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

Flag process-level request APIs

• Reports native headers, sessions, cookies, and superglobals that bypass coroutine request state.

src/phpstan/src/Rules/NoNativeRequestApisRule.php

ObserverAttributeRule.phpPrefer model observer attributes +195/-0

Prefer model observer attributes

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

src/phpstan/src/Rules/ObserverAttributeRule.php

RequestDependentBindingRule.phpDetect request-dependent conditional bindings +473/-0

Detect request-dependent conditional bindings

• Reports BindWhen conditions that capture current request or user state for worker-long bindings.

src/phpstan/src/Rules/RequestDependentBindingRule.php

SocialitePropertyRule.phpProtect coroutine-local Socialite state +105/-0

Protect coroutine-local Socialite state

• Reports direct provider property access when its per-request value belongs in coroutine context.

src/phpstan/src/Rules/SocialitePropertyRule.php

TypedConfigGetterRule.phpRecommend typed config getters +142/-0

Recommend typed config getters

• Reports generic getters for application configuration keys with a stable known type.

src/phpstan/src/Rules/TypedConfigGetterRule.php

UncastEnvValueRule.phpRequire numeric and boolean env casts +200/-0

Require numeric and boolean env casts

• Reports config-file env reads whose typed defaults conceal string environment values.

src/phpstan/src/Rules/UncastEnvValueRule.php

UnsupportedCallRule.phpReport calls to Unsupported methods +118/-0

Report calls to Unsupported methods

• Includes optional attribute guidance in the main diagnostic so terminal output retains it.

src/phpstan/src/Rules/UnsupportedCallRule.php

ConfigSourceResolver.phpResolve application config sources +39/-0

Resolve application config sources

• Provides source-aware support for application-specific configuration checks.

src/phpstan/src/Support/ConfigSourceResolver.php

ConstructorParser.phpExtend constructor source inspection +31/-14

Extend constructor source inspection

• Improves parsing of construction paths used in binding and lifetime inference.

src/phpstan/src/Support/ConstructorParser.php

FacadeHelper.phpFind facade targets without resolving them +388/-0

Find facade targets without resolving them

• Uses accessors, registration facts, reflection, and source parsing to infer facade method targets conservatively.

src/phpstan/src/Support/FacadeHelper.php

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

Preserve SQS callback result types

• Makes withConnection() generic over the callback return value.

src/queue/src/QueuePoolProxy.php

RedisProxy.phpPreserve pinned connection result types +4/-0

Preserve pinned connection result types

• Adds callback return templates to pinned Redis borrowing.

src/redis/src/RedisProxy.php

App.phpAnnotate application facade behavior +1/-0

Annotate application facade behavior

• Clarifies the facade contract for static analysis.

src/support/src/Facades/App.php

Attribute.phpRefine cast attribute annotations +2/-2

Refine cast attribute annotations

• Adjusts static types used by date and Eloquent 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 resource types to filesystem stubs.

types/Filesystem/Filesystem.php

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

Use a named syslog facility

• Changes the default facility from an integer constant to Monolog's string name.

skeletons/hypervel/config/logging.php

logging.phpUse a named syslog facility +1/-1

Use a named syslog facility

• Makes the starter kit's syslog default compatible with string environment values.

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

Container.phpExpose whether a resolved binding is shared +12/-0

Expose whether a resolved binding is shared

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

src/container/src/Container.php

logging.phpCorrect the syslog default +1/-1

Correct the syslog default

• Uses the facility name user instead of the integer LOG_USER.

src/foundation/config/logging.php

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

Remove FPM response bootstrap behavior

• Stops calling http_response_code() while bootstrapping the CLI worker.

src/foundation/src/Bootstrap/LoadEnvironmentVariables.php

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

Register the date factory at boot

• Uses singletonIf() to bind date without replacing an application-provided binding.

src/foundation/src/Providers/FoundationServiceProvider.php

ConfigHelper.phpLimit inferred config types to application reads +51/-4

Limit inferred config types to application reads

• Distinguishes the application's repository from independently constructed or injected repositories.

src/phpstan/src/Internal/ConfigHelper.php

ConfigCollectionRule.phpRespect application config ownership +1/-1

Respect application config ownership

• Aligns collection diagnostics with application-only configuration inference.

src/phpstan/src/Rules/ConfigCollectionRule.php

ConfigTypeRule.phpRespect application config ownership +1/-1

Respect application config ownership

• Keeps config type checks aligned with the actual application repository.

src/phpstan/src/Rules/ConfigTypeRule.php

RuntimeInputsResultCacheValueExtension.phpTrack vendor directory presence +8/-2

Track vendor directory presence

• Adds directory-presence inputs to result-cache keys.

src/phpstan/src/RuntimeInputsResultCacheValueExtension.php

RuntimeInputs.phpStabilize Testbench result-cache inputs +26/-7

Stabilize Testbench result-cache inputs

• Tracks Composer metadata at the skeleton and working vendor sources instead of the disposable runtime copy.

src/phpstan/src/Support/RuntimeInputs.php

Date.phpAvoid request-time date binding +3/-3

Avoid request-time date binding

• Keeps the standalone fallback from swapping a new container instance; framework registration now owns the binding.

src/support/src/Facades/Date.php

Facade.phpCache roots for their actual lifetime +53/-18

Cache roots for their actual lifetime

• Keeps shared roots worker-wide and other cacheable roots coroutine-local, with generation-based invalidation.

src/support/src/Facades/Facade.php

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

Store coroutine-local facade roots

• Adds the state object used for per-coroutine root caching.

src/support/src/Facades/FacadeState.php

Bootstrapper.phpExpose runtime vendor source +20/-1

Expose runtime vendor source

• Provides the working vendor path used when Testbench links a runtime application copy.

src/testbench/src/Bootstrapper.php

Refactor (25) +1050 / -668
CachedConnectionResolver.phpNarrow cached resolver returns +1/-1

Narrow cached resolver returns

• Declares the concrete driver-neutral Connection return type.

src/database/src/CachedConnectionResolver.php

Manager.phpAlign capsule connection types +3/-3

Align capsule connection types

• Uses Connection where the resolver contract now guarantees it.

src/database/src/Capsule/Manager.php

ConnectionResolver.phpReturn concrete database connections +9/-10

Return concrete database connections

• Narrows resolved and held connections to Connection and removes redundant type checks.

src/database/src/ConnectionResolver.php

DatabaseManager.phpSimplify manager connection handling +7/-14

Simplify manager connection handling

• Uses the stronger resolver contract instead of defensive interface checks and type ignores.

src/database/src/DatabaseManager.php

Model.phpRemove obsolete connection annotation +0/-1

Remove obsolete connection annotation

• Drops a type workaround made unnecessary by the resolver return contract.

src/database/src/Eloquent/Model.php

Migrator.phpTrust the resolver connection contract +1/-1

Trust the resolver connection contract

• Removes a redundant connection type workaround.

src/database/src/Migrations/Migrator.php

SimpleConnectionResolver.phpNarrow simple resolver returns +1/-1

Narrow simple resolver returns

• Declares Connection as the resolver's return type.

src/database/src/SimpleConnectionResolver.php

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

Align test resolver connection types

• Updates the test resolver to return Connection and simplifies checks under the stronger contract.

src/foundation/src/Testing/DatabaseConnectionResolver.php

DatabaseTransactions.phpSimplify transaction test connections +1/-4

Simplify transaction test connections

• Removes type workarounds superseded by the Connection resolver contract.

src/foundation/src/Testing/DatabaseTransactions.php

RefreshDatabase.phpSimplify refresh-database connections +1/-5

Simplify refresh-database connections

• Uses the guaranteed Connection return type in test database setup.

src/foundation/src/Testing/RefreshDatabase.php

WorkerStateCallCollector.phpConsolidate collected call facts +4/-38

Consolidate collected call facts

• Refines call collection for shared worker-context analysis.

src/phpstan/src/Collectors/WorkerStateCallCollector.php

InspectsConnections.phpShare connection-call inspection +100/-0

Share connection-call inspection

• Provides reusable connection identification for resource and transaction rules.

src/phpstan/src/Concerns/InspectsConnections.php

InspectsQueuedJobs.phpReuse queue inspection logic +2/-18

Reuse queue inspection logic

• Simplifies queued-job detection around the shared lifetime model.

src/phpstan/src/Concerns/InspectsQueuedJobs.php

CoroutineRuleHelper.phpCentralize coroutine safety inspection +391/-0

Centralize coroutine safety inspection

• Shares cancellation and coroutine-call classification across rules.

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

ExactCancellationCatchRule.phpReuse cancellation inspection +1/-247

Reuse cancellation inspection

• Delegates exact-catch detection to the shared coroutine helper.

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

NoCoroutinesInTransactionsRule.phpReuse coroutine classification +1/-3

Reuse coroutine classification

• Aligns transaction coroutine detection with the shared helper.

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

WorkerStateAnalysis.phpShare collected worker-state analysis +267/-0

Share collected worker-state analysis

• Builds a reusable analysis from collected calls, routes, observers, and booted container lifetimes.

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

WorkerStateHelper.phpExpose reusable worker-context queries +205/-24

Expose reusable worker-context queries

• Supports context, receiver, and dependency reasoning for multiple worker-safety rules.

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

WorkerStateRule.phpUse shared worker-state model +20/-181

Use shared worker-state model

• Removes duplicated analysis construction while retaining existing worker-state diagnostics.

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

NoEnvCallsOutsideOfConfigRule.phpConsolidate environment-call detection +5/-59

Consolidate environment-call detection

• Reuses shared environment-reading inspection rather than duplicating recognition logic.

src/phpstan/src/Rules/NoEnvCallsOutsideOfConfigRule.php

JobDispatchedInTransactionUsesAfterCommitRule.phpReuse connection analysis for queued jobs +7/-26

Reuse connection analysis for queued jobs

• Simplifies detection of transaction-sensitive dispatches using shared inspection.

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

NoPipeSeparatedValidationRulesRule.phpSimplify validation rule inspection +0/-4

Simplify validation rule inspection

• Removes unnecessary code from the existing validation diagnostic.

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

DatabaseQueue.phpUse the concrete connection contract +2/-4

Use the concrete connection contract

• Removes database connection checks made redundant by resolver typing.

src/queue/src/DatabaseQueue.php

DatabaseOutboxRepository.phpSimplify outbox connection typing +2/-2

Simplify outbox connection typing

• Uses Connection directly where the database resolver guarantees it.

src/queue/src/Outbox/DatabaseOutboxRepository.php

DB.phpAlign DB facade connection types +2/-2

Align DB facade connection types

• Updates facade typing to reflect Connection-returning resolvers.

src/support/src/Facades/DB.php

Tests (97) +4529 / -137
AuthDatabaseTokenRepositoryTest.phpAlign token tests with connection typing +5/-4

Align token tests with connection typing

• Adjusts database test doubles for the narrowed resolver contract.

tests/Auth/AuthDatabaseTokenRepositoryTest.php

AuthDatabaseUserProviderTest.phpAlign user-provider database mocks +7/-6

Align user-provider database mocks

• Updates connection expectations for the concrete resolver return type.

tests/Auth/AuthDatabaseUserProviderTest.php

AuthManagerTest.phpAlign auth manager database fixtures +3/-3

Align auth manager database fixtures

• Adjusts test connection setup for the stronger contract.

tests/Auth/AuthManagerTest.php

AuthPasswordBrokerManagerTest.phpAlign password broker database fixtures +3/-3

Align password broker database fixtures

• Updates connection doubles to satisfy Connection returns.

tests/Auth/AuthPasswordBrokerManagerTest.php

CacheDatabaseLockTest.phpAlign database lock mocks +2/-2

Align database lock mocks

• Updates mocked database connections for the resolver type change.

tests/Cache/CacheDatabaseLockTest.php

CacheDatabaseStoreTest.phpAlign database cache mocks +4/-4

Align database cache mocks

• Updates mocked connections to match Connection-returning resolvers.

tests/Cache/CacheDatabaseStoreTest.php

ContainerTest.phpTest resolved shared lifetimes +47/-0

Test resolved shared lifetimes

• Covers resolvedShared() across binding and resolution behaviors.

tests/Container/ContainerTest.php

DatabaseMigratorTest.phpAdapt migrator resolver tests +1/-2

Adapt migrator resolver tests

• Aligns migrator mocks with the concrete connection contract.

tests/Database/DatabaseMigratorTest.php

DatabaseMonitorCommandTest.phpAdapt database monitor mocks +4/-4

Adapt database monitor mocks

• Uses compatible Connection test doubles.

tests/Database/DatabaseMonitorCommandTest.php

FoundationServiceProviderTest.phpTest date factory registration +10/-0

Test date factory registration

• Checks framework date binding and precedence of earlier application bindings.

tests/Foundation/Providers/FoundationServiceProviderTest.php

InteractsWithParallelDatabaseTest.phpAdapt parallel database tests +3/-3

Adapt parallel database tests

• Aligns connection fixtures with the resolver contract.

tests/Foundation/Testing/Concerns/InteractsWithParallelDatabaseTest.php

DatabaseTransactionsTest.phpAdapt transaction test connections +2/-3

Adapt transaction test connections

• Updates resolver doubles for concrete Connection returns.

tests/Foundation/Testing/DatabaseTransactionsTest.php

RefreshDatabaseTest.phpAdapt refresh-database fixtures +6/-5

Adapt refresh-database fixtures

• Uses connection mocks compatible with the narrowed resolver contract.

tests/Foundation/Testing/RefreshDatabaseTest.php

ProviderSources.phpAdjust config composition fixture +2/-3

Adjust config composition fixture

• Keeps provider-sourced configuration examples consistent with application-only inference.

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

FrenchGreeterServiceProvider.phpAdd package registration cache fixture +18/-0

Add package registration cache fixture

• Provides a package service provider for runtime input dependency tests.

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

MarkedMethod.phpAdd attributed source fixture +15/-0

Add attributed source fixture

• Supplies marked methods for reflection and source-file tests.

tests/PHPStan/Fixtures/SourceFiles/MarkedMethod.php

Defaults.phpAdd worker-context defaults fixture +10/-0

Add worker-context defaults fixture

• Supplies test values for context and dependency invalidation.

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

ImportFailed.phpAdd queued-event fixture +16/-0

Add queued-event fixture

• Exercises job-event listener context inference.

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

ImportJob.phpAdd job lifetime fixture +21/-0

Add job lifetime fixture

• Exercises collected job-handler and dependency contexts.

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

RebuildReport.phpAdd report operation fixture +15/-0

Add report operation fixture

• Provides an operation for worker-context dependency tests.

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

Registry.phpAdd shared registry fixture +18/-0

Add shared registry fixture

• Models a container-held receiver for restricted-call tests.

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

ReportFacade.phpAdd report facade fixture +15/-0

Add report facade fixture

• Exercises facade-to-root discovery without resolving the facade.

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

Reporting.phpAdd reporting fixture +13/-0

Add reporting fixture

• Provides a report contract for dependency-based analysis.

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

SupportedReporter.phpAdd supported reporter fixture +12/-0

Add supported reporter fixture

• Provides an allowed implementation in restricted-call dependency tests.

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

UnsupportedReporter.phpAdd unsupported reporter fixture +17/-0

Add unsupported reporter fixture

• Provides an attributed target for dependency and diagnostic tests.

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

IntegrationTest.phpExercise rules in integration analysis +7/-2

Exercise rules in integration analysis

• Expands integration coverage for the new analyzer behavior.

tests/PHPStan/Integration/IntegrationTest.php

ResultCacheTestCase.phpSupport result-cache test setup +2/-0

Support result-cache test setup

• Adds shared test support for cache-input verification.

tests/PHPStan/ResultCacheTestCase.php

BindingCycleRuleTest.phpTest binding cycle diagnostics +40/-0

Test binding cycle diagnostics

• Checks cyclic aliases and legitimate registrations.

tests/PHPStan/Rules/BindingCycleRuleTest.php

ConfigCollectionRuleTest.phpTest application config ownership +4/-4

Test application config ownership

• Adjusts expected diagnostics for non-application repositories.

tests/PHPStan/Rules/ConfigCollectionRuleTest.php

BorrowedResourceEscapeRuleTest.phpTest borrowed resource escapes +78/-0

Test borrowed resource escapes

• Covers returned, stored, and asynchronously captured resources alongside allowed callback use.

tests/PHPStan/Rules/Coroutine/BorrowedResourceEscapeRuleTest.php

NoRawCoroutinesRuleTest.phpTest raw coroutine diagnostics +40/-0

Test raw coroutine diagnostics

• Checks direct starts and approved alternatives.

tests/PHPStan/Rules/Coroutine/NoRawCoroutinesRuleTest.php

RestrictedAttributeRuleTest.phpTest restriction marker validation +57/-0

Test restriction marker validation

• Checks valid and misleading lifetime attribute arguments.

tests/PHPStan/Rules/Coroutine/RestrictedAttributeRuleTest.php

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

Test context-aware restricted calls

• Covers execution contexts, shared receivers, permitted calls, and diagnostic messages.

tests/PHPStan/Rules/Coroutine/RestrictedCallRuleTest.php

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

Test standalone restricted-call configuration

• Verifies the rule operates with focused extension settings.

tests/PHPStan/Rules/Coroutine/RestrictedCallsOnlyTest.php

SwallowedCancellationRuleTest.phpTest cancellation propagation checks +95/-0

Test cancellation propagation checks

• Covers sensitive operations, broad catches, and valid rethrows or exclusions.

tests/PHPStan/Rules/Coroutine/SwallowedCancellationRuleTest.php

SwooleTableInProviderRuleTest.phpTest provider table warnings +33/-0

Test provider table warnings

• Checks provider lifecycle placement and permitted table construction.

tests/PHPStan/Rules/Coroutine/SwooleTableInProviderRuleTest.php

WorkerStateRuleTest.phpExtend worker-state context tests +43/-4

Extend worker-state context tests

• Verifies existing state checks across newly recognized listener and observer contexts.

tests/PHPStan/Rules/Coroutine/WorkerStateRuleTest.php

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

Fixture borrowed-resource lifetimes

• Supplies escaping and safe borrowing examples.

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

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

Fixture exact cancellation catches

• Supplies cases for the shared cancellation helper.

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

raw-coroutines.phpFixture coroutine start patterns +46/-0

Fixture coroutine start patterns

• Provides direct and managed coroutine calls.

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

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

Fixture restriction attributes

• Provides valid and invalid marker arguments.

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

restricted-calls-only.neonConfigure focused restricted-call fixture +9/-0

Configure focused restricted-call fixture

• Isolates restricted-call analysis for its focused test.

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

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

Fixture standalone restricted calls

• Provides calls for the focused rule configuration.

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

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

Fixture lifetime-restricted calls

• Exercises attributed targets through facades, injection, and differing execution contexts.

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

swallowed-cancellations.phpFixture cancellation handling branches +496/-0

Fixture cancellation handling branches

• Provides operation and catch combinations that should report or remain allowed.

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

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

Fixture Swoole table construction

• Contrasts provider lifecycle creation with allowed contexts.

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

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

Fixture listener and observer contexts

• Exercises request, queue, server-operation, and observer lifetime detection.

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

DiscardedDateRuleTest.phpTest discarded date results +72/-0

Test discarded date results

• Checks unused immutable modifiers and valid result consumption.

tests/PHPStan/Rules/DiscardedDateRuleTest.php

ManagedTransactionRuleTest.phpTest managed transaction safeguards +51/-0

Test managed transaction safeguards

• Covers premature endings, rollBack(0), and legitimate transaction handling.

tests/PHPStan/Rules/ManagedTransactionRuleTest.php

MigrationColumnTypeRuleTest.phpTest optional migration warnings +32/-0

Test optional migration warnings

• Checks selected column types and opt-in configuration.

tests/PHPStan/Rules/MigrationColumnTypeRuleTest.php

NoEnvCallsOutsideOfConfigRuleTest.phpRetest environment call detection +11/-5

Retest environment call detection

• Aligns existing expectations with shared environment-reader logic.

tests/PHPStan/Rules/NoEnvCallsOutsideOfConfigRuleTest.php

NoMutableDatesRuleTest.phpTest mutable date detection +41/-0

Test mutable date detection

• Distinguishes mutable Carbon construction from allowed immutable usage.

tests/PHPStan/Rules/NoMutableDatesRuleTest.php

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

Test native request API detection

• Covers process-level functions and superglobals alongside permitted cases.

<a href='https://github.com...

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

Copy link
Copy Markdown

Code Review by Qodo

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

Grey Divider


Remediation recommended

1. Callable creation triggers call warnings 🐞 Bug ≡ Correctness
Description
RestrictedCallCollector and UnsupportedCallRule inspect first-class callable expressions as
method invocations without checking isFirstClassCallable(). Assigning a restricted or unsupported
method to a callable therefore produces a diagnostic before that method is invoked, including when
the callable is never used.
Code

src/phpstan/src/Collectors/RestrictedCallCollector.php[R81-83]

+        // PHPStan also analyzes a nullsafe call as a method call, and a first-class callable as a separate node.
+        if (! ($node instanceof MethodCall || $node instanceof StaticCall) || ! $node->name instanceof Identifier) {
+            return null;
Evidence
Both new checks accept method-call nodes and resolve their target without excluding first-class
callables. The existing worker-state collector previously had an explicit isFirstClassCallable()
guard, demonstrating the distinction needed for this AST node.

src/phpstan/src/Collectors/RestrictedCallCollector.php[79-99]
src/phpstan/src/Rules/UnsupportedCallRule.php[67-80]
src/phpstan/src/Support/FacadeHelper.php[153-184]

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

## Issue description
First-class callable creation is treated as an invocation by both restricted-call and unsupported-call analysis, producing diagnostics for methods that have not run.

## Fix Focus Areas
- src/phpstan/src/Collectors/RestrictedCallCollector.php[79-89]
- src/phpstan/src/Rules/UnsupportedCallRule.php[67-76]

## Recommended Fix
Check `isFirstClassCallable()` before resolving or reporting a call in both paths. Add fixtures that create, but do not invoke, restricted and unsupported method callables.

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


2. Filesystem callbacks can receive wrong types 🐞 Bug ≡ Correctness
Description
The new withClient(), withAdapter() and withDriver() templates infer a callback's narrow
parameter type without tying it to the object that the disk actually supplies. When a callback typed
for an S3 client or adapter is passed a GCS-backed pooled disk, the callback receives an
incompatible object and fails with a type error.
Code

src/filesystem/src/Concerns/InteractsWithPooledFilesystem.php[R595-598]

+     * @template TClient
+     * @template T
+     *
+     * @param Closure(TClient): T $callback
Evidence
The same ClientPooledFilesystem type is constructed with either S3 or GCS client and adapter
factories, while its borrowed-accessor path passes the resulting object directly to the callback.
The added type fixture nonetheless accepts an S3-only callback for a parameter typed merely
ClientPooledFilesystem.

src/filesystem/src/Concerns/InteractsWithPooledFilesystem.php[577-600]
src/filesystem/src/FilesystemManager.php[319-350]
src/filesystem/src/ClientPooledFilesystem.php[173-185]
types/Filesystem/Filesystem.php[16-28]

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

## Issue description
Filesystem callback templates accept concrete parameter types that a generic disk cannot guarantee it will pass, allowing statically accepted callbacks to fail at runtime.

## Fix Focus Areas
- src/filesystem/src/Concerns/InteractsWithPooledFilesystem.php[535-600]
- src/filesystem/src/FilesystemAdapter.php[1101-1125]
- types/Filesystem/Filesystem.php[16-29]

## Recommended Fix
Tie the callback parameter types to types carried by the disk instance, or require callbacks to accept the guaranteed base type when the concrete type is unknown. Update type tests so an S3-only callback is not accepted for a generic pooled disk.

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



Informational

3. Borrowed connections wrapped in an array go unreported 🐞 Bug ≡ Correctness
Description
getBorrowedKind() only recognises a tracked variable, an assignment, or a direct getter call such
as $connection->client(), so an Array_ expression always returns null. As a result,
$this->connections = [$connection], return [$connection], and `static::$pool[] =
[$connection->client()]` all let the pooled resource outlive its callback with no warning, even
though the documentation says storing or returning a borrowed resource is reported.
Code

src/phpstan/src/Rules/Coroutine/BorrowedResourceEscapeRule.php[R537-545]

+    private function getBorrowedKind(Expr $expr, array $tracked, array $owners, string $resource, Scope $scope): ?string
+    {
+        if ($expr instanceof Assign || $expr instanceof AssignRef) {
+            return $this->getBorrowedKind($expr->expr, $tracked, $owners, $resource, $scope);
+        }
+
+        if ($expr instanceof Variable) {
+            return is_string($expr->name) ? $tracked[$expr->name] ?? null : null;
+        }
Evidence
getBorrowedKind recurses only through Assign/AssignRef and resolves only Variable and method or
static calls. Every other expression type, including Array_, falls through to return null. The
return and store paths report only when this method returns a non-null kind, so a resource placed
inside an array literal is never flagged.

src/phpstan/src/Rules/Coroutine/BorrowedResourceEscapeRule.php[537-573]
src/phpstan/src/Rules/Coroutine/BorrowedResourceEscapeRule.php[360-371]

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

## Issue description
`BorrowedResourceEscapeRule::getBorrowedKind()` returns null for array literals, so a borrowed resource that is returned or stored inside an array (`[$connection]`, `['c' => $connection->client()]`) is not reported.

## Fix Focus Areas
- src/phpstan/src/Rules/Coroutine/BorrowedResourceEscapeRule.php[537-573]

## Recommended Fix
Add a branch for `PhpParser\Node\Expr\Array_` that walks `$expr->items`, including nested arrays. For each non-null item, call `getBorrowedKind($item->value, ...)` and return the first non-null kind. Add fixture cases to tests/PHPStan/Rules/Coroutine/data/borrowed-resources.php for returning an array and for storing one on a property.

ⓘ 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: Dense, cross-cutting logic spans many independent analyzer and runtime paths.

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

@greptile-apps

greptile-apps Bot commented Nov 13, 2026 •

Copy link
Copy Markdown

RetriggerView in GreptileConfidence Score: 4/5 Tier: plus

[High impact] The PR adds checks and changes runtime behavior across worker requests and shared framework services.

The PR appears safe to merge, with three non-blocking fixes recommended for the new analyzer rules.

Fix All in Claude CodeFindings

  1. P2 Safe callbacks get flagged ▶
  2. P2 Lost cancellation goes unnoticed ▶
  3. P2 Class constants lose their scope ▶

Summary

Adds PHPStan checks for long-lived workers, expands request and job context analysis, and makes non-shared facade roots coroutine-local. It also updates database resolver types and Testbench result-cache inputs.

  • PHPStan now points out worker-specific mistakes and unsafe framework patterns.
  • Facades now keep each service for the lifetime the container gives it.
  • Database resolvers now return Hypervel’s concrete connection type.
  • Warm package analysis now tracks Composer metadata in Testbench’s real vendor directories.
  • Pooled-resource callbacks now keep the value type returned by their callback.
  • Framework defaults now use a named syslog facility and bind the date factory at startup.

Intentional changes acknowledged by binaryfire: resolver connections now return Connection, the framework binds date, and arbitrary repository parameters and properties no longer receive application-config types.

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

Comment on lines +750 to +756
foreach ((new NodeFinder)->findInstanceOf($closure->expr, Variable::class) as $variable) {
if (is_string($variable->name)) {
$used[$variable->name] = true;
}
}

$captured = array_intersect_key($captured, $used);

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 Safe callbacks get flagged

getCapturedKind() counts variable names inside nested functions without checking whether their parameters hide the outer variable. Inside Redis::withConnection(), go(fn () => array_map(fn ($connection) => $connection, ['safe'])) is therefore reported as capturing the borrowed connection, although it never uses that connection.

Respect nested function scopes when collecting captured names. Otherwise, renaming an unrelated inner parameter changes whether safe code gets a warning.

Fix in Claude Code Fix in Codex

Comment on lines +476 to +478
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 Lost cancellation goes unnoticed

rethrows() accepts a trailing throw $exception even when an earlier branch throws a different exception without keeping the original. For example, a catch containing if (Coroutine::causedByCancellation($exception)) { throw new RuntimeException('Canceled'); } followed by throw $exception passes this check.

The cancellation path never reaches that final rethrow. The new exception loses the cancellation, but the analyzer gives no warning. Check earlier throws before accepting the catch as safe.

Fix in Claude Code Fix in Codex

Comment on lines +200 to +205
$namespace = $this->getNamespace($function, $file);
$declarationScope = $this->scopeFactory->create(ScopeContext::create($file));

$declarationScope = $namespace === null ? $declarationScope : $declarationScope->enterNamespace($namespace);

return $this->findRead($function->stmts ?? [], $this->getContainerVariable($function->params), $declarationScope) === 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.

P2 Class constants lose their scope

Static method conditions are scanned in a scope containing only their file and namespace. A condition such as Conditions::active(...), whose body calls $app->make(self::REQUEST) with self::REQUEST = 'request', needs its declaring class to resolve that constant.

getRequestedAbstracts() otherwise cannot identify the request key, so the request-dependent condition escapes the check. Enter the method's declaring class before scanning its body, and cover this case in the tests.

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