Repository navigation
Replace lifetime docblock tags with method attributes - #741
Conversation
Introduce hypervel/attributes with BootOnly, TestsOnly, BootOrTestsOnly and Unsupported markers under Hypervel\Support\Attributes. Lifetime markers can describe argument-dependent restrictions and methods that only affect a shared receiver. Wire the package into framework installation and Components autoloading without adding runtime enforcement or dependencies beyond PHP.
Give each container a lazily allocated context prefix so independent containers cannot adopt or invalidate each other’s scoped services or share circular-resolution stacks. Retain the existing context copying behavior and number of context lookups. Cloned containers start with shallow copies of currently resolved scoped values while owning their cache entries independently. Cover independent resolution, invalidation, cloning and child-coroutine inheritance, and mark container mutators according to their corrected ownership.
Replace prose-tag matching with PHPStan attribute reflection for method bodies and promoted constructor parameters. Preserve the existing exemption for restricted setters, including attributes with argument conditions. Convert the rule fixtures and cover a conditional request setter alongside the existing unmarked write. Remove obsolete lifetime tags from the configuration stub.
Convert core package lifetime warnings to attributes while retaining useful explanations of shared state. Distinguish instance-owned state from global effects, leave target-dependent forwarding methods unmarked, and declare the attributes dependency in each using package. Mark unsupported stream operations and Str cache clearing, cover facade fake and registration wrappers, and allow Queue::popUsing(null) to remove a worker callback as its underlying API supports. Record the remaining conversions and the ArrayStore clone correction in the implementation plan.
Give each guard its own coroutine context keys so replacement guards do not adopt cached users, clients or credentials from an earlier instance. Preserve session and remember-cookie identifiers so persisted authentication still reloads normally. Convert auth, session, cookie, encryption and hashing lifecycle restrictions to attributes and declare their dependency. Cover replacement guards across authentication drivers, HTTP test state synchronization and session reauthentication.
Allocate memoized cache keys per manager so independent managers cannot reuse or evict each other's repositories. Give cloned array stores fresh context keys and shallow copies of their current values and locks. Convert cache lifecycle restrictions to attributes, mark unsupported any-mode reads and declare the attribute dependency. Regression coverage checks manager isolation and independent mutations through cloned repositories.
Build pinned-connection and deferred-release keys from the owning pool manager, caching the resulting keys on each proxy. Proxies sharing a manager and connection name continue to share their pin; independent managers no longer release or reuse each other's connections. Convert Redis and object-pool lifecycle restrictions to attributes and declare their dependencies. Update context-key inspection in existing tests and cover separate pins, shared pins and deferred cleanup.
Replace lifecycle prose tags with method attributes, distinguishing receiver-owned configuration from shared resource mutation. Mark intentionally unsupported filesystem accessors and declare the attribute dependencies. Update the active implementation plan with the completed package group and the context ownership corrections, retaining the remaining conversion and documentation work.
Convert broadcasting, bus, mail, queue, Horizon and pipeline lifetime restrictions to method attributes, with receiver conditions for instance-owned state. Preserve runtime recovery through pooled manager purges and exclusive borrowed-object configuration. Mark the queue connection contract and after-commit dispatcher setter consistently, declare the attribute dependencies, and update queue package metadata coverage.
Replace console, scheduling, concurrency and process-factory lifetime tags with attributes. Shared mutex configuration remains restricted while process recording helpers retain their supported conditional runtime behavior. Mark scheduled callback background execution as unsupported, declare the package dependencies and update console metadata coverage.
Mark process registration, server binding, signal listener setup and shared server configuration with lifecycle attributes. Keep ProcessManager running-state updates unmarked because both startup and shutdown legitimately own them. Add the corresponding contract marker and package dependencies, preserving existing behavior and checking Reverb and WebSocket server metadata.
Leave protected provider implementation helpers unmarked when only startup hooks call them, while retaining markers on generally callable registration APIs. Preserve useful existing explanations on contracts and mark the inherited application environment resolver consistently. Update the active plan with exclusive pool-borrow semantics, legitimate startup and shutdown calls, completed conversions and remaining work.
Replace database, schema, migration and Eloquent lifetime tags with method attributes. Preserve read-only morph-map calls and lazy model boot hooks, while marking event registration consistently across ordinary and soft-delete events. Describe dispatcher and transaction-manager restrictions on reusable physical connections, mark retained unsupported database APIs, and declare the attribute dependency with metadata coverage. Runtime behavior and signatures are unchanged.
Convert search engine configuration and Scout callbacks to lifecycle attributes, applying receiver conditions to engine-owned state. Keep model boot registration available during lazy initialization. Mark index creation methods that always reject calls, declare the attribute dependency and update package metadata coverage without changing engine behavior.
Replace lifecycle tags with attributes for data morph maps, pagination resolvers and permission configuration. Distinguish receiver-owned state from setters that update shared configuration and bindings. Mark retained unsupported data APIs, declare package dependencies, and update the implementation plan with completed conversions and the remaining package groups.
Explore replacing the manual static-reset registry with discovered method attributes. Record discovery, ordering, inheritance and optional-package constraints while keeping test execution free of repeated scanning.
Replace lifetime prose tags with attributes across application bootstrap, exception handling, middleware configuration, console registration and Vite. Mark configuration wrappers and align the application contract with its implementation. Limit instance-owned settings to shared receivers while retaining unconditional restrictions for global configuration. Keep lifecycle hooks and stable lazy initialization unrestricted, and declare the attributes dependency.
Convert shared HTTP factory, middleware, resource and routing configuration to lifetime attributes. Preserve runtime-safe reflection and routing cache resets without restrictions, and retain conditional getter behavior. Mark retained unsupported HTTP and transport methods, including stream rewind paths that always reject seeking. Add direct attributes dependencies to each affected package without changing runtime behavior.
Convert DI registries, proxy generation, server initialization and test infrastructure to method attributes. Align server initialization with its contract and declare the package dependencies. Leave aspect-chain and Eloquent caster cache clearing unrestricted because subsequent calls rebuild them from unchanged configuration. Update the implementation plan with these cache boundaries, lifecycle exclusions and remaining conversion work.
Allow Unsupported to carry an optional message while preserving bare markers. Supply verified alternatives for rejected cache, database, filesystem, HTTP, validation and transport methods. Keep runtime exceptions unchanged. The metadata gives static analysis the guidance needed to direct callers to supported APIs without inspecting method bodies.
Convert AI manager configuration, fake gateways and provider setters to lifetime attributes with shared-receiver conditions. Mark MCP route registration and client configuration according to the state each method owns. Describe unsupported provider and MCP content operations with useful guidance where an alternative exists. Declare attributes dependencies and update the implementation plan and approved guidance wording.
Qualify HTTP factory, cookie middleware, migration hook, pool callback and Redis configuration warnings for shared receivers. Independently constructed instances retain their own state, matching the conditional lifetime attributes.
Convert compiler, finder, head registry and Inertia configuration warnings to attributes, preserving receiver-specific restrictions and runtime-safe cache clearing. Keep forwarding methods unmarked where caller-supplied dependencies determine the effect. Document shared dispatcher and registry behavior without treating every independently built object as application-global. Declare the attributes dependencies.
Mark shared loader and translator mutations, validation factory configuration and DNS test overrides. Preserve getter calls to validation defaults through conditional attributes and keep injected-loader forwarders unrestricted. Declare the attributes dependencies and update validation package metadata coverage.
Convert process-wide prompt theme, cancellation, reset and test configuration to attributes while retaining runtime getter behavior. Add supported alternatives to the unsupported prompt methods on progress, spinner, stream and task objects. Declare the attributes package and preserve coroutine-local prompt settings.
Remove unconditional restrictions from exception configuration and event registration wrappers whose handlers or dispatchers can be independently constructed. Retain the target mutators and static-target restrictions, with accurate shared-instance guidance. Record the forwarding boundary and completed package conversions in the implementation plan.
…tributes Replace prose lifetime tags across Fortify, JWT, Passkeys, Passport, Sanctum and Socialite with method attributes and declare their package dependency. Preserve conditional getter behavior for Passport settings and Fortify feature options, including the distinction between omitted and explicit null consent durations. Restrict instance configuration only on shared receivers and mark the Socialite fake as test-only. Leave guard listener forwarders unmarked because the injected dispatcher owns the registration. Update the Passkeys dependency assertion alongside its manifest.
Convert lifetime tags in Doctor, gRPC, Image, Pennant, RateLimiter and Saloon to attributes, with direct dependencies and matching metadata assertions. Keep configuration writes and global descriptor registration unconditional, while owned instance registries are restricted only when shared. Mark explicit Pennant definitions in both argument forms without restricting its guarded lazy registration callers. Preserve unknown dispatcher ownership for Connection::listen(), mark global mock cleanup as test-only, and add actionable Unsupported guidance for health watches and cursor paginator pooling.
Replace lifetime tags in OpenTelemetry, Sentry and Telescope with attributes that distinguish shared instance configuration from global registrations. Mark Telescope startup and listener registration, retain conditional ownership explanations for injected forwarders, and describe the exception recorder handoff precisely. Declare the attributes dependency and complete Telescope direct-dependency assertions. Update the active plan to record the completed source conversion and Unsupported inventory, leaving guidance, documentation and final verification as the remaining work.
Explain the lifetime markers, argument conditions and Unsupported guidance in the static analysis documentation. Distinguish metadata and worker-state body exemptions from runtime enforcement and call-site checking, and link the existing worker-state section to the new examples. Update the authoring guide to use attributes instead of prose tags, retain the requirement to explain runtime harm, and keep flushState cleanup hooks unmarked. Document the exclusions for intentionally unsupported methods and require useful alternative guidance when available.
Add the attributes split repository and Packagist registration to release preparation. Record the Channel capability investigation without changing the existing unsupported methods. Mark the attributes implementation and verification complete in the active plan. Keep private consumer conversion dependent on the merge and the subsequent PHPStan rule work separate.
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the
|
|
@cubic-dev-ai review |
@binaryfire I have started the AI code review. It will take a few minutes to complete. |
Code Review by Qodo
1. The static analysis package omits the new attributes dependency
|
There was a problem hiding this comment.
8 issues found across 543 files
Confidence score: 4/5
GuardHelpersclones share the context prefix, so the clone and original can read and clear the same user state. Reset the context in__clone().- The lifetime attributes in
Broadcaster.php,HasAttributes.php,SessionGuard.php, andAspectManager.phpno longer match the intended boot/test restrictions, which can mislead worker-state analysis. Restore the appropriate attributes. EncryptCookies::except()is marked boot-only, butMercureBroadcastercan call it at request time when its worker-cached instance is built lazily. Align the attribute with that runtime use.BootOrTestsOnlyacceptswhenNotDefaultandwhenProvidedtogether even though they are meant to be mutually exclusive. Reject that combination in the constructor.
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="src/attributes/src/Attributes/BootOrTestsOnly.php">
<violation number="1" location="src/attributes/src/Attributes/BootOrTestsOnly.php:20">
P3: The constructor accepts `whenNotDefault` and `whenProvided` together, though the plan doc says they are mutually exclusive. Reject the combination in the constructor so an invalid attribute fails fast instead of being interpreted ambiguously by the analyzer.</violation>
</file>
<file name="src/ai/src/Contracts/Providers/RerankingProvider.php">
<violation number="1" location="src/ai/src/Contracts/Providers/RerankingProvider.php:29">
P3: The restriction on `useRerankingGateway()` has no explanation in this interface docblock. AGENTS.md asks for a concise docblock stating the runtime harm, so add the sentence from `HasRerankingGateway::useRerankingGateway()`: "Changing a shared provider's gateway affects subsequent operations."</violation>
</file>
<file name="src/broadcasting/src/Broadcasters/Broadcaster.php">
<violation number="1" location="src/broadcasting/src/Broadcasters/Broadcaster.php:100">
P2: `formatChannelsUsing()` and `authorizeChannelsUsing()` document a test-time reset by passing `null`, and the suite does this at BroadcasterTest.php:453 and :512. Plain `#[BootOnly]` marks that cleanup as boot-only. Use `#[BootOrTestsOnly]` on both methods so the marker matches the documented behavior.</violation>
</file>
<file name="src/cookie/src/Middleware/EncryptCookies.php">
<violation number="1" location="src/cookie/src/Middleware/EncryptCookies.php:202">
P2: `EncryptCookies::except()` is marked `#[BootOnly]`, but `MercureBroadcaster` calls it from its constructor on a lazily built, worker-cached broadcaster, which can run at request time. Either drop the attribute here, or document this path as a supported exception to the boot-only rule. The attribute is metadata for the worker-state analysis, so the runtime behavior is unchanged.</violation>
</file>
<file name="src/auth/src/GuardHelpers.php">
<violation number="1" location="src/auth/src/GuardHelpers.php:48">
P2: Cloning a guard leaves its context prefix shared with the original. The clone and the source then read and clear the same user state, which is the sharing this PR is meant to prevent. Add a `__clone()` that resets `contextKeyPrefix`, matching Container and ArrayStore. Guards with `readonly` context keys like SessionGuard will also need their keys recomputed.</violation>
</file>
<file name="src/database/src/Eloquent/Concerns/HasAttributes.php">
<violation number="1" location="src/database/src/Eloquent/Concerns/HasAttributes.php:1880">
P2: `flushCasterCache()` lost its replacement lifetime attribute. Add `#[BootOrTestsOnly]` and its import so reflection-based worker-state analysis preserves this boot/tests-only restriction.</violation>
</file>
<file name="src/auth/src/SessionGuard.php">
<violation number="1" location="src/auth/src/SessionGuard.php:710">
P2: `attempting()` lost its lifetime restriction when its docblock tag was removed. Add `#[BootOnly(whenShared: true)]` so worker-shared dispatcher registrations remain boot-only to the analyzer.</violation>
</file>
<file name="src/di/src/Aop/AspectManager.php">
<violation number="1" location="src/di/src/Aop/AspectManager.php:42">
P2: `clear()` loses its lifetime restriction when the docblock tag is removed without adding an attribute. Add `#[BootOrTestsOnly]` (and its import) so the worker-state analyzer continues to exempt its static cache reset.</violation>
</file>
Note: This PR contains a large number of files. cubic selects up to 200 of the highest-priority eligible files for this review, so some files may not have been reviewed.
View guided diff | Turn on auto-fix | Re-trigger cubic
| * The list persists in a static property for the worker lifetime and | ||
| * applies to every subsequent request. | ||
| */ | ||
| #[BootOnly] |
There was a problem hiding this comment.
P2: EncryptCookies::except() is marked #[BootOnly], but MercureBroadcaster calls it from its constructor on a lazily built, worker-cached broadcaster, which can run at request time. Either drop the attribute here, or document this path as a supported exception to the boot-only rule. The attribute is metadata for the worker-state analysis, so the runtime behavior is unchanged.
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/cookie/src/Middleware/EncryptCookies.php, line 202:
<comment>`EncryptCookies::except()` is marked `#[BootOnly]`, but `MercureBroadcaster` calls it from its constructor on a lazily built, worker-cached broadcaster, which can run at request time. Either drop the attribute here, or document this path as a supported exception to the boot-only rule. The attribute is metadata for the worker-state analysis, so the runtime behavior is unchanged.</comment>
<file context>
@@ -194,9 +196,10 @@ public function isDisabled(string $name): bool
+ * The list persists in a static property for the worker lifetime and
+ * applies to every subsequent request.
*/
+ #[BootOnly]
public static function except(array|string $cookies): void
{
</file context>
There was a problem hiding this comment.
Leaving this as is. except() changes a list that every request shares, so calling it directly while handling a request is still a mistake, and the marker should say so.
The Mercure broadcaster is a deliberate exception. When it's first built, it exempts the hub's cookie from encryption. The cookie name comes from config, so every call adds the same entry, and except() drops duplicates. That's lazy registration of a fixed value, not request state leaking into the list.
| * resolved them, so a guard created later with the same name, such as | ||
| * after AuthManager::forgetGuards(), starts without them. | ||
| */ | ||
| private ?string $contextKeyPrefix = null; |
There was a problem hiding this comment.
P2: Cloning a guard leaves its context prefix shared with the original. The clone and the source then read and clear the same user state, which is the sharing this PR is meant to prevent. Add a __clone() that resets contextKeyPrefix, matching Container and ArrayStore. Guards with readonly context keys like SessionGuard will also need their keys recomputed.
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/auth/src/GuardHelpers.php, line 48:
<comment>Cloning a guard leaves its context prefix shared with the original. The clone and the source then read and clear the same user state, which is the sharing this PR is meant to prevent. Add a `__clone()` that resets `contextKeyPrefix`, matching Container and ArrayStore. Guards with `readonly` context keys like SessionGuard will also need their keys recomputed.</comment>
<file context>
@@ -28,6 +29,24 @@ trait GuardHelpers
+ * resolved them, so a guard created later with the same name, such as
+ * after AuthManager::forgetGuards(), starts without them.
+ */
+ private ?string $contextKeyPrefix = null;
+
/**
</file context>
There was a problem hiding this comment.
Nothing in the framework clones a guard, and none of the guards define __clone(), so no supported path reaches this. Before this change, a cloned guard also shared its state with the original, through the guard name, so this isn't a regression either. Leaving it as is.
| * | ||
| * Boot or tests only. Clears the worker-wide caster instance cache shared | ||
| * by every coroutine; next attribute access rebuilds the casters. | ||
| * Clears the worker-wide caster instance cache shared by every coroutine; |
There was a problem hiding this comment.
P2: flushCasterCache() lost its replacement lifetime attribute. Add #[BootOrTestsOnly] and its import so reflection-based worker-state analysis preserves this boot/tests-only restriction.
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/database/src/Eloquent/Concerns/HasAttributes.php, line 1880:
<comment>`flushCasterCache()` lost its replacement lifetime attribute. Add `#[BootOrTestsOnly]` and its import so reflection-based worker-state analysis preserves this boot/tests-only restriction.</comment>
<file context>
@@ -1875,8 +1877,8 @@ protected function resolveCasterClass(string $key): mixed
*
- * Boot or tests only. Clears the worker-wide caster instance cache shared
- * by every coroutine; next attribute access rebuilds the casters.
+ * Clears the worker-wide caster instance cache shared by every coroutine;
+ * next attribute access rebuilds the casters.
*/
</file context>
There was a problem hiding this comment.
This is intentional. Clearing the caster cache is safe at runtime: the next attribute access rebuilds the casters from the same definitions. A marker would flag a call that does no harm, so the tag was replaced with a docblock that says what the method affects.
| * When the dispatcher is the application's worker-shared dispatcher, | ||
| * listener registrations persist and affect every subsequent request. | ||
| */ |
There was a problem hiding this comment.
P2: attempting() lost its lifetime restriction when its docblock tag was removed. Add #[BootOnly(whenShared: true)] so worker-shared dispatcher registrations remain boot-only to the analyzer.
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/auth/src/SessionGuard.php, line 710:
<comment>`attempting()` lost its lifetime restriction when its docblock tag was removed. Add `#[BootOnly(whenShared: true)]` so worker-shared dispatcher registrations remain boot-only to the analyzer.</comment>
<file context>
@@ -705,8 +707,8 @@ protected function rehashUserPasswordForDeviceLogout(#[SensitiveParameter] strin
*
- * Boot-only. Listener registrations persist on the worker-lifetime dispatcher
- * and affect every subsequent request.
+ * When the dispatcher is the application's worker-shared dispatcher,
+ * listener registrations persist and affect every subsequent request.
*/
</file context>
| * When the dispatcher is the application's worker-shared dispatcher, | |
| * listener registrations persist and affect every subsequent request. | |
| */ | |
| * When the dispatcher is the application's worker-shared dispatcher, | |
| * listener registrations persist and affect every subsequent request. | |
| */ | |
| #[BootOnly(whenShared: true)] |
There was a problem hiding this comment.
This is intentional. attempting() only forwards to the guard's event dispatcher, and the guard doesn't own that dispatcher: it's injected. Whether the listener ends up shared by the whole worker depends on which dispatcher was passed in.
The dispatcher's own listen() carries #[BootOnly(whenShared: true)], which is where that restriction belongs. The docblock here says when the registration affects every request.
| * | ||
| * Boot or tests only. Clearing this worker-wide cache makes subsequent | ||
| * calls compile their chains from the current aspect rules. | ||
| * Clearing this worker-wide cache makes subsequent calls compile their |
There was a problem hiding this comment.
P2: clear() loses its lifetime restriction when the docblock tag is removed without adding an attribute. Add #[BootOrTestsOnly] (and its import) so the worker-state analyzer continues to exempt its static cache reset.
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/di/src/Aop/AspectManager.php, line 42:
<comment>`clear()` loses its lifetime restriction when the docblock tag is removed without adding an attribute. Add `#[BootOrTestsOnly]` (and its import) so the worker-state analyzer continues to exempt its static cache reset.</comment>
<file context>
@@ -39,8 +39,8 @@ public static function set(string $class, string $method, Closure $value): void
*
- * Boot or tests only. Clearing this worker-wide cache makes subsequent
- * calls compile their chains from the current aspect rules.
+ * Clearing this worker-wide cache makes subsequent calls compile their
+ * chains from the current aspect rules.
*/
</file context>
There was a problem hiding this comment.
This is intentional, for the same reason as the caster cache. Clearing the compiled aspect chains is safe at runtime: the next call compiles them again from the unchanged aspect rules, so there's nothing to restrict.
| * @param bool $whenShared whether the restriction applies only to a worker-shared receiver | ||
| */ | ||
| public function __construct( | ||
| public ?string $whenNotDefault = null, |
There was a problem hiding this comment.
P3: The constructor accepts whenNotDefault and whenProvided together, though the plan doc says they are mutually exclusive. Reject the combination in the constructor so an invalid attribute fails fast instead of being interpreted ambiguously by the analyzer.
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/attributes/src/Attributes/BootOrTestsOnly.php, line 20:
<comment>The constructor accepts `whenNotDefault` and `whenProvided` together, though the plan doc says they are mutually exclusive. Reject the combination in the constructor so an invalid attribute fails fast instead of being interpreted ambiguously by the analyzer.</comment>
<file context>
@@ -0,0 +1,25 @@
+ * @param bool $whenShared whether the restriction applies only to a worker-shared receiver
+ */
+ public function __construct(
+ public ?string $whenNotDefault = null,
+ public ?string $whenProvided = null,
+ public bool $whenShared = false,
</file context>
There was a problem hiding this comment.
A check here would never run. PHPStan reads attribute arguments straight from the source without calling the constructor, and nothing creates these attributes at runtime.
The follow-up that adds the call-site rules also adds a rule that reports a bad marker: both conditions set, a parameter that doesn't exist or has no default, or whenShared on a static method. That's where the check can actually catch something, so the constructor stays as is.
| /** | ||
| * Set the provider's reranking gateway. | ||
| */ | ||
| #[BootOrTestsOnly(whenShared: true)] |
There was a problem hiding this comment.
P3: The restriction on useRerankingGateway() has no explanation in this interface docblock. AGENTS.md asks for a concise docblock stating the runtime harm, so add the sentence from HasRerankingGateway::useRerankingGateway(): "Changing a shared provider's gateway affects subsequent operations."
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/ai/src/Contracts/Providers/RerankingProvider.php, line 29:
<comment>The restriction on `useRerankingGateway()` has no explanation in this interface docblock. AGENTS.md asks for a concise docblock stating the runtime harm, so add the sentence from `HasRerankingGateway::useRerankingGateway()`: "Changing a shared provider's gateway affects subsequent operations."</comment>
<file context>
@@ -25,6 +26,7 @@ public function rerankingGateway(): RerankingGateway;
/**
* Set the provider's reranking gateway.
*/
+ #[BootOrTestsOnly(whenShared: true)]
public function useRerankingGateway(RerankingGateway $gateway): self;
</file context>
There was a problem hiding this comment.
This is intentional. On contracts, we add the attribute and leave the explanation to the implementation, which is where the harm happens. All of the AI provider contracts follow that pattern. Repeating the implementation's sentence on each interface wouldn't add anything the attribute doesn't already say.
[High impact] The PR appears safe to merge; no blocking issues remain from this review. SummaryAdds
Reviews (3) · Last reviewed commit: "Normalize numeric cache store names duri..." · Reviewed by Greptile |
PR Summary by QodoReplace lifetime docblock tags with method attributes
AI Description
Diagram
High-Level Assessment
Files changed (25)
|
Make the PoolManager mock partial so RedisProxy can derive its instance-owned context keys while the test still controls pool borrowing. Preserve the existing assertions for command failures, event dispatch and connection release.
Copy tag sets onto the cloned store so flushing an array-backed tagged repository invalidates the copy without rotating tag identifiers in the original. Preserve concrete tag-set classes and their state without reconstructing their constructors. Clone memoized tagged repositories through their delegate and reuse its store and tags. Clear the wrapper own memo before flushing because cloned wrappers are not registered with the memoized store. Cover independent writes, direct and fresh-tag reads after flush, and preservation of the original backing values.
Allow both boot configuration and test resets for the shared channel formatter and authorizer. Mark view extension registration when the factory is shared, retaining the explanation of its separate effect on the injected engine resolver. Update the attributes plan to describe the final view restriction and tagged-cache clone ownership, and replace the completed reporting step with the remaining PR verification work.
PHP converts numeric string configuration keys to integers, so names obtained from array_keys must be normalized before deriving string-based memo context keys. Preserve enum normalization and the existing public signatures. Exercise both ordinary string names and numeric names from configuration in the memo invalidation test. Verify that forgetting either name rebuilds the memoized repository and its underlying store.
This adds
hypervel/attributes, a small package of method attributes, and uses it to replace the "Boot-only.", "Tests only." and "Boot or tests only." docblock warnings across the framework.Hypervel workers are long-lived, so some methods are only safe to call while the application boots or in tests. Until now, those methods said so in prose at the start of a docblock paragraph. That works for someone reading the source, but it isn't reliable for tools. The worker-state analysis matched the tags with a regex, which also matched conditional prose such as "Boot-only when setting", and there was no way to say that only some calls to a method are restricted. Attributes carry the same information as exact metadata that PHPStan reads through reflection.
The attributes don't change runtime behavior. In this PR, the existing worker-state analysis uses them to skip writes in marked methods, as it did with the tags. Checking calls to marked methods is left to a follow-up that builds on this.
The attributes
BootOnly,TestsOnlyandBootOrTestsOnlylive inHypervel\Support\Attributes. Each takes optional arguments for methods where only some calls are restricted:whenNotDefault: 'parameter'restricts calls that pass that parameter a value other than its default.Relation::morphMap()uses it, so calling it without a map to read the current map is still fine at runtime.whenProvided: 'parameter'restricts any call that passes that parameter. This is for methods that treat an explicit default differently from an omitted argument, such asPassport::rememberConsentFor(), where an explicitnullclears the duration.whenShared: truerestricts calls only on the worker-shared instance, for methods that change only their own object. An HTTP client factory you create yourself can set its own global options; the application's shared factory should only be configured while booting.A parameter condition can be combined with
whenShared, in which case a call is restricted only when both apply.Unsupportedmarks a retained public method that always throws, such as a Symfony or PSR method that doesn't fit Hypervel's runtime. It takes an optional message pointing to the supported alternative:Base methods that drivers override, extension hooks, deliberate control-flow throws and methods that only reject some calls aren't marked.
The attributes are a separate package, rather than part of
hypervel/support, because PHPStan drops an attribute that has arguments when the attribute class isn't installed. Every package using one has to depend on it, and several of those packages, such ascontainer,contractsandcollections, don't depend on Support. The package requires nothing but PHP and shares theHypervel\Supportnamespace, the same wayhypervel/macroableandhypervel/conditionabledo.Converting the existing warnings
Each tagged method was checked against what it actually changes instead of being converted mechanically. Most keep their restriction, but the conversion also corrected a number of them:
whenShared, and their docblocks now say they affect the shared instance rather than every instance.purge()methods, whose documented use is recovering broken pooled resources at runtime.fake()methods and service provider registration helpers.Each docblock keeps its explanation of what goes wrong; only the tag phrase is gone.
Context state owned by the right object
Checking each marker against its real behavior turned up places where coroutine context state was keyed by a fixed name instead of by the object that owns it. Independent instances could then read, replace or clear each other's state:
CircularDependencyException. Each container now has its own lazily allocated key prefix. A cloned container gets a new prefix and starts with copies of the scoped instances already resolved in the current coroutine.AuthManager::forgetGuards(), picked up the previous guard's user and cached credentials. Guard keys now belong to the guard instance. Session keys and remember cookies are unchanged, so a replacement guard still reauthenticates from a valid session.memo()repository, andforgetDriver()evicted another manager's memo. Memo keys now belong to the manager. Cloning a cache repository also cloned itsArrayStore, which kept the original context keys, so writes through the clone changed the original. Cloned array stores now get their own keys, starting with copies of the current values and locks. A cloned tagged cache likewise keeps its tags on its cloned store, so flushing the copy no longer resets the original's tags.Only the keys change, not how values are stored, so child coroutines inherit context as before. The fixed-key constants (
Container::SCOPED_CONTEXT_PREFIXandRESOLUTION_STATE_CONTEXT_KEY,JwtGuard::GUARD_CONTEXT_KEY_PREFIX,RedisProxy::CONNECTION_CONTEXT_PREFIX) and thegetContextStateKey()helpers onSessionGuardandJwtGuardare removed.Other changes
Queuefacade'spopUsing()acceptsnullto remove a callback, matchingWorker::popUsing().Engine\Http\Stream::seek()has nativeintparameter types, matching PSR-7 v2.AGENTS.mddescribes the attributes in place of the tags.docs/todo.mdlists thehypervel/attributessplit repository and Packagist entry, which need to exist before release.Validation
Formatting, PHPStan and the analyzer test suite pass. A standalone copy of the package was installed on its own and every attribute instantiated from it, and each package that uses an attribute declares the dependency. The context-key fixes have regression tests covering independent instances, invalidation, cloning, child coroutines and reauthentication after a guard is replaced.
Note
Replace lifetime docblock tags with
BootOnly/TestsOnlymethod attributeshypervel/attributespackage withBootOnly,BootOrTestsOnly,TestsOnly, andUnsupportedmethod attributes, and replaces textual "Boot only"/"Tests only" docblock wording across nearly every framework package. Attributes carry conditions such aswhenShared,whenNotDefault, andwhenProvided, and expose the metadata through PHP reflection.hypervel/attributes ^0.4is added as a dependency everywhere, and the PHPStan coroutine rules in WorkerStateHelper.php now detect lifetime restrictions via reflected attributes instead of docblock tag matching.Container,CacheManager,PoolManager,RedisProxy, and the auth guards (GuardHelpers::contextKey,SessionGuard,TokenGuard,JwtGuard, Sanctum/Passport guards) now generate unique context-key prefixes per instance. A new guard with the same name no longer inherits the previous guard's user.Container::__cloneandArrayStore/TaggedCache/MemoizedTaggedCacheclone hooks copy scoped instances and memoized state into fresh context keys, so clones start with current state but stay independent.TagSet::withStore(), makesQueue::popUsing()acceptnullto remove the callback, and makesMemoizedTaggedCache::flush()clear its own memoized values.CacheManager::forgetDriver()now string-casts numeric configured store names;flushState-adjacent detection in PHPStan rules keys off attributes, so any out-of-tree methods relying on docblock tags for detection are no longer recognized.Macroscope summarized 8db14b5.