Skip to content

Add interface impl #527

@Norbytus

Description

@Norbytus

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, use zend_register_internal_interface if the class entry has ClassFlags::Interface.

2. Macros

  • Add a php_interface procedural macro.

  • Inside the macro:

    1. Iterate over all TraitItems.
    2. Collect all functions.
    3. If any function has a default implementation, return an error (not allowed in interfaces).
  • 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 includes IMPLEMENTS (an array) and an optional EXTENDS. But in PHP internals, interfaces also use zend_class_implements, so maybe we can reuse RegisteredClass::IMPLEMENTS in Rust.
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)

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions