-
Notifications
You must be signed in to change notification settings - Fork 76
Description
Description
Interface Registration
This change adds interface registration support.
To register an interface, we need to bind and use zend_register_internal_interface
from php-src
. Unlike classes, PHP interfaces do not support additional modifiers like final
, readonly
, etc.
Unlike abstract or regular classes, interfaces cannot use the implements
keyword. However, interfaces can extend multiple other interfaces. To extend an interface in C, you use the same function that is used to implement class interfaces.
A good example in C is how IteratorAggregate
extends Traversable
:
[IteratorAggregate extends Traversable in C](https://github.com/php/php-src/blob/dd3a098a9bf967831e889d2e19e873d09c71c9b9/Zend/zend_interfaces_arginfo.h#L120)
Interfaces can also declare constants using zend_register_stringl_constant
— we can reuse existing code for that.
To declare methods, use the same zend_function_entry
as with classes, but with the ZEND_ACC_ABSTRACT
flag. You can also use standard visibility flags like ZEND_ACC_PUBLIC
, ZEND_ACC_PROTECTED
, and ZEND_ACC_PRIVATE
. (Although it's still strange that PHP allows private or protected methods in interfaces.)
Since PHP 8.4, interfaces can also declare property hooks.
Goals
1. Binding and lib
- Add
zend_register_internal_interface
binding. - In
builders/class.rs
, usezend_register_internal_interface
if the class entry hasClassFlags::Interface
.
2. Macros
-
Add a
php_interface
procedural macro. -
Inside the macro:
- Iterate over all
TraitItem
s. - Collect all functions.
- If any function has a default implementation, return an error (not allowed in interfaces).
- Iterate over all
-
Since we can’t directly use
ext_php_rs::class::RegisteredClass
on a trait, generate a proxy (empty) struct with a generated name.- I'm not entirely sure if we should reuse
RegisteredClass
for interfaces, since it includesIMPLEMENTS
(an array) and an optionalEXTENDS
. But in PHP internals, interfaces also usezend_class_implements
, so maybe we can reuseRegisteredClass::IMPLEMENTS
in Rust.
- I'm not entirely sure if we should reuse
Interface Declaration Proposal
I'm considering whether we can declare a PHP interface in Rust like this (I need some feedback):
trait ExampleTrait {}
// Attempted this approach with `darling`, but had issues due to limited meta-programming experience
#[php_interface]
#[php(ty = ExampleTrait)]
struct ProxyStruct {}
This version looks easier for registering the interface in a module.
Alternatively, if we declare the trait directly:
#[php_interface]
trait ExampleTrait {}
Then the macro must somehow internally associate the trait with a generated struct. This might require a less obvious connection between the trait and the generated struct.
Next Steps
- Collect all supported items from the trait (e.g.,
const
,fn
). - Reuse the same macro as for classes, but mark all functions with the
Abstract
flag.
Use Case
Allow a Rust trait to be exposed and used as an interface in PHP.
Example
// PHP interfaces do not support modifiers like `final`, `readonly`, etc.
// They cannot `implement`, but can extend multiple other interfaces.
#[php_interface]
#[php(name = "NameToUsageInPhp")]
#[php(extends = "OtherInterface")] // Can specify multiple extends
trait RustInterface
{
const EXAMPLE: &'static str = "EXAMPLE"; // String constant
#[php(name = "customMethodName")] // Optional
#[php(vis = Public)] // Valid values: Public, Protected, Private
fn method_with_arg(input: String);
}
P.S.
This is a relatively large change. I suggest splitting the implementation into multiple PRs.
Future PRs can add additional features, such as:
#[php_interface]
trait One {}
#[php_interface]
trait Two: One {}
#[php_class]
struct B {}
impl One for B {}
impl Two for B {}
Let me know if you'd like a version with inline comments or if you're planning to submit this as a GitHub PR and want help with a commit message or changelog entry.
Additional Context
- I can try implementing this feature myself (please assign me to this issue)