fix(plugin): replace PluginProxy with generated interceptor classes#15
Merged
fix(plugin): replace PluginProxy with generated interceptor classes#15
Conversation
Fixes TypeError when a plugin targets an interface and the proxy is injected into a constructor that type-hints that interface. Also fixes plugins being silently skipped when an interface is resolved via preferences rather than closure bindings. Key changes: - Add PluginInterceptedInterface marker interface with getPluginTarget() - Add PluginInterception trait with full before→target→after chain logic - Add InterceptorClassGenerator for eval-based class generation (no files on disk): generates interface wrappers (implements target interface) or concrete subclasses (extends target class, only for non-readonly targets) - Rewrite PluginInterceptor::createProxy() with dual strategy, now takes originalId and resolvedId to find plugins registered against interfaces even when resolved via preferences - Fix Container::resolve() to pass originalId to createProxy at both call sites (closure bindings and auto-wired classes) - Add PluginRegistry::getEffectiveTargetClass() for interface-aware lookup - Add PluginException::cannotInterceptReadonly() and ambiguousInterfacePlugins() - Remove PluginProxy — replaced by generated interceptors - Update Router.php to use instanceof PluginInterceptedInterface - Remove untracked demo/ directory; remove demo/ paths from php-cs-fixer config Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Document the recommended approach of targeting interfaces instead of concrete classes, explain how the interceptor generation works at a high level, and document the two new error cases (readonly targets, ambiguous multi-interface plugins). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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.
Summary
HasherInterface) and the proxy is injected into a constructor type-hinting that interface — the core bug reported by a userPreferenceRegistryinstead of closure bindings — plugins registered against the interface were never foundPluginProxy(generic__call()wrapper that didn't satisfy PHP's type system) with eval-generated interceptor classes that properly implement target interfaces or extend target concrete classesdemo/directory and its references from linter configArchitecture
Dual-strategy interceptor generation (no files on disk):
implementsthe interface, delegates to wrapped instanceextendsthe class, overrides plugged methodsPluginException::cannotInterceptReadonly()with helpful suggestionPluginException::ambiguousInterfacePlugins()New files:
PluginInterceptedInterface— marker interface withgetPluginTarget()(replacesinstanceof PluginProxychecks)PluginInterceptiontrait — core before→target→after chain logic (deliberate exception to "no traits" rule; documented rationale)InterceptorClassGenerator— generates interceptor classes viaeval(), cached per targetKey changes:
PluginInterceptor::createProxy(originalId, resolvedId, target)— new signature passes both the requested interface and resolved concrete classContainer::resolve()— passes$originalIdat bothcreateProxycall sitesPluginRegistry::getEffectiveTargetClass()— interface-aware plugin lookupRouter.php— updated to useinstanceof PluginInterceptedInterfaceTest plan
PluginInterceptiontrait (16 tests),InterceptorClassGenerator(20 tests),PluginInterceptor(23 tests),PluginRegistryadditions (8 tests),PluginExceptionnew methods (6 tests)PluginProxyhas been fully removed from the codebasemarkotalk-localwith the exact bug scenario from the report — controller type-hintingHasherInterfacewith a#[Plugin(target: HasherInterface::class)]before plugin: no TypeError, plugin fires correctly🤖 Generated with Claude Code