[PHP-DEV] [RFC] [Discussion] Optional interfaces

Hi,

I would like to propose a new syntax that let’s you implement an interface only if it exists.

class MyClass extends ?OptionalInterface {}

If the OptionalInterface exists, the class implements it. If no such interface can be found, it is ignored.

https://wiki.php.net/rfc/optional-interfaces

The need to declare compatibility with a certain interface without requiring the interface itself comes up when you’re writing code that should work with various versions of other libraries or PHP itself and when a library provides interoperability with some other library or a PHP extension, but does not require it.

Although this would mainly be used by library developers, the consumers of those libraries would also benefit as this syntax would clearly communicate the intention of optional interoperability. IDEs and static analysis tools could also have easier time with ?OptionalInterface instead of class definitions inside an if (interface_exists(…)) { … } else {…}.

I’ve not entirely sure about the naming as an “optional interface” feels like an oxymoron. I’ve also considered names like non-required interfaces, conditional interfaces, optional/conditional implementation or even soft interfaces/soft implementation. Please let me know if the naming itself bothers you.

Previous discussion: https://externals.io/message/125967

WIP implementation: https://github.com/php/php-src/pull/17288

Looking forward to your feedback and suggestions!

Juris

On Sat, Dec 28, 2024, at 2:56 PM, Juris Evertovskis wrote:

Hi,

I would like to propose a new syntax that let’s you implement an
interface only if it exists.

`class MyClass extends ?OptionalInterface {}`

If the `OptionalInterface` exists, the class implements it. If no such
interface can be found, it is ignored.

PHP: rfc:optional-interfaces

The need to declare compatibility with a certain interface without
requiring the interface itself comes up when you’re writing code that
should work with various versions of other libraries or PHP itself and
when a library provides interoperability with some other library or a
PHP extension, but does not require it.

Although this would mainly be used by library developers, the consumers
of those libraries would also benefit as this syntax would clearly
communicate the intention of optional interoperability. IDEs and static
analysis tools could also have easier time with `?OptionalInterface`
instead of class definitions inside an `if (interface_exists(…)) { … }
else {…}`.

I’ve not entirely sure about the naming as an “optional interface”
feels like an oxymoron. I’ve also considered names like non-required
interfaces, conditional interfaces, optional/conditional implementation
or even soft interfaces/soft implementation. Please let me know if the
naming itself bothers you.

Previous discussion: Optional interfaces - Externals
WIP implementation: [RFC] Optional interfaces by tontonsb · Pull Request #17288 · php/php-src · GitHub

Looking forward to your feedback and suggestions!
Juris

Going by the design/RFC, this seems reasonable to me, and I can see the use for it. My only concern would be if the implementation makes anything more complex for the engine or compiler that could cause future optimization problems, but I'm not qualified to answer that.

It may help the RFC to include some non-trivial real-world-ish examples, in addition to the A and B stuff. (You can probably steal some from the projects linked to.)

--Larry Garfield

On Sat, Dec 28, 2024, 22:58 Juris Evertovskis <juris@glaive.pro> wrote:

Hi,

I would like to propose a new syntax that let’s you implement an interface only if it exists.

class MyClass extends ?OptionalInterface {}

If the OptionalInterface exists, the class implements it. If no such interface can be found, it is ignored.

https://wiki.php.net/rfc/optional-interfaces

Hi Juris,

I think the proposal looks good but I some some points to be discussed:

From what I understand, the autoloading mechanism would be triggered for the optional interface, but it would not be an error if it would not succeed. I think we should explicitly spell this out in the RFC, to make it clear that autoloading will be attempted.

If an interface was not available at class declaration time, the function class_implements (on both class and object) will return false going forward, as if the class definition did not contain the interface at all?

Similarly, if at runtime the object would be passed to a function/method that has the parameter type of that interface, would it fail saying that the object is not an instance of that interface?
Note: autoloading is not triggered when just using an interface/class name in a parameter type or as return type.

I think completely erasing the interface sounds good, I just think it should be explicitly spelled out in the RFC, and all the implications that follows.

If a second class will implement the same optional interface, will it trigger autoloading again? I think it should.

If in the meantime, between defining two such classes, the interface would be defined (using a hack or some sort), would it cause problems? I think that given the current existing workarounds this is a valid concern.
To avoid problems, it would be wise to completely erase the interface from first class definition when it was not available, even if the second class might have it.

– Alex

On 28/12/2024 21:56, Juris Evertovskis wrote:

Hi,

I would like to propose a new syntax that let’s you implement an interface only if it exists.

`class MyClass extends ?OptionalInterface {}`

If the `OptionalInterface` exists, the class implements it. If no such interface can be found, it is ignored.

PHP: rfc:optional-interfaces

The need to declare compatibility with a certain interface without requiring the interface itself comes up when you’re writing code that should work with various versions of other libraries or PHP itself and when a library provides interoperability with some other library or a PHP extension, but does not require it.

Although this would mainly be used by library developers, the consumers of those libraries would also benefit as this syntax would clearly communicate the intention of optional interoperability. IDEs and static analysis tools could also have easier time with `?OptionalInterface` instead of class definitions inside an `if (interface_exists(…)) { … } else {…}`.

I’ve not entirely sure about the naming as an “optional interface” feels like an oxymoron. I’ve also considered names like non-required interfaces, conditional interfaces, optional/conditional implementation or even soft interfaces/soft implementation. Please let me know if the naming itself bothers you.

Previous discussion: Optional interfaces - Externals

WIP implementation: [RFC] Optional interfaces by tontonsb · Pull Request #17288 · php/php-src · GitHub

Looking forward to your feedback and suggestions!

Juris

Hi

I've left some remarks on your PR to help you further with the opcache stuff.
This should fix the tests and answer some questions.
I also left a concern regarding the caching, and in particular I'm thinking about the inheritance cache. I'll copy it here:

Suppose this scenario:
1. You do a request and an optional interface is not available, this class is cached without that interface.
2. A future request comes in, and reuses the cache, but since this may be via a different code path it's possible that the interface is now available. Yet because the class was cached, this won't implement the interface now even though it's available at this point in time. I'm not entirely sure if this is a real problem but it should be checked. I also don't know what assumptions in the engine this may break.

Kind regards
Niels

Hey,

Thanks for all the feedback!

The RFC is now expanded to explicitly address the concerns on how the optional interfaces interact with type checks, eval’d interfaces, opcache, autoloading, reflection and the Override attribute. Did Did I miss something? Please let me know if anything is still unclear or requires other improvement.

https://wiki.php.net/rfc/optional-interfaces

I’ve also polished the implementation and added tests for all the concerns mentioned above.

https://github.com/php/php-src/pull/17288

However the implementation is not final as I was not able (yet) to solve how my implementation interacts with the Stringable interface.

BR,

Juris

Hi Juris

On Sun, Feb 9, 2025 at 10:16 PM Juris Evertovskis <juris@glaive.pro> wrote:

PHP: rfc:optional-interfaces

Thanks for your efforts.

I've reviewed the patch, it seems there are still a few issues and it
would be great if we could solve them before this goes into voting.

A few thoughts on the RFC itself:

You mention `#[Override]` in the RFC and how it will work on optional
interfaces that are available at runtime (the `TestClass::method()`
example). Given the interface is implemented as optional, it is to be
expected that the interface will go away under certain configurations.
This will break, because the method is no longer overridden. Hence, it
might be best to also disallow `#[Override]` on optional interface
methods, to signal the breaking for the missing interface scenario.

A class only conforms to types that are actually implemented.

Just as a note, this also has some semantic implications. Notably
`$object instanceof OptionalInterface` will evaluate to `false`
without errors when the interface goes missing. To catch this issue
with static analysis, the project will need to be analysed without the
interface being available, or they will need to gain new rules that
catch such expressions that can lead to inconsistent results.

Also, when I read the subject "optional interfaces", what came to mind
was "an interface that may or may not be implemented", i.e. similar to
Objective-C's optional protocol methods [1]. I'm not sure if the
naming can be improved.

Overall, I'm not particularly convinced this is a big problem that
needs solving, but I'll leave that up to framework designers to
decide.

Ilija

[1] Working with Protocols

Hi all,

It’s almost awkward to bother the list with this humble RFC amidst so many exciting proposals :slight_smile:

With help from Ilija and Niels the implementation is now working and tested, so the RFC seems getting closer to the voting phase.

Ilija raised a point about optional interfaces not being a reliable “overridable” for #[Override]. I can certainly understand that concern.

At the same time I think there could be value in being able to say “although all overridables are optional, this method must override something, so at least one of them must be present”. This would be applicable when a package provides compatibility with multiple tools (or versions of a tool), but still requires at least one of them to be present. Besides I think that using #[Override] with an optional interface would be a very deliberate step so such consequences would be not only expected, but intended.

I’ve added an example illustrating this to the RFC. Does this justification make sense or does it sound like a horrible design? Would it be better suited for a secondary vote? I haven’t yet investigated how complex it would be to let Overrides ignore optional interfaces as non-existant.

Does anyone have strong feelings on this or any other concerns?

BR,

Juris

Hey,

Just a headsup that I plan to start the vote in a couple of days. Please let me know if there are concerns that might affect your stance on this proposal.

https://wiki.php.net/rfc/optional-interfaces

BR,

Juris

On Tuesday, 11 March 2025 at 16:37, Juris Evertovskis <juris@glaive.pro> wrote:

Hey,

Just a headsup that I plan to start the vote in a couple of days. Please let me know if there are concerns that might affect your stance on this proposal.

PHP: rfc:optional-interfaces

I will be voting against this.

I understand why you might want this, but this seems to be a very niche requirement and messing with type level guarantees is not something I want to see.
The RFCs implies that any invalid code will be caught at compile time, but this is not true.
If the optional dependency is never present, you can violate the interface in whatever way you want, which you might not be doing on purpose.
Moreover, this also does not help if an optional dependency changes the requirements of their interface and is present, so you would still need to do those "hacky" workarounds.
(e.g. if a dependency changes how they define the interface in a major release)

The fact an interface can become "available" at a later stage and classes defined prior to it being available having an optional dependency on it not implementing it is bound to be weird and unintuitive.

And ultimately, this does not really solve the problem of supporting optional dependencies, or using your object in ways that are compatible with another dependency.
Because it still relies on a library/framework/whatever maintainer to add, possibly infinite, optional interfaces for any new optional dependency which might want to interact with instances of your classes.

As such I would prefer something which is more similar to type classes ("add type information outside of your class declaration").
Or even explicit runtime interface implementation which could for example look like:

$obj implements OptionalDependency;
someFunctionFromOptionalDependency($obj);

which gives control to the *user* where and how class instance may interact with dependencies that are outside of your control, and that you don't even need to care of.

Best regards,
Gina P. Banyard

Hello Juris,

There is some uncertainty for me on how this approach would work with namespaces.

Let’s get this example:
https://3v4l.org/bI1Rj

I would expect to get the error message that I forgot to insert use Stringable;.
But with your idea everything is fine. I don’t like such ambiguity and I worry it may bring some confusion to developers.

Kind regards,

Jorg

On Sat, Dec 28, 2024 at 9:57 PM Juris Evertovskis <juris@glaive.pro> wrote:

Hi,

I would like to propose a new syntax that let’s you implement an interface only if it exists.

class MyClass extends ?OptionalInterface {}

If the OptionalInterface exists, the class implements it. If no such interface can be found, it is ignored.

https://wiki.php.net/rfc/optional-interfaces

The need to declare compatibility with a certain interface without requiring the interface itself comes up when you’re writing code that should work with various versions of other libraries or PHP itself and when a library provides interoperability with some other library or a PHP extension, but does not require it.

Although this would mainly be used by library developers, the consumers of those libraries would also benefit as this syntax would clearly communicate the intention of optional interoperability. IDEs and static analysis tools could also have easier time with ?OptionalInterface instead of class definitions inside an if (interface_exists(…)) { … } else {…}.

I’ve not entirely sure about the naming as an “optional interface” feels like an oxymoron. I’ve also considered names like non-required interfaces, conditional interfaces, optional/conditional implementation or even soft interfaces/soft implementation. Please let me know if the naming itself bothers you.

Previous discussion: https://externals.io/message/125967

WIP implementation: https://github.com/php/php-src/pull/17288

Looking forward to your feedback and suggestions!

Juris

Hey Juris.

I have a question about this feature.

At the example blow

interface A
{
public function x(Foo $foo);
}

interface B
{
public function x(Bar $foo);
}

class Test implements ?A, ?B
{
}

what would happen if both interfaces existed?

пт, 14 мар. 2025 г. в 17:08, Jorg Sowa <jorg.sowa@gmail.com>:

Hello Juris,

There is some uncertainty for me on how this approach would work with namespaces.

Let’s get this example:
https://3v4l.org/bI1Rj

I would expect to get the error message that I forgot to insert use Stringable;.
But with your idea everything is fine. I don’t like such ambiguity and I worry it may bring some confusion to developers.

Kind regards,

Jorg

On Sat, Dec 28, 2024 at 9:57 PM Juris Evertovskis <juris@glaive.pro> wrote:

Hi,

I would like to propose a new syntax that let’s you implement an interface only if it exists.

class MyClass extends ?OptionalInterface {}

If the OptionalInterface exists, the class implements it. If no such interface can be found, it is ignored.

https://wiki.php.net/rfc/optional-interfaces

The need to declare compatibility with a certain interface without requiring the interface itself comes up when you’re writing code that should work with various versions of other libraries or PHP itself and when a library provides interoperability with some other library or a PHP extension, but does not require it.

Although this would mainly be used by library developers, the consumers of those libraries would also benefit as this syntax would clearly communicate the intention of optional interoperability. IDEs and static analysis tools could also have easier time with ?OptionalInterface instead of class definitions inside an if (interface_exists(…)) { … } else {…}.

I’ve not entirely sure about the naming as an “optional interface” feels like an oxymoron. I’ve also considered names like non-required interfaces, conditional interfaces, optional/conditional implementation or even soft interfaces/soft implementation. Please let me know if the naming itself bothers you.

Previous discussion: https://externals.io/message/125967

WIP implementation: https://github.com/php/php-src/pull/17288

Looking forward to your feedback and suggestions!

Juris

Hey Gina,

I’d assume you’d require-dev all your dependencies and see the issues at compile time. That’s what version requirements are for in composer. Similarly, if a major release is released, you just implement the methods defined on the interface for all major versions you support. This does not need the hacky workarounds in any way. This is not more or less intuitive than the current workarounds you’ll have to do with “if (class_exists(‘interfacename’))”.

···

On 12.3.2025 06:16:15, Gina P. Banyard wrote:

On Tuesday, 11 March 2025 at 16:37, Juris Evertovskis juris@glaive.pro wrote:

Hey,

Just a headsup that I plan to start the vote in a couple of days. Please let me know if there are concerns that might affect your stance on this proposal.

https://wiki.php.net/rfc/optional-interfaces

I will be voting against this.

I understand why you might want this, but this seems to be a very niche requirement and messing with type level guarantees is not something I want to see.
The RFCs implies that any invalid code will be caught at compile time, but this is not true.

If the optional dependency is never present, you can violate the interface in whatever way you want, which you might not be doing on purpose.
Moreover, this also does not help if an optional dependency changes the requirements of their interface and is present, so you would still need to do those “hacky” workarounds.
(e.g. if a dependency changes how they define the interface in a major release)

The fact an interface can become “available” at a later stage and classes defined prior to it being available having an optional dependency on it not implementing it is bound to be weird and unintuitive.

And ultimately, this does not really solve the problem of supporting optional dependencies, or using your object in ways that are compatible with another dependency.
Because it still relies on a library/framework/whatever maintainer to add, possibly infinite, optional interfaces for any new optional dependency which might want to interact with instances of your classes.

It does not and it does not need to. That’s a wholly different feature (e.g. “when class X is declared, auto-attach trait Y and implement Z”) or similar.

But that doesn’t preclude me from wanting to support various external libraries out-of-the box without manual interaction of the users. Which is what this RFC solves.

As such I would prefer something which is more similar to type classes (“add type information outside of your class declaration”).
Or even explicit runtime interface implementation which could for example look like:

$obj implements OptionalDependency;
someFunctionFromOptionalDependency($obj);

which gives control to the user where and how class instance may interact with dependencies that are outside of your control, and that you don’t even need to care of.

Best regards,

Gina P. Banyard

Bob

On 2025-03-14 16:04, Jorg Sowa wrote:

Hello Juris,
There is some uncertainty for me on how this approach would work with namespaces.

Let’s get this example:
https://3v4l.org/bI1Rj

I would expect to get the error message that I forgot to insert use Stringable;.
But with your idea everything is fine. I don’t like such ambiguity and I worry it may bring some confusion to developers.

Kind regards,
Jorg

Hi Jorg,

You are correct. We can’t really distinguish whether an interface is absent because the dependency is absent or because the name was mistyped.

In practice I would not expect this to be a big issue. Adding a ?OptionalInterface would be a deliberate action when your objects needs to pass a specific type check. In case of a typo the type check will fail and the object will be rejected by the consumer.

I admit that it’s not as nice as a compile-time error, but my point is that the typo would still be easy to notice as it would break the whole purpose of that interface implementation.

BR,
Juris

On 2025-03-14 16:24, Viktor Khramov wrote:

Hey Juris.

I have a question about this feature.

At the example blow

interface A
{
public function x(Foo $foo);
}

interface B
{
public function x(Bar $foo);
}

class Test implements ?A, ?B
{
}

what would happen if both interfaces existed?

Hi Viktor,

If A exists then ?A is equivalent to A.

In your example Test will implement both A and B.

If Test::x() will be incompatible with either A::x() or B::x(), you will end up with an error.

BR,
Juris