# `#[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_fn` must be a function with the signature `fn() -> &'static ClassEntry`.
- `#[php(implements(ce = ce_fn, stub = "InterfaceName"))]` - Implements the given interface on the class. Can be used
  multiple times. `ce_fn` must be a valid function with the signature
  `fn() -> &'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 rename the property with options:

- `name` - Allows you to rename the property, e.g.
  `#[php(name = "new_name")]`
- `change_case` - Allows you to rename the property using rename rules, e.g.
  `#[php(change_case = PascalCase)]`

## 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](https://doc.rust-lang.org/std/sync/struct.Arc.html).

### 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`.

```rust,no_run
# #![cfg_attr(windows, feature(abi_vectorcall))]
# extern crate ext_php_rs;
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>()
}
# fn main() {}
```

Create a custom exception `RedisException`, which extends `Exception`, and put
it in the `Redis\Exception` namespace:

```rust,no_run
# #![cfg_attr(windows, feature(abi_vectorcall))]
# extern crate ext_php_rs;
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))
}
# fn main() {}
```

## 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`](https://www.php.net/manual/en/class.arrayaccess.php):

````rust,no_run
# #![cfg_attr(windows, feature(abi_vectorcall))]
# extern crate ext_php_rs;
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>()
}
# fn main() {}
````
