diff --git a/phper-doc/doc/_06_module/_06_register_class/index.md b/phper-doc/doc/_06_module/_06_register_class/index.md index ff206fbc..73e25d7c 100644 --- a/phper-doc/doc/_06_module/_06_register_class/index.md +++ b/phper-doc/doc/_06_module/_06_register_class/index.md @@ -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, diff --git a/phper-doc/doc/_06_module/_07_register_interface/index.md b/phper-doc/doc/_06_module/_07_register_interface/index.md index c71c36ba..1bb5733b 100644 --- a/phper-doc/doc/_06_module/_07_register_interface/index.md +++ b/phper-doc/doc/_06_module/_07_register_interface/index.md @@ -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); +``` diff --git a/phper/src/classes.rs b/phper/src/classes.rs index 68166dd0..852031de 100644 --- a/phper/src/classes.rs +++ b/phper/src/classes.rs @@ -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}, @@ -377,6 +377,7 @@ pub struct ClassEntity { property_entities: Vec, parent: Option &'static ClassEntry>>, interfaces: Vec &'static ClassEntry>>, + constants: Vec, bind_class: Option<&'static StaticStateClass>, state_cloner: Option>, _p: PhantomData<(*mut (), T)>, @@ -414,6 +415,7 @@ impl ClassEntity { property_entities: Vec::new(), parent: None, interfaces: Vec::new(), + constants: Vec::new(), bind_class: None, state_cloner: None, _p: PhantomData, @@ -487,6 +489,12 @@ impl ClassEntity { self.property_entities.push(entity); } + /// Add constant to class + pub fn add_constant(&mut self, name: impl Into, value: impl Into) { + 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* @@ -603,6 +611,10 @@ impl ClassEntity { 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 @@ -680,6 +692,7 @@ unsafe extern "C" fn class_init_handler( pub struct InterfaceEntity { interface_name: CString, method_entities: Vec, + constants: Vec, extends: Vec &'static ClassEntry>>, bind_interface: Option<&'static StaticInterface>, } @@ -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, } @@ -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, value: impl Into) { + 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. /// @@ -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 } @@ -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, value: impl Into) -> Self { + Self { + name: name.into(), + value: value.into(), + } + } +} + /// Builder for declare class property. struct PropertyEntity { name: String, @@ -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); diff --git a/tests/integration/src/classes.rs b/tests/integration/src/classes.rs index 5e243c1f..d7c5a5d1 100644 --- a/tests/integration/src/classes.rs +++ b/tests/integration/src/classes.rs @@ -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); } @@ -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| { @@ -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"); diff --git a/tests/integration/tests/php/classes.php b/tests/integration/tests/php/classes.php index fcfe9c72..ecd36787 100644 --- a/tests/integration/tests/php/classes.php +++ b/tests/integration/tests/php/classes.php @@ -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);