Skip to content

Commit 9d3b15c

Browse files
authored
adding class+interface constants (#171)
1 parent 7b23005 commit 9d3b15c

File tree

5 files changed

+142
-1
lines changed

5 files changed

+142
-1
lines changed

phper-doc/doc/_06_module/_06_register_class/index.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,18 @@ foo.add_static_method(
102102
).argument(Argument::by_val("name"));
103103
```
104104

105+
## Add constants
106+
Interfaces can have public constants. Value can be string|int|bool|float|null.
107+
108+
```rust,no_run
109+
use phper::classes::ClassEntity;
110+
111+
let mut foo = ClassEntity::new("Foo");
112+
foo.add_constant("ONE", "one");
113+
foo.add_constant("TWO", 2);
114+
foo.add_constant("THREE", 3.0);
115+
```
116+
105117
## Handle state
106118

107119
> The `ClassEntity` represents the class entry hold the state as generic type,

phper-doc/doc/_06_module/_07_register_interface/index.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,3 +72,15 @@ foo.add_method("doSomethings").argument(Argument::by_val("name"));
7272
```
7373

7474
Note that abstract has no method body, so you don't need to add the handler to the method.
75+
76+
## Add constants
77+
Interfaces can have public constants. Value can be string|int|bool|float|null.
78+
79+
```rust,no_run
80+
use phper::classes::InterfaceEntity;
81+
82+
let mut foo = InterfaceEntity::new("Foo");
83+
foo.add_constant("ONE", "one");
84+
foo.add_constant("TWO", 2);
85+
foo.add_constant("THREE", 3.0);
86+
```

phper/src/classes.rs

Lines changed: 81 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ use crate::{
2424
};
2525
use std::{
2626
any::Any,
27-
ffi::{c_void, CString},
27+
ffi::{c_char, c_void, CString},
2828
fmt::Debug,
2929
marker::PhantomData,
3030
mem::{replace, size_of, zeroed, ManuallyDrop},
@@ -377,6 +377,7 @@ pub struct ClassEntity<T: 'static> {
377377
property_entities: Vec<PropertyEntity>,
378378
parent: Option<Box<dyn Fn() -> &'static ClassEntry>>,
379379
interfaces: Vec<Box<dyn Fn() -> &'static ClassEntry>>,
380+
constants: Vec<ConstantEntity>,
380381
bind_class: Option<&'static StaticStateClass<T>>,
381382
state_cloner: Option<Rc<StateCloner>>,
382383
_p: PhantomData<(*mut (), T)>,
@@ -414,6 +415,7 @@ impl<T: 'static> ClassEntity<T> {
414415
property_entities: Vec::new(),
415416
parent: None,
416417
interfaces: Vec::new(),
418+
constants: Vec::new(),
417419
bind_class: None,
418420
state_cloner: None,
419421
_p: PhantomData,
@@ -487,6 +489,12 @@ impl<T: 'static> ClassEntity<T> {
487489
self.property_entities.push(entity);
488490
}
489491

492+
/// Add constant to class
493+
pub fn add_constant(&mut self, name: impl Into<String>, value: impl Into<Scalar>) {
494+
let constant = ConstantEntity::new(name, value);
495+
self.constants.push(constant);
496+
}
497+
490498
/// Register class to `extends` the parent class.
491499
///
492500
/// *Because in the `MINIT` phase, the class starts to register, so the*
@@ -603,6 +611,10 @@ impl<T: 'static> ClassEntity<T> {
603611
zend_class_implements(class_ce, 1, interface_ce);
604612
}
605613

614+
for constant in &self.constants {
615+
add_class_constant(class_ce, constant);
616+
}
617+
606618
*phper_get_create_object(class_ce) = Some(create_object);
607619

608620
class_ce
@@ -680,6 +692,7 @@ unsafe extern "C" fn class_init_handler(
680692
pub struct InterfaceEntity {
681693
interface_name: CString,
682694
method_entities: Vec<MethodEntity>,
695+
constants: Vec<ConstantEntity>,
683696
extends: Vec<Box<dyn Fn() -> &'static ClassEntry>>,
684697
bind_interface: Option<&'static StaticInterface>,
685698
}
@@ -690,6 +703,7 @@ impl InterfaceEntity {
690703
Self {
691704
interface_name: ensure_end_with_zero(interface_name.into()),
692705
method_entities: Vec::new(),
706+
constants: Vec::new(),
693707
extends: Vec::new(),
694708
bind_interface: None,
695709
}
@@ -704,6 +718,12 @@ impl InterfaceEntity {
704718
self.method_entities.last_mut().unwrap()
705719
}
706720

721+
/// Add constant to interface
722+
pub fn add_constant(&mut self, name: impl Into<String>, value: impl Into<Scalar>) {
723+
let constant = ConstantEntity::new(name, value);
724+
self.constants.push(constant);
725+
}
726+
707727
/// Register interface to `extends` the interfaces, due to the interface can
708728
/// extends multi interface, so this method can be called multi time.
709729
///
@@ -751,6 +771,10 @@ impl InterfaceEntity {
751771
zend_class_implements(class_ce, 1, interface_ce);
752772
}
753773

774+
for constant in &self.constants {
775+
add_class_constant(class_ce, constant);
776+
}
777+
754778
class_ce
755779
}
756780

@@ -773,6 +797,21 @@ unsafe extern "C" fn interface_init_handler(
773797
zend_register_internal_interface(class_ce)
774798
}
775799

800+
/// Builder for registering class/interface constants
801+
pub struct ConstantEntity {
802+
name: String,
803+
value: Scalar,
804+
}
805+
806+
impl ConstantEntity {
807+
fn new(name: impl Into<String>, value: impl Into<Scalar>) -> Self {
808+
Self {
809+
name: name.into(),
810+
value: value.into(),
811+
}
812+
}
813+
}
814+
776815
/// Builder for declare class property.
777816
struct PropertyEntity {
778817
name: String,
@@ -973,6 +1012,47 @@ unsafe fn clone_object_common(object: *mut zend_object) -> *mut zend_object {
9731012
new_object
9741013
}
9751014

1015+
unsafe fn add_class_constant(class_ce: *mut _zend_class_entry, constant: &ConstantEntity) {
1016+
let name_ptr = constant.name.as_ptr() as *const c_char;
1017+
let name_len = constant.name.len();
1018+
unsafe {
1019+
match &constant.value {
1020+
Scalar::Null => {
1021+
zend_declare_class_constant_null(class_ce, name_ptr, name_len);
1022+
}
1023+
Scalar::Bool(b) => {
1024+
zend_declare_class_constant_bool(class_ce, name_ptr, name_len, *b as zend_bool);
1025+
}
1026+
Scalar::I64(i) => {
1027+
zend_declare_class_constant_long(class_ce, name_ptr, name_len, *i as zend_long);
1028+
}
1029+
Scalar::F64(f) => {
1030+
zend_declare_class_constant_double(class_ce, name_ptr, name_len, *f);
1031+
}
1032+
Scalar::String(s) => {
1033+
let s_ptr = s.as_ptr() as *mut u8;
1034+
zend_declare_class_constant_stringl(
1035+
class_ce,
1036+
name_ptr,
1037+
name_len,
1038+
s_ptr.cast(),
1039+
s.len(),
1040+
);
1041+
}
1042+
Scalar::Bytes(s) => {
1043+
let s_ptr = s.as_ptr() as *mut u8;
1044+
zend_declare_class_constant_stringl(
1045+
class_ce,
1046+
name_ptr,
1047+
name_len,
1048+
s_ptr.cast(),
1049+
s.len(),
1050+
);
1051+
}
1052+
}
1053+
}
1054+
}
1055+
9761056
unsafe extern "C" fn free_object(object: *mut zend_object) {
9771057
let state_object = StateObj::<()>::from_mut_object_ptr(object);
9781058

tests/integration/src/classes.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ pub fn integrate(module: &mut Module) {
2525
integrate_foo(module);
2626
integrate_i_bar(module);
2727
integrate_static_props(module);
28+
integrate_i_constants(module);
2829
#[cfg(phper_major_version = "8")]
2930
integrate_stringable(module);
3031
}
@@ -34,6 +35,12 @@ fn integrate_a(module: &mut Module) {
3435

3536
class.add_property("name", Visibility::Private, "default");
3637
class.add_property("number", Visibility::Private, 100);
38+
class.add_constant("CST_STRING", "foo");
39+
class.add_constant("CST_NULL", ());
40+
class.add_constant("CST_TRUE", true);
41+
class.add_constant("CST_FALSE", false);
42+
class.add_constant("CST_INT", 100);
43+
class.add_constant("CST_FLOAT", 10.0);
3744

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

168+
fn integrate_i_constants(module: &mut Module) {
169+
let mut interface = InterfaceEntity::new(r"IntegrationTest\IConstants");
170+
171+
interface.add_constant("CST_STRING", "foo");
172+
interface.add_constant("CST_NULL", ());
173+
interface.add_constant("CST_TRUE", true);
174+
interface.add_constant("CST_FALSE", false);
175+
interface.add_constant("CST_INT", 100);
176+
interface.add_constant("CST_FLOAT", 10.0);
177+
178+
module.add_interface(interface);
179+
}
180+
161181
fn integrate_static_props(module: &mut Module) {
162182
let mut class = ClassEntity::new("IntegrationTest\\PropsHolder");
163183

tests/integration/tests/php/classes.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,3 +78,20 @@ class Foo2 extends IntegrationTest\Foo {}
7878
if (PHP_VERSION_ID >= 80000) {
7979
assert_eq(((string) (new IntegrationTest\FooString())), 'string');
8080
}
81+
82+
// Test class constants
83+
assert_eq('foo', IntegrationTest\A::CST_STRING);
84+
assert_eq(null, IntegrationTest\A::CST_NULL);
85+
assert_true(true, IntegrationTest\A::CST_TRUE);
86+
assert_false(false, IntegrationTest\A::CST_FALSE);
87+
assert_eq(100, IntegrationTest\A::CST_INT);
88+
assert_eq(10.0, IntegrationTest\A::CST_FLOAT);
89+
90+
// Test interface constants
91+
assert_true(interface_exists(IntegrationTest\IConstants::class));
92+
assert_eq('foo', IntegrationTest\IConstants::CST_STRING);
93+
assert_eq(null, IntegrationTest\IConstants::CST_NULL);
94+
assert_true(IntegrationTest\IConstants::CST_TRUE);
95+
assert_false(IntegrationTest\IConstants::CST_FALSE);
96+
assert_eq(100, IntegrationTest\IConstants::CST_INT);
97+
assert_eq(10.0, IntegrationTest\IConstants::CST_FLOAT);

0 commit comments

Comments
 (0)