# RFC proposal: Invokable interface

- Thread: https://externals.io/message/130483
- Original archive: https://news-web.php.net/php.internals/130483
- Started by Osama Aldemeery on 2026-03-29T21:42:48+00:00
- Messages: 14
- Reply by email to internals@lists.php.net
- More for agents: search with https://externals.io/search.md?q={query} · RFCs at https://externals.io/rfc.md · JSON API at https://externals.io/api · documentation at https://externals.io/llms.txt

---

## #130483 — Osama Aldemeery — 2026-03-29T21:42:48+00:00

URL: https://externals.io/message/130483#130483

Hi all,

I'd like to propose an Invokable interface, the Stringable equivalent for
__invoke().

The idea has come up a few times over the years (most recently in the PR
#15492 discussion, where Gina suggested this exact approach) but never had
a concrete implementation.

I've put one together: https://github.com/php/php-src/pull/21574

It follows the Stringable pattern: auto-implemented for any class defining
__invoke(), explicitly implementable with enforcement, and covariant to
callable in return type checks.

I'm working on a formal RFC and would love feedback before posting it.

I'd also like to request wiki karma to create the RFC page — my wiki
username is *aldemeery*.

Thanks,

Osama Aldemeery

---

## #130484 — Anton Smirnov — 2026-03-29T22:51:16+00:00

URL: https://externals.io/message/130483#130484 · In reply to: #130483

Hi!


> It follows the Stringable pattern: auto-implemented for any class 
> defining __invoke(), explicitly implementable with enforcement, and 
> covariant to callable in return type checks.

Stringable is just a normal interface that can be used directly as any 
other regular interface, it's just also applied magically.

Invokable cannot be a regular interface because the signature for 
__invoke is not fixed, so it's a "weird" interface with magic behavior 
like Throwable and Iterable. What is common about these weird interfaces 
is that they can't be directly implemented by user. I don't feel like 
breaking this rule is a good idea for something with no clear benefits.

Second thing is that magic methods are not supposed to be used directly, 
so having __invoke is just an implementation detail, the consumer code 
should not check for that, that's a code smell.

Anton

---

## #130485 — Osama Aldemeery — 2026-03-30T00:32:24+00:00

URL: https://externals.io/message/130483#130485 · In reply to: #130484

Anton,

Thank you for taking the time to respond.
This is really helpful feedback, and I genuinely think the distinction
you're drawing is an important one to get right.

I think the parallel with Stringable holds more closely than it might seem.
If we put them side by side:

   - Both are automatically implemented by the engine for classes that
   define the corresponding magic method.
   - Both can be explicitly implemented by users.
   - Both enforce the presence of a magic method. Stringable enforces
   __toString(), Invokable enforces __invoke().
   - The argument that magic methods are just implementation details holds
   true for both.

The only difference is that __toString() has a fixed signature, so
Stringable can enforce it through a normal interface declaration.
__invoke() doesn't
have a fixed signature, so Invokable uses an enforcement handler instead.
Different mechanism, same contract. The difference is caused by the
variable signature, not by any fundamental difference in what the interface
represents.

The comparison to Throwable is interesting, but as far as I understand (and
please correct me if I am wrong), Throwable restricts direct user
implementation by design to keep the exception hierarchy clean and
predictable.

That said, I could be wrong about any of this, and in which case I would
genuinely appreciate you correcting my reasoning.

Regards,

Osama Aldemeery



> Hi!
>
> 
> > […]
>
> Stringable is just a normal interface that can be used directly as any
> other regular interface, it's just also applied magically.
>
> Invokable cannot be a regular interface because the signature for
> __invoke is not fixed, so it's a "weird" interface with magic behavior
> like Throwable and Iterable. What is common about these weird interfaces
> is that they can't be directly implemented by user. I don't feel like
> breaking this rule is a good idea for something with no clear benefits.
>
> Second thing is that magic methods are not supposed to be used directly,
> so having __invoke is just an implementation detail, the consumer code
> should not check for that, that's a code smell.
>
> Anton

---

## #130486 — Rowan Tommins [IMSoP] — 2026-03-30T07:37:19+00:00

URL: https://externals.io/message/130483#130486 · In reply to: #130485

>The only difference is that __toString() has a fixed signature, so
>Stringable can enforce it through a normal interface declaration.
>__invoke() doesn't
>have a fixed signature, so Invokable uses an enforcement handler instead.
>Different mechanism, same contract. The difference is caused by the
>variable signature, not by any fundamental difference in what the interface
>represents.

I think that is a very fundamental difference.

Given $foo instanceof Stringable, the user knows they can write (string)$foo and the object will do something - that's already a pretty weak contract, in my eyes, but it is a contract.

Given $foo instanceof Invokable, the user knows even less. They know they can invoke the object somehow, but there's a fair chance that $foo() will fail because of mandatory parameters.

Can you give an example where this very loose contract would be useful?

Regards,

Rowan Tommins
[IMSoP]

---

## #130487 — Rob Landers — 2026-03-30T07:58:03+00:00

URL: https://externals.io/message/130483#130487 · In reply to: #130486

> 
> 
> > […]
> 
> I think that is a very fundamental difference.
> 
> Given $foo instanceof Stringable, the user knows they can write (string)$foo and the object will do something - that's already a pretty weak contract, in my eyes, but it is a contract.
> 
> Given $foo instanceof Invokable, the user knows even less. They know they can invoke the object somehow, but there's a fair chance that $foo() will fail because of mandatory parameters.
> 
> Can you give an example where this very loose contract would be useful?
> 
> Regards,
> 
> Rowan Tommins
> [IMSoP]

I'd prefer if the interface forced an empty argument set. You can add arguments so long as they're optional. But generally speaking, there is no need for arguments on an invokable class -- that is what constructors and properties are for.

— Rob

---

## #130488 — Rowan Tommins [IMSoP] — 2026-03-30T08:37:49+00:00

URL: https://externals.io/message/130483#130488 · In reply to: #130487

>I'd prefer if the interface forced an empty argument set. You can add arguments so long as they're optional. But generally speaking, there is no need for arguments on an invokable class -- that is what constructors and properties are for.

An invokable class can be used for all the same things as any other callable - I've seen them used for event handlers, middleware, comparison for sorting, and so on.

A type check for "callable with no arguments and mixed/unspecified return" would be a simple special case of the often-discussed "callable with specified signature"; but I don't think that's what's proposed here.

Regards,

Rowan Tommins
[IMSoP]

---

## #130489 — Hans Krentel — 2026-03-30T09:06:49+00:00

URL: https://externals.io/message/130483#130489 · In reply to: #130485

> 
 > That said, I could be wrong about any of this, and in which case I would
 > genuinely appreciate you correcting my reasoning.

Before that you wrote earlier that this has come up multiple times before. 
As far as I understand the idea, has it been explored with an interface 
ordinarily declared in PHP scripts, and if so, what were the outcomes?

And thinking, stringable before PHP's exclusive internal use came out of 
PHP scripts, namely framework scripts from Symfony land IIRC. If you draw 
the relation to that interface, was there a recent review if/how it went? 
Would be interesting as we now should have more data.

What I can say for sure is that stringable was an annoyance after it went 
internal as now everything with a __toString() method had to be declared 
anonymous or with it's own interface exported upfront. No big deal if you 
find an internal class you can extend from and re-use it, but was not 
always pretty and it's not possible to extend from Closure and apart from 
that I'm not creative enough right know to remember another one with 
__invoke(). (might be a non-issue with anonymous classes, so merely food 
for thought, not reasoning against or for it here)

So how sharp is the axe before we start with the woodwork? (from your point 
of view)

-- hakre

---

## #130491 — Hans Krentel — 2026-03-30T09:51:22+00:00

URL: https://externals.io/message/130483#130491 · In reply to: #130488

> 
> > […]
arguments so long as they're optional. But generally speaking, there is no 
need for arguments on an invokable class -- that is what constructors and 
properties are for.
 > 
 > An invokable class can be used for all the same things as any other 
callable - I've seen them used for event handlers, middleware, comparison 
for sorting, and so on.

When you spotted those, were they also with such a generic invocable 
interface as in this idea in their interface hierarchy?

---

## #130492 — Rowan Tommins [IMSoP] — 2026-03-30T11:21:09+00:00

URL: https://externals.io/message/130483#130492 · In reply to: #130491

>
>When you spotted those, were they also with such a generic invocable interface as in this idea in their interface hierarchy?

If they had any interfaces at all, I would expect them to be specific to the use case. As I said earlier, I can't see how you would use an interface that asserted just the existence of the method without any of its signature.

Regards,

Rowan Tommins
[IMSoP]

---

## #130499 — Larry Garfield — 2026-03-30T18:37:28+00:00

URL: https://externals.io/message/130483#130499 · In reply to: #130492

> 
> > […]
>
> If they had any interfaces at all, I would expect them to be specific 
> to the use case. As I said earlier, I can't see how you would use an 
> interface that asserted just the existence of the method without any of 
> its signature.
>
> Regards,
>
> Rowan Tommins
> [IMSoP]

This has sort of come up in designing the compose operator that I've been working on, on-and-off.  I'd want to allow Closure + Closure to return a closure, but also allow Invokable + Closure, Closure + Invokable, and Invokable + Invokable to return a closure.  But right now there is no obvious way (to me at least) to detect an Invokable object and treat it accordingly.  So that's where it might be useful to me, at least.

However, that's an engine level check, not one that would make sense in user-space.  In user space, I'm not entirely sure where you would want this and not *also* want a use-case-specific interface.

I've done quite a bit of dynamic dispatch work lately, and `is_callable()` and method_exists('__invoke') have sufficed for what I need.

As an older example:

https://github.com/php-fig/event-dispatcher-util/blob/master/src/ParameterDeriverTrait.php

I'm sure someone will argue it could be simplified with more modern assumptions, but for the sake of this thread, how would an Invokable interface make that code cleaner?

--Larry Garfield

---

## #130516 — Gina P. Banyard — 2026-03-31T12:53:07+00:00

URL: https://externals.io/message/130483#130516 · In reply to: #130483

> Hi all,
>
> I'd like to propose an Invokable interface, the Stringable equivalent for __invoke().
>
> The idea has come up a few times over the years (most recently in the PR #15492 discussion, where Gina suggested this exact approach) but never had a concrete implementation.
>
> I've put one together: https://github.com/php/php-src/pull/21574
>
> It follows the Stringable pattern: auto-implemented for any class defining __invoke(), explicitly implementable with enforcement, and covariant to callable in return type checks.
>
> I'm working on a formal RFC and would love feedback before posting it.
> I'd also like to request wiki karma to create the RFC page — my wiki username is aldemeery

I already replied on the PR, but I am very much *not* in favour of this proposal.
My comment in PR #15492 [1] specifically says that we don't have such an interface, and I don't want to add duck typing as this goes against PHP's nominal typing system.
The discussion around such an interface happened in PR #18161 [2].
However, Tim noted that we cannot have an interface the defines the signature of __invoke().
And then Ilija suggested a marker interface similar to Throwabale.

In the year since this discussion has happened I've had time to think and the reason I never even attempted to move this forward is that this is, IMHO, repeating the same mistake of `Stringable`.

The only reason for `Stringable` to exists is that `strict_types` exists, and when enabled prevents objects with a `__toString()` method to be passed to `string` types.
This causes nonsensical design choices of "should I mark my parameters as `string|Stringable` or not" when it should just always be `string` and let the engine do the type juggling.

The proposal of adding an `Invokable` interfaces reproduces this exact same mistake.
One shouldn't care that the callable is an object with an invoke method? Why could it not be a callable array or be a callable string?
As the consumer of such an argument the representation of a callable shouldn't matter.

The main argument seems to be that the callable type cannot be used on property types.
There are 2 reasons why this is the case.
The one most people know is that string/array callables is because there are scope visibility implications.
(See https://3v4l.org/hCpiG for an example.)
However, the **primary** reason is because of partially supported callables that have been deprecated in PHP 8.2. [3]
Those partially supported callables don't just depend on *where* they are created but also where they are *used*.

When those partially supported callables are removed in PHP 9, it seems very feasible to allow `callable` to be used as a property type.
The mechanism to do so would be to *effectively* convert any "legacy" (array, string, object with __invoke methods) callables into a `Closure` object during the type check.
This would remove any scope visibility issues from callables created within methods.
(As an aside I firmly believe this behaviour would reduce the engine complexity around callables)

Moreover, I don't believe that a magic interface that does effectively nothing is good language design,
and that this is trying to fix a "problem" in a way that is going to cause more problems down the line rather than wait for the proper solution of fixing the `callable` type.
Similarly to how I feel about the introduction of `Stringable`, where the proper solution IMHO is to unify PHP's typing modes. [4]

Finally, with all the syntactic improvement in PHP, creating a Closure object to go deal with the current limitations of the callable type feels like a non-issue.

Best regards,
Gina P. Banyard
[1] https://github.com/php/php-src/pull/15492
[2] https://github.com/php/php-src/issues/18161
[3] https://wiki.php.net/rfc/deprecate_partially_supported_callables
[4] https://github.com/Girgias/unify-typing-modes-rfc

---

## #130531 — Matthew Weier O'Phinney — 2026-04-02T14:23:03+00:00

URL: https://externals.io/message/130483#130531 · In reply to: #130483

> Hi all,
>
> I'd like to propose an Invokable interface, the Stringable equivalent for
> __invoke().
>
> The idea has come up a few times over the years (most recently in the PR
> #15492 discussion, where Gina suggested this exact approach) but never had
> a concrete implementation.
>
> I've put one together: https://github.com/php/php-src/pull/21574
>
> It follows the Stringable pattern: auto-implemented for any class defining
> __invoke(), explicitly implementable with enforcement, and covariant to
> callable in return type checks.
>
>
I honestly don't feel there's a need for this, as we already have the
callable type. It shouldn't matter if the callable is an object defining
`__invoke()`, a function, a closure, or a first class callable. If you need
to restrict to an object that's invokable, you can do that already via
(object&callable). Further, PHPStan, Psalm, PHPCS, and php-cs-fixer all
understand annotations around the callable type already to further define
the allowed signature (e.g., number and types of arguments, return value).
This is a solved problem already.

Further, creating an interface for this is hugely difficult, if not
impossible, due to the return value associated with the interface, as
implementations will inevitably conflict with the Liskov Substitution
Principle. If we say "void", you cannot declare an implementation with a
return value. If you define mixed, you cannot have a void or never return.

-- 
Matthew Weier O'Phinney
mweierophinney@gmail.com
https://mwop.net/
he/him

---

## #130563 — Hans Krentel — 2026-04-05T02:57:49+00:00

URL: https://externals.io/message/130483#130563 · In reply to: #130531

> If you define mixed, you cannot have a void or never return.

We can't speak for Liskov (only she can), but as far as stable PHP is 
concerned never is actually fine, and should also stand a common 
interpretation of LSP, as never is the bottom type and henceforth a 
specialization of mixed. (Some may go for void, too, but that is far more 
abstract and does not work conceptually in PHP if I'm not mistaken.)

Furthermore, the PHP runtime also guarantees that any function can never 
return, not only by the never return type.

So it's at least irrelevant to implement a new throwable via an invocable 
mixed interface or never, the invocation in the runtime guarantees the 
higher contract to the return:

     <?php
     
     interface invocable {
         public function __invoke(): mixed;
     }
     
     $o = new class implements invocable {
         public function __invoke(): never {}
     };
     
     $o();

Interface violation normally yields a compile error, not a type error, 
nowadays.

So if you think that is an error, the discussion should have brought 
something to the table.

-- hakre

---

## #130847 — Osama Aldemeery — 2026-05-12T06:13:09+00:00

URL: https://externals.io/message/130483#130847 · In reply to: #130563

All,
Apologies for the long silence. I wanted time to sit with the responses
before replying, and that turned into longer than I intended.

Thanks for the depth of the replies. The points raised here, and in the
discussion on the PR, have shown me things I hadn't seen.
I still think there is a real gap between Closure and callable, but I am no
longer sure a marker interface is the right shape for filling it.

Given that, it doesn't feel right to keep this thread open and ask for more
of the list's time on something I'm no longer confident in.
I'll step back from the proposal, and I've closed the PR.

Thank you all.

Regards,
Osama Aldemeery



> 
>
> > […]
>
> We can't speak for Liskov (only she can), but as far as stable PHP is
> concerned never is actually fine, and should also stand a common
> interpretation of LSP, as never is the bottom type and henceforth a
> specialization of mixed. (Some may go for void, too, but that is far more
> abstract and does not work conceptually in PHP if I'm not mistaken.)
>
> Furthermore, the PHP runtime also guarantees that any function can never
> return, not only by the never return type.
>
> So it's at least irrelevant to implement a new throwable via an invocable
> mixed interface or never, the invocation in the runtime guarantees the
> higher contract to the return:
>
>      <?php
>
>      interface invocable {
>          public function __invoke(): mixed;
>      }
>
>      $o = new class implements invocable {
>          public function __invoke(): never {}
>      };
>
>      $o();
>
> Interface violation normally yields a compile error, not a type error,
> nowadays.
>
> So if you think that is an error, the discussion should have brought
> something to the table.
>
> -- hakre
