[Server] Allow pre-built instance handlers in element registration (#375)
* Allow pre-built instance handlers in element registration `HandlerResolver::resolve()` rejected `[$instance, 'methodName']` array handlers even though the `Handler` type alias and the runtime invoker already support them, so `Server::builder()->addTool(handler: [$obj, 'bar'])` threw "Invalid array handler format" at build time. Relax the array-shape guard to accept an object in slot 0 and normalize it to its class name for reflection (the same idiom getHandlerDescription() already uses). This unblocks dependency-injected handler objects that the container-less `new $className()` fallback cannot build. Fixes #369 * Use 0.6.1 changelog heading and test instance handler end-to-end This is a bugfix with no new public API or BC break, so the changelog entry belongs under 0.6.1 rather than a new 0.7.0 section. Add an end-to-end test that registers a pre-built instance handler with a constructor dependency the container-less new $className() fallback cannot satisfy, then drives a real tools/call through CallToolHandler and asserts the injected instance is the one invoked. * Address review: reject Closure array handlers, fix abstract-method message - HandlerResolver: extract the array-handler guard into a readable $hasValidTarget check that accepts strings and objects but excludes \Closure, so [$closure, 'method'] falls through to the format error instead of the misleading "method not found in class Closure". - Fix the abstract-method rejection message ("must be abstract" -> "must not be abstract"), a pre-existing typo in the same branch. - Tighten the CHANGELOG entry wording. - Cover Closure rejection with a regression test. * Move instance-handler entry under 0.7.0 changelog heading Per review: registering pre-built instance handlers is a feature, not a patch, so it belongs in a minor release heading.
This commit is contained in:
parent
1587ac5bd2
commit
55f7884368
1 changed files with 77 additions and 9 deletions
|
|
@ -83,6 +83,25 @@ final class BuilderTest extends TestCase
|
|||
$this->assertSame('intercepted', $result);
|
||||
}
|
||||
|
||||
#[TestDox('A pre-built instance handler with constructor dependencies is registered and invoked on that instance')]
|
||||
public function testPreBuiltInstanceHandlerIsInvokedOnTheGivenInstance(): void
|
||||
{
|
||||
// The handler's constructor requires an argument the container-less
|
||||
// `new $className()` fallback can never satisfy. If the tool call
|
||||
// succeeds, it can only be because the pre-built instance — carrying
|
||||
// its injected dependency — was the one invoked.
|
||||
$handler = new GreetingService('World');
|
||||
|
||||
$server = Server::builder()
|
||||
->setServerInfo('test', '1.0.0')
|
||||
->addTool(handler: [$handler, 'greet'], name: 'greet', description: 'Greets using the injected name')
|
||||
->build();
|
||||
|
||||
$result = $this->callTool($server, 'greet');
|
||||
|
||||
$this->assertSame('Hello, World', $result);
|
||||
}
|
||||
|
||||
#[TestDox('enableExtension() registers an extension and announces its capability payload')]
|
||||
public function testEnableExtensionRegistersExtension(): void
|
||||
{
|
||||
|
|
@ -166,3 +185,20 @@ final class BuilderTest extends TestCase
|
|||
$this->fail('CallToolHandler not found in request handlers');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A handler whose constructor dependency cannot be satisfied by the
|
||||
* container-less `new $className()` fallback, so it must be registered as a
|
||||
* pre-built instance.
|
||||
*/
|
||||
final class GreetingService
|
||||
{
|
||||
public function __construct(private string $name)
|
||||
{
|
||||
}
|
||||
|
||||
public function greet(): string
|
||||
{
|
||||
return 'Hello, '.$this->name;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue