1use std::ptr;
4
5use crate::{
6 boxed::ZBox,
7 class::RegisteredClass,
8 convert::{FromZendObject, FromZval, IntoZendObject, IntoZval},
9 describe::DocComments,
10 error::{Error, Result},
11 ffi::zend_enum_get_case,
12 flags::{ClassFlags, DataType},
13 rc::PhpRc,
14 types::{ZendObject, ZendStr, Zval},
15};
16
17pub trait RegisteredEnum {
19 const CASES: &'static [EnumCase];
21
22 fn from_name(name: &str) -> Result<Self>
27 where
28 Self: Sized;
29
30 fn to_name(&self) -> &'static str;
32}
33
34impl<T> FromZendObject<'_> for T
35where
36 T: RegisteredEnum,
37{
38 fn from_zend_object(obj: &ZendObject) -> Result<Self> {
39 if !ClassFlags::from_bits_truncate(unsafe { (*obj.ce).ce_flags }).contains(ClassFlags::Enum)
40 {
41 return Err(Error::InvalidProperty);
42 }
43
44 let name = obj
45 .get_properties()?
46 .get("name")
47 .and_then(Zval::indirect)
48 .and_then(Zval::str)
49 .ok_or(Error::InvalidProperty)?;
50
51 T::from_name(name)
52 }
53}
54
55impl<T> FromZval<'_> for T
56where
57 T: RegisteredEnum + RegisteredClass,
58{
59 const TYPE: DataType = DataType::Object(Some(T::CLASS_NAME));
60
61 fn from_zval(zval: &Zval) -> Option<Self> {
62 zval.object()
63 .and_then(|obj| Self::from_zend_object(obj).ok())
64 }
65}
66
67impl<T> IntoZendObject for T
68where
69 T: RegisteredEnum + RegisteredClass,
70{
71 fn into_zend_object(self) -> Result<ZBox<ZendObject>> {
72 let mut name = ZendStr::new(T::to_name(&self), false);
73 let variant = unsafe {
74 zend_enum_get_case(
75 ptr::from_ref(T::get_metadata().ce()).cast_mut(),
76 &raw mut *name,
77 )
78 };
79
80 Ok(unsafe { ZBox::from_raw(variant) })
81 }
82}
83
84impl<T> IntoZval for T
85where
86 T: RegisteredEnum + RegisteredClass,
87{
88 const TYPE: DataType = DataType::Object(Some(T::CLASS_NAME));
89 const NULLABLE: bool = false;
90
91 fn set_zval(self, zv: &mut Zval, _persistent: bool) -> Result<()> {
92 let mut obj = self.into_zend_object()?;
97 obj.dec_count();
98 zv.set_object(obj.into_raw());
99 Ok(())
100 }
101}
102pub struct EnumCase {
116 pub name: &'static str,
118 pub discriminant: Option<Discriminant>,
120 pub docs: DocComments,
122}
123
124impl EnumCase {
125 #[must_use]
127 pub fn data_type(&self) -> DataType {
128 match self.discriminant {
129 Some(Discriminant::Int(_)) => DataType::Long,
130 Some(Discriminant::String(_)) => DataType::String,
131 None => DataType::Undef,
132 }
133 }
134}
135
136#[derive(Debug, PartialEq, Eq)]
139pub enum Discriminant {
140 Int(i64),
142 String(&'static str),
144}
145
146impl TryFrom<&Discriminant> for Zval {
147 type Error = Error;
148
149 fn try_from(value: &Discriminant) -> Result<Self> {
150 match value {
151 Discriminant::Int(i) => i.into_zval(false),
152 Discriminant::String(s) => s.into_zval(false),
153 }
154 }
155}
156
157#[cfg(test)]
158#[cfg(feature = "embed")]
159mod tests {
160 #![allow(clippy::unwrap_used)]
161 use super::*;
162 use crate::embed::Embed;
163
164 #[test]
165 fn test_zval_try_from_discriminant() {
166 Embed::run(|| {
167 let zval_int: Zval = Zval::try_from(&Discriminant::Int(42)).unwrap();
168 assert!(zval_int.is_long());
169 assert_eq!(zval_int.long().unwrap(), 42);
170
171 let zval_str: Zval = Zval::try_from(&Discriminant::String("foo")).unwrap();
172 assert!(zval_str.is_string());
173 assert_eq!(zval_str.string().unwrap().clone(), "foo");
174 });
175 }
176}