Repository navigation
Replace lifetime docblock tags with method attributes - #104
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
|
PR Summary by QodoReplace lifecycle docblock tags with method attributes
AI Description
Diagram
High-Level Assessment
Files changed (543)
|
Code Review by Qodo
1.
|
[High impact] The latest cleanup fix looks safe to merge; the earlier cache-clone memory growth remains non-blocking.
|
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.
| CoroutineContext::set($this->storageContextKey, $items); | ||
| CoroutineContext::set($this->locksContextKey, $records); |
There was a problem hiding this comment.
Discarded cache clones retain memory
ArrayStore::__clone() leaves two new context entries behind after every clone is discarded. Outside a coroutine, these entries live in the static nonCoroutineContext array, and the store has no cleanup path for them.
A long-running CLI loop that clones an array-backed repository now keeps growing this array. Modified copies also retain their cached values. Give discarded clones a way to release their entries without clearing unrelated context.
Knowledge Base Used: Cache, filesystem, and Redis storage
There was a problem hiding this comment.
Not changing this. Every array store already gets its own context entries when it's created, and the clone follows the same model, so it doesn't introduce a new kind of retention.
We haven't found a realistic flow where these entries build up. Console commands run in their own coroutine by default, so their context ends with the command. The framework never clones a cache repository, and the test harness clears context after every test. A destructor-based cleanup also couldn't reliably know which coroutine's context holds the entries, so it would add machinery without solving a real problem.
There was a problem hiding this comment.
That’s fair. __clone() does leave new entries in nonCoroutineContext when cloning happens outside a coroutine, but given that the framework doesn’t clone cache repositories in normal operation, commands and tests have context cleanup boundaries, and a destructor cannot reliably identify the owning coroutine context, this isn’t an actionable leak in the flows described. I’ll withdraw the concern; no change is needed.
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 lifecycle docblock tags with
BootOnly/TestsOnly/Unsupportedmethod attributeshypervel/attributespackage definingBootOnly,BootOrTestsOnly,TestsOnly, andUnsupportedattributes withwhenNotDefault,whenProvided, andwhenSharedoptions. All packages declare it as a dependency.WorkerStateHelperandWorkerStateClassCollectorto detect lifecycle markers via reflected attributes instead of doc-comment patterns.GuardHelpers,SessionGuard,RequestGuard,TokenGuard) andContainernow use per-instance context-key prefixes, so same-named guards and separate containers no longer share cached state.CacheManager,ArrayStore, and tagged-cache classes gain similar per-instance/clone isolation.Queue::popUsingnow accepts a nullable callable (null removes the callback);engine.Stream::seekdeclares integer parameter types;phpstan/src/Rules/Coroutine/WorkerStateHelper.phpandphpstan/src/Collectors/WorkerStateClassCollector.phpchange how lifetime markers are detected, so custom classes relying on docblock wording for the analysis are no longer recognized.Macroscope summarized 8db14b5.