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
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 anif (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/17288Looking 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
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
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 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 anif (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 https://externals.io/message/125967
WIP implementation: https://github.com/php/php-src/pull/17288 https://github.com/php/php-src/pull/17288
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:
- You do a request and an optional interface is not available, this class is cached without that interface.
- 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
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
Hi all,
It's almost awkward to bother the list with this humble RFC amidst so many
exciting proposals :)
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
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.
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
Hey Gina,
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.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.
I'd assume you'd require-dev all your dependencies and see the issues at
compile time.
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)
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.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.
This is not more or less intuitive than the current workarounds you'll
have to do with "if (class_exists('interfacename'))".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
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,
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 anif (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/bI1RjI 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,
JorgOn 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 anif (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?
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
Hello Juris, There is some uncertainty for me on how this approach
would work with namespaces.Let's get this example:
https://3v4l.org/bI1RjI 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