2020#![ cfg( phper_enum_supported) ]
2121
2222use 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} ;
3135use sealed:: sealed;
3236use 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