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    rc::PhpRc,
14    types::{ZendObject, ZendStr, Zval},
15};
16
17/// Implemented on Rust enums which are exported to PHP.
18pub trait RegisteredEnum {
19    /// The cases of the enum.
20    const CASES: &'static [EnumCase];
21
22    /// # Errors
23    ///
24    /// - [`Error::InvalidProperty`] if the enum does not have a case with the
25    ///   given name, an error is returned.
26    fn from_name(name: &str) -> Result<Self>
27    where
28        Self: Sized;
29
30    /// Returns the variant name of the enum as it is registered in PHP.
31    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        // `set_object` increments the object refcount; the ZBox returned by
93        // `into_zend_object` already owns one ref, so we drop one before
94        // handing the pointer over to keep the net count at 1. Same pattern as
95        // `ZBox<ZendObject>::set_zval` in `object.rs`.
96        let mut obj = self.into_zend_object()?;
97        obj.dec_count();
98        zv.set_object(obj.into_raw());
99        Ok(())
100    }
101}
102// impl<'a, T> IntoZval for T
103// where
104//     T: RegisteredEnum + RegisteredClass + IntoZendObject
105// {
106//     const TYPE: DataType = DataType::Object(Some(T::CLASS_NAME));
107//     const NULLABLE: bool = false;
108//
109//     fn set_zval(self, zv: &mut Zval, persistent: bool) -> Result<()> {
110//         let obj = self.into_zend_object()?;
111//     }
112// }
113
114/// Represents a case in a PHP enum.
115pub struct EnumCase {
116    /// The identifier of the enum case, e.g. `Bar` in `enum Foo { Bar }`.
117    pub name: &'static str,
118    /// The value of the enum case, which can be an integer or a string.
119    pub discriminant: Option<Discriminant>,
120    /// The documentation comments for the enum case.
121    pub docs: DocComments,
122}
123
124impl EnumCase {
125    /// Gets the PHP data type of the enum case's discriminant.
126    #[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/// Represents the discriminant of an enum case in PHP, which can be either an
137/// integer or a string.
138#[derive(Debug, PartialEq, Eq)]
139pub enum Discriminant {
140    /// An integer discriminant.
141    Int(i64),
142    /// A string discriminant.
143    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}