#[php_class]Expand description
§#[php_class] Attribute
Structs can be exported to PHP as classes with the #[php_class] attribute
macro. This attribute derives the RegisteredClass trait on your struct, as
well as registering the class to be registered with the #[php_module]
macro.
§Options
There are additional macros that modify the class. These macros must be
placed underneath the #[php_class] attribute.
name- Changes the name of the class when exported to PHP. The Rust struct name is kept the same. If no name is given, the name of the struct is used. Useful for namespacing classes.change_case- Changes the case of the class name when exported to PHP.#[php(extends(ce = ce_fn, stub = "ParentClass"))]- Sets the parent class of the class. Can only be used once.ce_fnmust be a function with the signaturefn() -> &'static ClassEntry.#[php(implements(ce = ce_fn, stub = "InterfaceName"))]- Implements the given interface on the class. Can be used multiple times.ce_fnmust be a valid function with the signaturefn() -> &'static ClassEntry.
You may also use the #[php(prop)] attribute on a struct field to use the
field as a PHP property. By default, the field will be accessible from PHP
publicly with the same name as the field. Property types must implement
IntoZval and FromZval.
You can customize properties with these options:
name- Allows you to rename the property, e.g.#[php(prop, name = "new_name")]change_case- Allows you to rename the property using rename rules, e.g.#[php(prop, change_case = PascalCase)]static- Makes the property static (shared across all instances), e.g.#[php(prop, static)]flags- Sets property visibility flags, e.g.#[php(prop, flags = ext_php_rs::flags::PropertyFlags::Private)]
§Restrictions
§No lifetime parameters
Rust lifetimes are used by the Rust compiler to reason about a program’s memory safety. They are a compile-time only concept; there is no way to access Rust lifetimes at runtime from a dynamic language like PHP.
As soon as Rust data is exposed to PHP,
there is no guarantee which the Rust compiler can make on how long the data
will live. PHP is a reference-counted language and those references can be
held for an arbitrarily long time, which is untraceable by the Rust
compiler. The only possible way to express this correctly is to require that
any #[php_class] does not borrow data for any lifetime shorter than the
'static lifetime, i.e. the #[php_class] cannot have any lifetime
parameters.
When you need to share ownership of data between PHP and Rust, instead of using borrowed references with lifetimes, consider using reference-counted smart pointers such as Arc.
§No generic parameters
A Rust struct Foo<T> with a generic parameter T generates new compiled
implementations each time it is used with a different concrete type for T.
These new implementations are generated by the compiler at each usage site.
This is incompatible with wrapping Foo in PHP,
where there needs to be a single compiled implementation of Foo which is
integrated with the PHP interpreter.
§Example
This example creates a PHP class Human, adding a PHP property address.
use ext_php_rs::prelude::*;
#[php_class]
pub struct Human {
name: String,
age: i32,
#[php(prop)]
address: String,
}
#[php_module]
pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
module.class::<Human>()
}Create a custom exception RedisException, which extends Exception, and
put it in the Redis\Exception namespace:
use ext_php_rs::{
prelude::*,
exception::PhpException,
zend::ce
};
#[php_class]
#[php(name = "Redis\\Exception\\RedisException")]
#[php(extends(ce = ce::exception, stub = "\\Exception"))]
#[derive(Default)]
pub struct RedisException;
// Throw our newly created exception
#[php_function]
pub fn throw_exception() -> PhpResult<i32> {
Err(PhpException::from_class::<RedisException>("Not good!".into()))
}
#[php_module]
pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
module
.class::<RedisException>()
.function(wrap_function!(throw_exception))
}§Implementing an Interface
To implement an interface, use #[php(implements(ce = ce_fn, stub = "InterfaceName")] where ce_fn is an function returning a ClassEntry. The following example implements ArrayAccess:
use ext_php_rs::{
prelude::*,
exception::PhpResult,
types::Zval,
zend::ce,
};
#[php_class]
#[php(implements(ce = ce::arrayaccess, stub = "\\ArrayAccess"))]
#[derive(Default)]
pub struct EvenNumbersArray;
/// Returns `true` if the array offset is an even number.
/// Usage:
/// ```php
/// $arr = new EvenNumbersArray();
/// var_dump($arr[0]); // true
/// var_dump($arr[1]); // false
/// var_dump($arr[2]); // true
/// var_dump($arr[3]); // false
/// var_dump($arr[4]); // true
/// var_dump($arr[5] = true); // Fatal error: Uncaught Exception: Setting values is not supported
/// ```
#[php_impl]
impl EvenNumbersArray {
pub fn __construct() -> EvenNumbersArray {
EvenNumbersArray {}
}
// We need to use `Zval` because ArrayAccess needs $offset to be a `mixed`
pub fn offset_exists(&self, offset: &'_ Zval) -> bool {
offset.is_long()
}
pub fn offset_get(&self, offset: &'_ Zval) -> PhpResult<bool> {
let integer_offset = offset.long().ok_or("Expected integer offset")?;
Ok(integer_offset % 2 == 0)
}
pub fn offset_set(&mut self, _offset: &'_ Zval, _value: &'_ Zval) -> PhpResult {
Err("Setting values is not supported".into())
}
pub fn offset_unset(&mut self, _offset: &'_ Zval) -> PhpResult {
Err("Setting values is not supported".into())
}
}
#[php_module]
pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
module.class::<EvenNumbersArray>()
}§Static Properties
Static properties are shared across all instances of a class. Use
#[php(prop, static)] to declare a static property. Unlike instance
properties, static properties are managed entirely by PHP and do not use
Rust property handlers.
You can specify a default value using the default attribute:
use ext_php_rs::prelude::*;
use ext_php_rs::class::RegisteredClass;
#[php_class]
pub struct Counter {
#[php(prop)]
pub instance_value: i32,
#[php(prop, static, default = 0)]
pub count: i32,
#[php(prop, static, flags = ext_php_rs::flags::PropertyFlags::Private)]
pub internal_state: String,
}
#[php_impl]
impl Counter {
pub fn __construct(value: i32) -> Self {
Self {
instance_value: value,
count: 0,
internal_state: String::new(),
}
}
/// Increment the static counter from Rust
pub fn increment() {
let ce = Self::get_metadata().ce();
let current: i64 = ce.get_static_property("count").unwrap_or(0);
ce.set_static_property("count", current + 1).unwrap();
}
/// Get the current count
pub fn get_count() -> i64 {
let ce = Self::get_metadata().ce();
ce.get_static_property("count").unwrap_or(0)
}
}
#[php_module]
pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
module.class::<Counter>()
}From PHP, you can access static properties directly on the class:
// No need to initialize - count already has default value of 0
Counter::increment();
Counter::increment();
echo Counter::$count; // 2
echo Counter::getCount(); // 2