#[php_interface] Attribute
You can export a Trait block to PHP. This exports all methods as well as
constants to PHP on the interface. Trait method SHOULD NOT contain default
implementations, as these are not supported in PHP interfaces.
Options
By default all constants are renamed to UPPER_CASE and all methods are renamed to
camelCase. This can be changed by passing the change_method_case and
change_constant_case as #[php] attributes on the impl block. The options are:
#[php(change_method_case = "snake_case")]- Renames the method to snake case.#[php(change_constant_case = "snake_case")]- Renames the constant to snake case.
See the name and change_case section for a list of all
available cases.
Methods
See the php_impl
Constants
See the php_impl
Example
Define an example trait with methods and constant:
#![cfg_attr(windows, feature(abi_vectorcall))]
extern crate ext_php_rs;
use ext_php_rs::{prelude::*, types::ZendClassObject};
#[php_interface]
#[php(name = "Rust\\TestInterface")]
trait Test {
const TEST: &'static str = "TEST";
fn co();
#[php(defaults(value = 0))]
fn set_value(&mut self, value: i32);
}
#[php_module]
pub fn module(module: ModuleBuilder) -> ModuleBuilder {
module
.interface::<PhpInterfaceTest>()
}
fn main() {}
Using our newly created interface in PHP:
<?php
assert(interface_exists("Rust\TestInterface"));
class B implements Rust\TestInterface {
public static function co() {}
public function setValue(?int $value = 0) {
}
}
Interface Inheritance
PHP interfaces can extend other interfaces. You can achieve this in two ways:
Using #[php(extends(...))]
Use the extends attribute to extend a built-in PHP interface or another Rust-defined interface.
For built-in PHP interfaces, use the explicit form:
#![cfg_attr(windows, feature(abi_vectorcall))]
extern crate ext_php_rs;
use ext_php_rs::prelude::*;
use ext_php_rs::zend::ce;
#[php_interface]
#[php(extends(ce = ce::throwable, stub = "\\Throwable"))]
#[php(name = "MyException")]
trait MyExceptionInterface {
fn get_error_code(&self) -> i32;
}
fn main() {}
For Rust-defined interfaces, you can use the simpler type syntax:
#![cfg_attr(windows, feature(abi_vectorcall))]
extern crate ext_php_rs;
use ext_php_rs::prelude::*;
#[php_interface]
trait BaseInterface {
fn base_method(&self) -> i32;
}
#[php_interface]
#[php(extends(BaseInterface))]
trait ExtendedInterface {
fn extended_method(&self) -> String;
}
fn main() {}
Using Rust Trait Bounds
You can also use Rust’s trait bound syntax. When a trait marked with #[php_interface]
has supertraits, the PHP interface will automatically extend those parent interfaces:
#![cfg_attr(windows, feature(abi_vectorcall))]
extern crate ext_php_rs;
use ext_php_rs::prelude::*;
#[php_interface]
#[php(name = "Rust\\ParentInterface")]
trait ParentInterface {
fn parent_method(&self) -> String;
}
// ChildInterface extends ParentInterface in PHP
#[php_interface]
#[php(name = "Rust\\ChildInterface")]
trait ChildInterface: ParentInterface {
fn child_method(&self) -> String;
}
#[php_module]
pub fn module(module: ModuleBuilder) -> ModuleBuilder {
module
.interface::<PhpInterfaceParentInterface>()
.interface::<PhpInterfaceChildInterface>()
}
fn main() {}
In PHP:
<?php
// ChildInterface extends ParentInterface
assert(is_a('Rust\ChildInterface', 'Rust\ParentInterface', true));
#[php_impl_interface] Attribute
The #[php_impl_interface] attribute allows a Rust class to implement a custom PHP
interface defined with #[php_interface]. This creates a relationship where PHP’s
instanceof and is_a() recognize the implementation.
Key feature: The macro automatically registers the trait methods as PHP methods
on the class. You don’t need to duplicate them in a separate #[php_impl] block.
Example
#![cfg_attr(windows, feature(abi_vectorcall))]
extern crate ext_php_rs;
use ext_php_rs::prelude::*;
// Define a custom interface
#[php_interface]
#[php(name = "Rust\\Greetable")]
trait Greetable {
fn greet(&self) -> String;
}
// Define a class
#[php_class]
#[php(name = "Rust\\Greeter")]
pub struct Greeter {
name: String,
}
#[php_impl]
impl Greeter {
pub fn __construct(name: String) -> Self {
Self { name }
}
// Note: No need to add greet() here - it's automatically
// registered by #[php_impl_interface] below
}
// Implement the interface for the class
// This automatically registers greet() as a PHP method
#[php_impl_interface]
impl Greetable for Greeter {
fn greet(&self) -> String {
format!("Hello, {}!", self.name)
}
}
#[php_module]
pub fn module(module: ModuleBuilder) -> ModuleBuilder {
module
.interface::<PhpInterfaceGreetable>()
.class::<Greeter>()
}
fn main() {}
Using in PHP:
<?php
$greeter = new Rust\Greeter("World");
// instanceof works
assert($greeter instanceof Rust\Greetable);
// is_a() works
assert(is_a($greeter, 'Rust\Greetable'));
// The greet() method is available (registered by #[php_impl_interface])
echo $greeter->greet(); // Output: Hello, World!
// Can be used as type hint
function greet(Rust\Greetable $obj): void {
echo $obj->greet();
}
greet($greeter);
When to Use
- Use
#[php_impl_interface]for custom interfaces you define with#[php_interface] - Use
#[php(implements(ce = ...))]on#[php_class]for built-in PHP interfaces likeIterator,ArrayAccess,Countable, etc.
See the Classes documentation for examples of implementing built-in interfaces.
Cross-Crate Support
The #[php_impl_interface] macro supports cross-crate interface discovery via the
inventory crate. This means you can define
an interface in one crate and implement it in another crate, and the implementation
will be automatically discovered at link time.
Example: Defining an Interface in a Library Crate
First, create a library crate that defines the interface:
# my-interfaces/Cargo.toml
[package]
name = "my-interfaces"
version = "0.1.0"
[dependencies]
ext-php-rs = "0.15"
// my-interfaces/src/lib.rs
use ext_php_rs::prelude::*;
/// A serializable interface that can convert objects to JSON.
#[php_interface]
#[php(name = "MyInterfaces\\Serializable")]
pub trait Serializable {
fn to_json(&self) -> String;
}
// Re-export the generated PHP interface struct for consumers
pub use PhpInterfaceSerializable;
Example: Implementing the Interface in Another Crate
Now create your extension crate that implements the interface:
# my-extension/Cargo.toml
[package]
name = "my-extension"
version = "0.1.0"
[lib]
crate-type = ["cdylib"]
[dependencies]
ext-php-rs = "0.15"
my-interfaces = { path = "../my-interfaces" }
// my-extension/src/lib.rs
use ext_php_rs::prelude::*;
use my_interfaces::Serializable;
#[php_class]
#[php(name = "MyExtension\\User")]
pub struct User {
name: String,
email: String,
}
#[php_impl]
impl User {
pub fn __construct(name: String, email: String) -> Self {
Self { name, email }
}
// Note: No need to add to_json() here - it's automatically
// registered by #[php_impl_interface] below
}
// Register the interface implementation
// This automatically registers to_json() as a PHP method
#[php_impl_interface]
impl Serializable for User {
fn to_json(&self) -> String {
format!(r#"{{"name":"{}","email":"{}"}}"#, self.name, self.email)
}
}
#[php_module]
pub fn module(module: ModuleBuilder) -> ModuleBuilder {
module
// Register the interface from the library crate
.interface::<my_interfaces::PhpInterfaceSerializable>()
.class::<User>()
}
Using in PHP
<?php
use MyExtension\User;
use MyInterfaces\Serializable;
$user = new User("John", "john@example.com");
// instanceof works across crates
assert($user instanceof Serializable);
// Type hints work
function serialize_object(Serializable $obj): string {
return $obj->toJson();
}
echo serialize_object($user);
// Output: {"name":"John","email":"john@example.com"}
Important Notes
-
Automatic method registration: The
#[php_impl_interface]macro automatically registers all trait methods as PHP methods on the class. You don’t need to duplicate them in a#[php_impl]block. -
Interface registration: The interface must be registered in the
#[php_module]function using.interface::<PhpInterfaceName>(). -
Link-time discovery: The
inventorycrate uses link-time registration for interface discovery, so all implementations are automatically discovered when the final binary is linked.