Conversation
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Two file-shaped events (WatchedFileChangedEvent, OpenDocumentClosedEvent) and one internal AutoloadMapRegeneratedEvent replace the invalidator fan-out. The map reader listens to WatchedFileChangedEvent, and when the path is under vendor/composer, publishes AutoloadMapRegeneratedEvent carrying the fresh map. Downstream backends subscribe to the concrete events they care about rather than polling the reader for map identity changes. Subscription is exact-string match against the event's own $type, so the provider does no runtime kind inspection (RFC 1 §4.5). Subscribers that treat two file events the same way register the same callable against each concrete type; the tax is two lines instead of one, and it keeps the disallowed introspection functions off the dispatch path. InvalidatableInterface and CompositeInvalidatable are gone; handlers publish events through EventDispatcherInterface instead of calling an invalidator directly. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Made at Eric's direction: adding a layer and adding ruleset edges are both human-only edits per docs/architecture/enforcement-edits.md. - New Events layer covers src/Events/. - Handler -> Events (publish WatchedFileChangedEvent, OpenDocumentClosedEvent). - Knowledge -> Events (subscribe; the map reader also publishes AutoloadMapRegeneratedEvent, which lives in Knowledge because it carries ComposerAutoloadMap). - Handler -> Cache is removed; no Handler file references Cache after InvalidatableInterface's deletion. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #643 +/- ##
============================================
- Coverage 99.65% 99.55% -0.10%
- Complexity 1913 1922 +9
============================================
Files 134 138 +4
Lines 4940 4968 +28
============================================
+ Hits 4923 4946 +23
- Misses 17 22 +5 ☔ View full report in Codecov by Harness. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #642.
The invalidator fan-out (
InvalidatableInterface,CompositeInvalidatable) is replaced with a PSR-14 event bus. Two file-shaped events are published by the LSP handlers, and one internal event is published by the map reader when it detects avendor/composer/change:WatchedFileChangedEvent— fromworkspace/didChangeWatchedFilesOpenDocumentClosedEvent— fromtextDocument/didCloseAutoloadMapRegeneratedEvent(inKnowledge; carries the fresh map) — from the map readerSubscribers register against each concrete event type. Backends stop polling
ComposerAutoloadMapReader::current()on every invalidation to check for map identity changes; instead they receive the new map on the event and rebuild their derived indexes on the next query.Notes
src/Events/. Sufficient for one composition root; nothing depends on Symfony or Tukio.EventInterface::$type(each event sets it toself::class). Nois_a,get_class,$event::class, or$event instanceof $varon the dispatch path — those are all disallowed as kind inspection (RFC 1 §4.5). A subscriber that treats two events the same way registers the same callable against each concrete type.FileEventInterfacestill exists as a listener parameter type for narrowing; it is not a subscription target.EventDispatcheris typed on the concreteListenerProvider, notPsr\EventDispatcher\ListenerProviderInterface. The PSR interface'sgetListenersForEvent()returnsiterablewith no value type, so an interface-typed field forces every dispatch site to widen throughmixed. This is one call site; the concrete typing avoids that.AutoloadMapRegeneratedEventlives inKnowledge/, notEvents/, because it carriesComposerAutoloadMap(confined to backends bySymbolDiscoveryAuthorityExtension). Keeps the exempt-namespaces list unchanged.AutoloadMapRegeneratedEventfrom inside itsWatchedFileChangedEventhandler; downstream listeners run in the same tick before control returns.KnowledgeStack::assemble()into a container factory closure (or a bootstrap seam) with no shape changes.Enforcement edits, made at Eric's direction
deptrac.yaml: newEventslayer coveringsrc/Events/; edgesHandler -> EventsandKnowledge -> Events;Handler -> Cacheedge removed (noHandlerfile referencesCacheafter this change).Test plan
composer test(phpstan, deptrac, layer-coverage, phpunit, phpcs) greenCachingSymbolSourceTest,ComposerAutoloadMapReaderTest,ComposerMapBackendTest,AutoloadFilesBackendTest,DidChangeWatchedFilesHandlerTest,TextDocumentSyncHandlerTest, plus newListenerProviderTestandEventDispatcherTestExternalFileChangeInvalidationTest,ComposerRegenerationInvalidationTest🤖 Generated with Claude Code