Skip to main content

ext_php_rs/
enum_.rs

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