Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions phper-doc/doc/_06_module/_06_register_class/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,18 @@ foo.add_static_method(
).argument(Argument::by_val("name"));
```

## Add constants
Interfaces can have public constants. Value can be string|int|bool|float|null.

```rust,no_run
use phper::classes::ClassEntity;

let mut foo = ClassEntity::new("Foo");
foo.add_constant("ONE", "one");
foo.add_constant("TWO", 2);
foo.add_constant("THREE", 3.0);
```

## Handle state

> The `ClassEntity` represents the class entry hold the state as generic type,
Expand Down
12 changes: 12 additions & 0 deletions phper-doc/doc/_06_module/_07_register_interface/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,3 +72,15 @@ foo.add_method("doSomethings").argument(Argument::by_val("name"));
```

Note that abstract has no method body, so you don't need to add the handler to the method.

## Add constants
Interfaces can have public constants. Value can be string|int|bool|float|null.

```rust,no_run
use phper::classes::InterfaceEntity;

let mut foo = InterfaceEntity::new("Foo");
foo.add_constant("ONE", "one");
foo.add_constant("TWO", 2);
foo.add_constant("THREE", 3.0);
```
82 changes: 81 additions & 1 deletion phper/src/classes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ use crate::{
};
use std::{
any::Any,
ffi::{c_void, CString},
ffi::{c_char, c_void, CString},
fmt::Debug,
marker::PhantomData,
mem::{replace, size_of, zeroed, ManuallyDrop},
Expand Down Expand Up @@ -377,6 +377,7 @@ pub struct ClassEntity<T: 'static> {
property_entities: Vec<PropertyEntity>,
parent: Option<Box<dyn Fn() -> &'static ClassEntry>>,
interfaces: Vec<Box<dyn Fn() -> &'static ClassEntry>>,
constants: Vec<ConstantEntity>,
bind_class: Option<&'static StaticStateClass<T>>,
state_cloner: Option<Rc<StateCloner>>,
_p: PhantomData<(*mut (), T)>,
Expand Down Expand Up @@ -414,6 +415,7 @@ impl<T: 'static> ClassEntity<T> {
property_entities: Vec::new(),
parent: None,
interfaces: Vec::new(),
constants: Vec::new(),
bind_class: None,
state_cloner: None,
_p: PhantomData,
Expand Down Expand Up @@ -487,6 +489,12 @@ impl<T: 'static> ClassEntity<T> {
self.property_entities.push(entity);
}

/// Add constant to class
pub fn add_constant(&mut self, name: impl Into<String>, value: impl Into<Scalar>) {
let constant = ConstantEntity::new(name, value);
self.constants.push(constant);
}

/// Register class to `extends` the parent class.
///
/// *Because in the `MINIT` phase, the class starts to register, so the*
Expand Down Expand Up @@ -603,6 +611,10 @@ impl<T: 'static> ClassEntity<T> {
zend_class_implements(class_ce, 1, interface_ce);
}

for constant in &self.constants {
add_class_constant(class_ce, constant);
}

*phper_get_create_object(class_ce) = Some(create_object);

class_ce
Expand Down Expand Up @@ -680,6 +692,7 @@ unsafe extern "C" fn class_init_handler(
pub struct InterfaceEntity {
interface_name: CString,
method_entities: Vec<MethodEntity>,
constants: Vec<ConstantEntity>,
extends: Vec<Box<dyn Fn() -> &'static ClassEntry>>,
bind_interface: Option<&'static StaticInterface>,
}
Expand All @@ -690,6 +703,7 @@ impl InterfaceEntity {
Self {
interface_name: ensure_end_with_zero(interface_name.into()),
method_entities: Vec::new(),
constants: Vec::new(),
extends: Vec::new(),
bind_interface: None,
}
Expand All @@ -704,6 +718,12 @@ impl InterfaceEntity {
self.method_entities.last_mut().unwrap()
}

/// Add constant to interface
pub fn add_constant(&mut self, name: impl Into<String>, value: impl Into<Scalar>) {
let constant = ConstantEntity::new(name, value);
self.constants.push(constant);
}

/// Register interface to `extends` the interfaces, due to the interface can
/// extends multi interface, so this method can be called multi time.
///
Expand Down Expand Up @@ -751,6 +771,10 @@ impl InterfaceEntity {
zend_class_implements(class_ce, 1, interface_ce);
}

for constant in &self.constants {
add_class_constant(class_ce, constant);
}

class_ce
}

Expand All @@ -773,6 +797,21 @@ unsafe extern "C" fn interface_init_handler(
zend_register_internal_interface(class_ce)
}

/// Builder for registering class/interface constants
pub struct ConstantEntity {
name: String,
value: Scalar,
}

impl ConstantEntity {
fn new(name: impl Into<String>, value: impl Into<Scalar>) -> Self {
Self {
name: name.into(),
value: value.into(),
}
}
}

/// Builder for declare class property.
struct PropertyEntity {
name: String,
Expand Down Expand Up @@ -973,6 +1012,47 @@ unsafe fn clone_object_common(object: *mut zend_object) -> *mut zend_object {
new_object
}

unsafe fn add_class_constant(class_ce: *mut _zend_class_entry, constant: &ConstantEntity) {
let name_ptr = constant.name.as_ptr() as *const c_char;
let name_len = constant.name.len();
unsafe {
match &constant.value {
Scalar::Null => {
zend_declare_class_constant_null(class_ce, name_ptr, name_len);
}
Scalar::Bool(b) => {
zend_declare_class_constant_bool(class_ce, name_ptr, name_len, *b as zend_bool);
}
Scalar::I64(i) => {
zend_declare_class_constant_long(class_ce, name_ptr, name_len, *i as zend_long);
}
Scalar::F64(f) => {
zend_declare_class_constant_double(class_ce, name_ptr, name_len, *f);
}
Scalar::String(s) => {
let s_ptr = s.as_ptr() as *mut u8;
zend_declare_class_constant_stringl(
class_ce,
name_ptr,
name_len,
s_ptr.cast(),
s.len(),
);
}
Scalar::Bytes(s) => {
let s_ptr = s.as_ptr() as *mut u8;
zend_declare_class_constant_stringl(
class_ce,
name_ptr,
name_len,
s_ptr.cast(),
s.len(),
);
}
}
}
}

unsafe extern "C" fn free_object(object: *mut zend_object) {
let state_object = StateObj::<()>::from_mut_object_ptr(object);

Expand Down
20 changes: 20 additions & 0 deletions tests/integration/src/classes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ pub fn integrate(module: &mut Module) {
integrate_foo(module);
integrate_i_bar(module);
integrate_static_props(module);
integrate_i_constants(module);
#[cfg(phper_major_version = "8")]
integrate_stringable(module);
}
Expand All @@ -34,6 +35,12 @@ fn integrate_a(module: &mut Module) {

class.add_property("name", Visibility::Private, "default");
class.add_property("number", Visibility::Private, 100);
class.add_constant("CST_STRING", "foo");
class.add_constant("CST_NULL", ());
class.add_constant("CST_TRUE", true);
class.add_constant("CST_FALSE", false);
class.add_constant("CST_INT", 100);
class.add_constant("CST_FLOAT", 10.0);

class
.add_method("__construct", Visibility::Public, |this, arguments| {
Expand Down Expand Up @@ -158,6 +165,19 @@ fn integrate_i_bar(module: &mut Module) {
module.add_interface(interface);
}

fn integrate_i_constants(module: &mut Module) {
let mut interface = InterfaceEntity::new(r"IntegrationTest\IConstants");

interface.add_constant("CST_STRING", "foo");
interface.add_constant("CST_NULL", ());
interface.add_constant("CST_TRUE", true);
interface.add_constant("CST_FALSE", false);
interface.add_constant("CST_INT", 100);
interface.add_constant("CST_FLOAT", 10.0);

module.add_interface(interface);
}

fn integrate_static_props(module: &mut Module) {
let mut class = ClassEntity::new("IntegrationTest\\PropsHolder");

Expand Down
17 changes: 17 additions & 0 deletions tests/integration/tests/php/classes.php
Original file line number Diff line number Diff line change
Expand Up @@ -78,3 +78,20 @@ class Foo2 extends IntegrationTest\Foo {}
if (PHP_VERSION_ID >= 80000) {
assert_eq(((string) (new IntegrationTest\FooString())), 'string');
}

// Test class constants
assert_eq('foo', IntegrationTest\A::CST_STRING);
assert_eq(null, IntegrationTest\A::CST_NULL);
assert_true(true, IntegrationTest\A::CST_TRUE);
assert_false(false, IntegrationTest\A::CST_FALSE);
assert_eq(100, IntegrationTest\A::CST_INT);
assert_eq(10.0, IntegrationTest\A::CST_FLOAT);

// Test interface constants
assert_true(interface_exists(IntegrationTest\IConstants::class));
assert_eq('foo', IntegrationTest\IConstants::CST_STRING);
assert_eq(null, IntegrationTest\IConstants::CST_NULL);
assert_true(IntegrationTest\IConstants::CST_TRUE);
assert_false(IntegrationTest\IConstants::CST_FALSE);
assert_eq(100, IntegrationTest\IConstants::CST_INT);
assert_eq(10.0, IntegrationTest\IConstants::CST_FLOAT);
Loading