[PHP-DEV] Usable classes?

Hello,

Sorry, but I’ve got another idea. I already validated it with ChatGPT who labeled it spicy. After clarifying some misunderstandings, they updated the label to “heretical”. I hope you’ll find it exciting as well.

As you’re all aware, classes in PHP are literally unusable and impossible to implement. Id est

class A {}

class B implements A {} // this is not allowed in PHP

class C {

use A; // this is also not allowed in PHP

}

However, under the hood both interfaces and traits are classes, just with different vibes. I mean flags. The limitation feels almost artificial:

https://github.com/php/php-src/blob/334d9bbc09e37d6f66dde23988df7d7299dc6f19/Zend/zend_inheritance.c#L2248-L2252

https://github.com/php/php-src/blob/334d9bbc09e37d6f66dde23988df7d7299dc6f19/Zend/zend_inheritance.c#L3554-L3558

I suspect it would be quite possible to drop these limitations, so the main question seems to be — are these limitations useful? Or would it be usefuler to have classes without such limits? Let me elaborate:

Part A. Implementable classes.

Idea: allow using normal classes in the interface list.

class A {}

class B implements A {}

The result would be that B gets checked whether it satisfies the public interface of A. From there on B gets treated as a subclass of A: (new B) instanceof A is true and instances of B satisfy an A typehint.

These days many projects, libraries and frameworks add a bunch of interfaces that each correspond to a single class (and mirror it’s public signature) that might be replaced with a different implementation. Some libraries define a hundred interfaces while the consumer projects use at most a few of those replacement opportunities.

These would make such just-in-case interface definitions redundant as the class itself could be used as the interface. In otter words: why make an interface if you’ve already defined the interface in that class right there?

Part B. Usable classes

Idea: allow using normal classes in the trait list.

class A {}

class B {

use A;

}

The result would be that B gets the guts of A, just like if A was trait. This would simplify reuse and composition and reduce the need for workarounds (aka design patterns).

Together they would simplify making libs that add features to your models or something like that. E.g. you can do this without splitting them into a trait and an interface and duplicating the signature.

class YourModel implements SomeFeature {

use SomeFeature;

}

Bonus feature

You essentially get multiple inheritance free of charge as the conflict rules for both interfaces and traits are already well defined.

class DoublyInherited implements A, B {

use A, B;

}

As ChatGPT told me yesterday: Sure, this blurs the lines between classes, traits, and interfaces. But so does PHP in general.

BR,

Juris

Hi

Am 2025-04-01 12:09, schrieb Juris Evertovskis:

Sorry, but I've got another idea. I already validated it with ChatGPT who
labeled it spicy. After clarifying some misunderstandings, they updated the
label to "heretical". I hope you'll find it exciting as well.

[…]

As ChatGPT told me yesterday: Sure, this blurs the lines between classes,
traits, and interfaces. But so does PHP in general.

The wording “spicy”, “heretical”, “literally unusable”, “vibe”, the use of ChatGPT, and the current calendar date requires me to ask:

Is this intended as a serious proposal? I've stopped reading halfway through, because depending on how the proposal is intended to be perceived, the appropriate response and the amount of reasonable time to spend on it would differ.

Best regards
Tim Düsterhus

The wording “spicy”, “heretical”, “literally unusable”, “vibe”, the use
of ChatGPT, and the current calendar date requires me to ask:

Actually, if we put aside all of this, and think about it more seriously, personally I myself find it somewhat tedious to have every class have interface for the sake of being able to decorate it.

Everything we expose as api for the client code normally should have an interface, and usually it ends up with superficial name: either suffixed with Interface (I know it is in PHP), or prefixed with I (C#), or just a normal name, while the actual class is suffixed with Impl (Java), or any other naming that only shows the problem of superficial separation.

In my opinion, it could be a good thing to consider if we could do it more easily.

It reminds me of C++, where every class has .h file with headers without actual implementation (IOW, interface, client code uses it), and the actual implementation in .cpp file. Though the two are still physically separated, they still logically make the same unit.

Having the ability to implement the class, we’d not need to suffix interfaces with Interface all around where there’s only one implementation of it. On the other hand, it still makes sense to keep normal full fledged interfaces, since these are usable when there are multiple different implementations (say, we have BankTransactionsGateway as the interface, and PrivatBankTransactionsGateway, MonoBankTransactionsGateway as implementations), since in this case interface is a normal expected abstraction, that should be kept separate.

Thank you

On Tue, Apr 1, 2025 at 1:24 PM Eugene Sidelnyk <zsidelnik@gmail.com> wrote:

The wording “spicy”, “heretical”, “literally unusable”, “vibe”, the use
of ChatGPT, and the current calendar date requires me to ask:

Actually, if we put aside all of this, and think about it more seriously, personally I myself find it somewhat tedious to have every class have interface for the sake of being able to decorate it.

Everything we expose as api for the client code normally should have an interface, and usually it ends up with superficial name: either suffixed with Interface (I know it is in PHP), or prefixed with I (C#), or just a normal name, while the actual class is suffixed with Impl (Java), or any other naming that only shows the problem of superficial separation.

In my opinion, it could be a good thing to consider if we could do it more easily.

It reminds me of C++, where every class has .h file with headers without actual implementation (IOW, interface, client code uses it), and the actual implementation in .cpp file. Though the two are still physically separated, they still logically make the same unit.

Having the ability to implement the class, we’d not need to suffix interfaces with Interface all around where there’s only one implementation of it. On the other hand, it still makes sense to keep normal full fledged interfaces, since these are usable when there are multiple different implementations (say, we have BankTransactionsGateway as the interface, and PrivatBankTransactionsGateway, MonoBankTransactionsGateway as implementations), since in this case interface is a normal expected abstraction, that should be kept separate.

Thank you

Reading the original post I actually thought about being able to implement the interface of a final class that doesn’t have an interface by default. This would include all public and protected methods and properties. Would make it really easy to decorate/mock/stub objects.

Not sure if this would be considered a good practice, but certainly would help with composition where otherwise nothing is possible. Would also make it possible for a single object to implement the interface of multiple different objects and fulfill all the contracts, which probably opens up a whole new can of worms, but I could see myself using it to support legacy code.

On 2025-04-01 13:27, Tim Düsterhus wrote:

Is this intended as a serious proposal? I've stopped reading halfway
through, because depending on how the proposal is intended to be
perceived, the appropriate response and the amount of reasonable time to
spend on it would differ.

Hi,

I'm abusing the 1st of April to present a bit outlandish idea in a not too serious manner.

At this point I don't expect anyone to spend significant time thinking about this. I was hoping to gauge whether some people apart from myself would be interested in such features.

BR,
Juris

On 01/04/2025 11:09, Juris Evertovskis wrote:

## Part B. Usable classes

Idea: allow using normal classes in the trait list.

Poking around in the archives, it seems there was a lot of discussion about what exactly "horizontal reuse" should look like before the current version of traits was added, way back in 2008. There were at least 4 RFCs drafted with different versions:

- PHP: rfc:nonbreakabletraits
- PHP: rfc:traits
- PHP: rfc:mixin
- PHP: rfc:horizontalreuse

That last one, interestingly, has the implemented definition of traits, but also a declined feature of "grafts", which plugged whole classes together with slightly different semantics.

You might be interested to skim through those, and some of the threads from the time (e.g. on https://externals.io) to see why they ended up how they did.

--
Rowan Tommins
[IMSoP]