Skip to content

Commit 5e5ace5

Browse files
committed
feat: Enhance enum functionality with access methods and error handling
1 parent 60a1053 commit 5e5ace5

File tree

7 files changed

+412
-8
lines changed

7 files changed

+412
-8
lines changed

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

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,106 @@ $colorFromValue = Color::from("FF0000"); // Returns RED
225225
$colorOrNull = Color::tryFrom("INVALID"); // Returns null (when the value doesn't exist)
226226
```
227227

228+
## Accessing Enums from Rust Code
229+
230+
After registering an enum, you might need to access it from Rust code. `PHPER` provides two ways to do this:
231+
232+
1. Using the returned `Enum` instance when registering the enum
233+
2. Using `Enum::from_name` to look up an enum by name
234+
235+
### Using the Returned `Enum` Instance
236+
237+
When you register an enum using `module.add_enum()`, it returns an `Enum` instance that you can save and use later.
238+
239+
```rust,no_run
240+
use phper::{modules::Module, php_get_module, enums::{EnumEntity, Enum}};
241+
242+
#[php_get_module]
243+
pub fn get_module() -> Module {
244+
let mut module = Module::new(
245+
env!("CARGO_CRATE_NAME"),
246+
env!("CARGO_PKG_VERSION"),
247+
env!("CARGO_PKG_AUTHORS"),
248+
);
249+
250+
// Create and register the enum
251+
let mut status_entity = EnumEntity::new("Status");
252+
status_entity.add_case("ACTIVE", ());
253+
status_entity.add_case("INACTIVE", ());
254+
255+
// Save the returned Enum instance
256+
let status_enum: Enum = module.add_enum(status_entity);
257+
258+
// Use the saved enum instance in a function
259+
module.add_function("get_active_status", move |_| {
260+
// Get the ACTIVE case from the enum
261+
let active_case = status_enum.get_case("ACTIVE")?;
262+
Ok::<_, phper::Error>(active_case)
263+
});
264+
265+
module
266+
}
267+
```
268+
269+
### Using `Enum::from_name`
270+
271+
If you don't have the original `Enum` instance, you can use `Enum::from_name` to look up an enum by its name.
272+
273+
```rust,no_run
274+
use phper::{enums::Enum, modules::Module, php_get_module};
275+
276+
#[php_get_module]
277+
pub fn get_module() -> Module {
278+
let mut module = Module::new(
279+
env!("CARGO_CRATE_NAME"),
280+
env!("CARGO_PKG_VERSION"),
281+
env!("CARGO_PKG_AUTHORS"),
282+
);
283+
284+
// Register functions that use enums
285+
module.add_function("get_status_case", |args| {
286+
// Look up the Status enum by name
287+
let status_enum = Enum::from_name("Status");
288+
289+
// Get the case name from the function arguments
290+
let case_name = args[0].as_z_str()?.to_str()?;
291+
292+
// Get the requested enum case
293+
let case = status_enum.get_case(case_name)?;
294+
295+
Ok::<_, phper::Error>(case)
296+
});
297+
298+
module
299+
}
300+
```
301+
302+
## Getting Enum Cases
303+
304+
Once you have an `Enum` instance, you can use the `get_case` method to access specific enum cases:
305+
306+
```rust,no_run
307+
// Get a case from a pure enum
308+
let status_enum = Enum::from_name("Status");
309+
let active_case = status_enum.get_case("ACTIVE")?;
310+
311+
// Get a case from a backed enum
312+
let level_enum = Enum::from_name("Level");
313+
let high_level = level_enum.get_case("HIGH")?;
314+
```
315+
316+
If you need to modify an enum case's properties, you can use `get_case_mut` to get a mutable reference:
317+
318+
```rust,no_run
319+
// Get a mutable reference to an enum case
320+
let mut status_enum = Enum::from_name("Status");
321+
let mut active_case = status_enum.get_case_mut("ACTIVE")?;
322+
323+
// Now you can modify the case object if needed
324+
```
325+
326+
If the specified case doesn't exist in the enum, both `get_case` and `get_case_mut` will return an `EnumCaseNotFoundError`.
327+
228328
## Complete Example
229329

230330
Here's a comprehensive example using both pure and backed enums:

phper/src/enums.rs

Lines changed: 188 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,20 +20,25 @@
2020
#![cfg(phper_enum_supported)]
2121

2222
use crate::{
23-
classes::{ConstantEntity, Interface, Visibility, add_class_constant},
24-
errors::Throwable,
23+
classes::{
24+
ClassEntry, ConstantEntity, InnerClassEntry, Interface, Visibility, add_class_constant,
25+
},
26+
errors::{EnumCaseNotFoundError, Throwable},
2527
functions::{Function, FunctionEntry, HandlerMap, MethodEntity},
28+
objects::ZObj,
29+
strings::ZString,
2630
sys::*,
2731
types::Scalar,
2832
utils::ensure_end_with_zero,
2933
values::ZVal,
3034
};
3135
use sealed::sealed;
3236
use std::{
37+
cell::RefCell,
3338
ffi::{CStr, CString},
3439
marker::PhantomData,
3540
mem::{MaybeUninit, zeroed},
36-
ptr::null_mut,
41+
ptr::{null, null_mut},
3742
rc::Rc,
3843
};
3944

@@ -94,6 +99,158 @@ struct EnumCase {
9499
value: Scalar,
95100
}
96101

102+
/// The [Enum] holds [zend_class_entry] for PHP enum, created by
103+
/// [Module::add_enum](crate::modules::Module::add_enum) or
104+
/// [EnumEntity::bound_enum].
105+
///
106+
/// When the enum registered (module initialized), the [Enum] will
107+
/// be initialized, so you can use the [Enum] to get enum cases, etc.
108+
///
109+
/// # Examples
110+
///
111+
/// ```rust
112+
/// use phper::{
113+
/// enums::{Enum, EnumEntity},
114+
/// modules::Module,
115+
/// php_get_module,
116+
/// };
117+
///
118+
/// fn make_status_enum() -> EnumEntity {
119+
/// let mut enum_entity = EnumEntity::new("Status");
120+
/// enum_entity.add_case("Active", ());
121+
/// enum_entity.add_case("Inactive", ());
122+
/// enum_entity.add_case("Pending", ());
123+
/// enum_entity
124+
/// }
125+
///
126+
/// #[php_get_module]
127+
/// pub fn get_module() -> Module {
128+
/// let mut module = Module::new(
129+
/// env!("CARGO_CRATE_NAME"),
130+
/// env!("CARGO_PKG_VERSION"),
131+
/// env!("CARGO_PKG_AUTHORS"),
132+
/// );
133+
///
134+
/// let _status_enum: Enum = module.add_enum(make_status_enum());
135+
///
136+
/// module
137+
/// }
138+
/// ```
139+
#[derive(Clone)]
140+
pub struct Enum {
141+
inner: Rc<RefCell<InnerClassEntry>>,
142+
}
143+
144+
impl Enum {
145+
/// Creates a null Enum reference. Used internally.
146+
fn null() -> Self {
147+
Self {
148+
inner: Rc::new(RefCell::new(InnerClassEntry::Ptr(null()))),
149+
}
150+
}
151+
152+
/// Create from name, which will be looked up from globals.
153+
pub fn from_name(name: impl Into<String>) -> Self {
154+
Self {
155+
inner: Rc::new(RefCell::new(InnerClassEntry::Name(name.into()))),
156+
}
157+
}
158+
159+
fn bind(&self, ptr: *mut zend_class_entry) {
160+
match &mut *self.inner.borrow_mut() {
161+
InnerClassEntry::Ptr(p) => {
162+
*p = ptr;
163+
}
164+
InnerClassEntry::Name(_) => {
165+
unreachable!("Cannot bind() an Enum created with from_name()");
166+
}
167+
}
168+
}
169+
170+
/// Converts to class entry.
171+
pub fn as_class_entry(&self) -> &ClassEntry {
172+
let inner = self.inner.borrow().clone();
173+
match inner {
174+
InnerClassEntry::Ptr(ptr) => unsafe { ClassEntry::from_ptr(ptr) },
175+
InnerClassEntry::Name(name) => {
176+
let entry = ClassEntry::from_globals(name).unwrap();
177+
*self.inner.borrow_mut() = InnerClassEntry::Ptr(entry.as_ptr());
178+
entry
179+
}
180+
}
181+
}
182+
183+
/// Get an enum case by name.
184+
///
185+
/// # Parameters
186+
///
187+
/// * `case_name` - The name of the enum case to retrieve
188+
///
189+
/// # Returns
190+
///
191+
/// A reference to ZObj representing the enum case, or an error if the case
192+
/// doesn't exist
193+
pub fn get_case<'a>(&self, case_name: impl AsRef<str>) -> crate::Result<&'a ZObj> {
194+
unsafe {
195+
let ce = self.as_class_entry().as_ptr() as *mut _;
196+
let case_name_str = case_name.as_ref();
197+
let mut name_zstr = ZString::new(case_name_str);
198+
199+
// Get the enum case
200+
let case_obj = zend_enum_get_case(ce, name_zstr.as_mut_ptr());
201+
202+
if case_obj.is_null() {
203+
return Err(EnumCaseNotFoundError::new(
204+
self.as_class_entry()
205+
.get_name()
206+
.to_string_lossy()
207+
.to_string(),
208+
case_name_str.to_owned(),
209+
)
210+
.into());
211+
}
212+
213+
// Convert to &ZObj
214+
Ok(ZObj::from_ptr(case_obj))
215+
}
216+
}
217+
218+
/// Get a mutable reference to an enum case by name.
219+
///
220+
/// # Parameters
221+
///
222+
/// * `case_name` - The name of the enum case to retrieve
223+
///
224+
/// # Returns
225+
///
226+
/// A mutable reference to ZObj representing the enum case, or an error if
227+
/// the case doesn't exist
228+
pub fn get_mut_case<'a>(&mut self, case_name: impl AsRef<str>) -> crate::Result<&'a mut ZObj> {
229+
unsafe {
230+
let ce = self.as_class_entry().as_ptr() as *mut _;
231+
let case_name_str = case_name.as_ref();
232+
let mut name_zstr = ZString::new(case_name_str);
233+
234+
// Get the enum case
235+
let case_obj = zend_enum_get_case(ce, name_zstr.as_mut_ptr());
236+
237+
if case_obj.is_null() {
238+
return Err(EnumCaseNotFoundError::new(
239+
self.as_class_entry()
240+
.get_name()
241+
.to_string_lossy()
242+
.to_string(),
243+
case_name_str.to_owned(),
244+
)
245+
.into());
246+
}
247+
248+
// Convert to &mut ZObj
249+
Ok(ZObj::from_mut_ptr(case_obj as *mut _))
250+
}
251+
}
252+
}
253+
97254
/// Builder for registering a PHP enum.
98255
///
99256
/// This struct facilitates the creation and registration of PHP enums from Rust
@@ -112,6 +269,7 @@ pub struct EnumEntity<B: EnumBackingType = ()> {
112269
cases: Vec<EnumCase>,
113270
constants: Vec<ConstantEntity>,
114271
interfaces: Vec<Interface>,
272+
bound_enum: Enum,
115273
_p: PhantomData<(B, *mut ())>,
116274
}
117275

@@ -133,6 +291,7 @@ impl<B: EnumBackingType> EnumEntity<B> {
133291
cases: Vec::new(),
134292
constants: Vec::new(),
135293
interfaces: Vec::new(),
294+
bound_enum: Enum::null(),
136295
_p: PhantomData,
137296
}
138297
}
@@ -199,6 +358,30 @@ impl<B: EnumBackingType> EnumEntity<B> {
199358
self.interfaces.push(interface);
200359
}
201360

361+
/// Get the bound enum.
362+
///
363+
/// # Examples
364+
///
365+
/// ```
366+
/// use phper::enums::{EnumEntity, Visibility};
367+
///
368+
/// pub fn make_status_enum() -> EnumEntity {
369+
/// let mut enum_entity = EnumEntity::new("Status");
370+
/// enum_entity.add_case("Active", ());
371+
/// enum_entity.add_case("Inactive", ());
372+
/// let status_enum = enum_entity.bound_enum();
373+
/// enum_entity.add_static_method("getActiveCase", Visibility::Public, move |_| {
374+
/// let active_case = status_enum.get_case("Active")?;
375+
/// Ok::<_, phper::Error>(active_case)
376+
/// });
377+
/// enum_entity
378+
/// }
379+
/// ```
380+
#[inline]
381+
pub fn bound_enum(&self) -> Enum {
382+
self.bound_enum.clone()
383+
}
384+
202385
unsafe fn function_entries(&self) -> *const zend_function_entry {
203386
unsafe {
204387
let mut methods = self
@@ -242,6 +425,8 @@ impl<B: EnumBackingType> EnumEntity<B> {
242425
self.function_entries(),
243426
);
244427

428+
self.bound_enum.bind(class_ce);
429+
245430
for interface in &self.interfaces {
246431
let interface_ce = interface.as_class_entry().as_ptr();
247432
zend_class_implements(class_ce, 1, interface_ce);

0 commit comments

Comments
 (0)