ext_php_rs/
enum_.rs

1//! This module defines the `PhpEnum` trait and related types for Rust enums that are exported to PHP.
2use std::ptr;
3
4use crate::{
5    boxed::ZBox,
6    class::RegisteredClass,
7    convert::{FromZendObject, FromZval, IntoZendObject, IntoZval},
8    describe::DocComments,
9    error::{Error, Result},
10    ffi::zend_enum_get_case,
11    flags::{ClassFlags, DataType},
12    types::{ZendObject, ZendStr, Zval},
13};
14
15/// Implemented on Rust enums which are exported to PHP.
16pub trait RegisteredEnum {
17    /// The cases of the enum.
18    const CASES: &'static [EnumCase];
19
20    /// # Errors
21    ///
22    /// - [`Error::InvalidProperty`] if the enum does not have a case with the given name, an error is returned.
23    fn from_name(name: &str) -> Result<Self>
24    where
25        Self: Sized;
26
27    /// Returns the variant name of the enum as it is registered in PHP.
28    fn to_name(&self) -> &'static str;
29}
30
31impl<T> FromZendObject<'_> for T
32where
33    T: RegisteredEnum,
34{
35    fn from_zend_object(obj: &ZendObject) -> Result<Self> {
36        if !ClassFlags::from_bits_truncate(unsafe { (*obj.ce).ce_flags }).contains(ClassFlags::Enum)
37        {
38            return Err(Error::InvalidProperty);
39        }
40
41        let name = obj
42            .get_properties()?
43            .get("name")
44            .and_then(Zval::indirect)
45            .and_then(Zval::str)
46            .ok_or(Error::InvalidProperty)?;
47
48        T::from_name(name)
49    }
50}
51
52impl<T> FromZval<'_> for T
53where
54    T: RegisteredEnum + RegisteredClass,
55{
56    const TYPE: DataType = DataType::Object(Some(T::CLASS_NAME));
57
58    fn from_zval(zval: &Zval) -> Option<Self> {
59        zval.object()
60            .and_then(|obj| Self::from_zend_object(obj).ok())
61    }
62}
63
64impl<T> IntoZendObject for T
65where
66    T: RegisteredEnum + RegisteredClass,
67{
68    fn into_zend_object(self) -> Result<ZBox<ZendObject>> {
69        let mut name = ZendStr::new(T::to_name(&self), false);
70        let variant = unsafe {
71            zend_enum_get_case(
72                ptr::from_ref(T::get_metadata().ce()).cast_mut(),
73                &raw mut *name,
74            )
75        };
76
77        Ok(unsafe { ZBox::from_raw(variant) })
78    }
79}
80
81impl<T> IntoZval for T
82where
83    T: RegisteredEnum + RegisteredClass,
84{
85    const TYPE: DataType = DataType::Object(Some(T::CLASS_NAME));
86    const NULLABLE: bool = false;
87
88    fn set_zval(self, zv: &mut Zval, _persistent: bool) -> Result<()> {
89        let obj = self.into_zend_object()?;
90        zv.set_object(obj.into_raw());
91        Ok(())
92    }
93}
94// impl<'a, T> IntoZval for T
95// where
96//     T: RegisteredEnum + RegisteredClass + IntoZendObject
97// {
98//     const TYPE: DataType = DataType::Object(Some(T::CLASS_NAME));
99//     const NULLABLE: bool = false;
100//
101//     fn set_zval(self, zv: &mut Zval, persistent: bool) -> Result<()> {
102//         let obj = self.into_zend_object()?;
103//     }
104// }
105
106/// Represents a case in a PHP enum.
107pub struct EnumCase {
108    /// The identifier of the enum case, e.g. `Bar` in `enum Foo { Bar }`.
109    pub name: &'static str,
110    /// The value of the enum case, which can be an integer or a string.
111    pub discriminant: Option<Discriminant>,
112    /// The documentation comments for the enum case.
113    pub docs: DocComments,
114}
115
116impl EnumCase {
117    /// Gets the PHP data type of the enum case's discriminant.
118    #[must_use]
119    pub fn data_type(&self) -> DataType {
120        match self.discriminant {
121            Some(Discriminant::Int(_)) => DataType::Long,
122            Some(Discriminant::String(_)) => DataType::String,
123            None => DataType::Undef,
124        }
125    }
126}
127
128/// Represents the discriminant of an enum case in PHP, which can be either an integer or a string.
129#[derive(Debug, PartialEq, Eq)]
130pub enum Discriminant {
131    /// An integer discriminant.
132    Int(i64),
133    /// A string discriminant.
134    String(&'static str),
135}
136
137impl TryFrom<&Discriminant> for Zval {
138    type Error = Error;
139
140    fn try_from(value: &Discriminant) -> Result<Self> {
141        match value {
142            Discriminant::Int(i) => i.into_zval(false),
143            Discriminant::String(s) => s.into_zval(false),
144        }
145    }
146}
147
148#[cfg(test)]
149#[cfg(feature = "embed")]
150mod tests {
151    #![allow(clippy::unwrap_used)]
152    use super::*;
153    use crate::embed::Embed;
154
155    #[test]
156    fn test_zval_try_from_discriminant() {
157        Embed::run(|| {
158            let zval_int: Zval = Zval::try_from(&Discriminant::Int(42)).unwrap();
159            assert!(zval_int.is_long());
160            assert_eq!(zval_int.long().unwrap(), 42);
161
162            let zval_str: Zval = Zval::try_from(&Discriminant::String("foo")).unwrap();
163            assert!(zval_str.is_string());
164            assert_eq!(zval_str.string().unwrap().to_string(), "foo");
165        });
166    }
167}