• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

extphprs / ext-php-rs / 20424568890

22 Dec 2025 07:07AM UTC coverage: 35.997% (-0.06%) from 36.059%
20424568890

Pull #628

github

web-flow
Merge 1573d74a8 into 4ef666b34
Pull Request #628: fix(stubs): Constants' values are now properly transferred

19 of 66 new or added lines in 5 files covered. (28.79%)

3 existing lines in 1 file now uncovered.

1694 of 4706 relevant lines covered (36.0%)

12.61 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

21.74
/src/convert.rs
1
//! Traits used to convert between Zend/PHP and Rust types.
2

3
use crate::{
4
    boxed::ZBox,
5
    error::Result,
6
    exception::PhpException,
7
    flags::DataType,
8
    types::{ZendObject, Zval},
9
};
10

11
/// Allows zvals to be converted into Rust types in a fallible way. Reciprocal
12
/// of the [`IntoZval`] trait.
13
pub trait FromZval<'a>: Sized {
14
    /// The corresponding type of the implemented value in PHP.
15
    const TYPE: DataType;
16

17
    /// Attempts to retrieve an instance of `Self` from a reference to a
18
    /// [`Zval`].
19
    ///
20
    /// # Parameters
21
    ///
22
    /// * `zval` - Zval to get value from.
23
    fn from_zval(zval: &'a Zval) -> Option<Self>;
24
}
25

26
impl<'a, T> FromZval<'a> for Option<T>
27
where
28
    T: FromZval<'a>,
29
{
30
    const TYPE: DataType = T::TYPE;
31

32
    fn from_zval(zval: &'a Zval) -> Option<Self> {
×
33
        Some(T::from_zval(zval))
×
34
    }
35
}
36

37
/// Allows mutable zvals to be converted into Rust types in a fallible way.
38
///
39
/// If `Self` does not require the zval to be mutable to be extracted, you
40
/// should implement [`FromZval`] instead, as this trait is generically
41
/// implemented for any type that implements [`FromZval`].
42
pub trait FromZvalMut<'a>: Sized {
43
    /// The corresponding type of the implemented value in PHP.
44
    const TYPE: DataType;
45

46
    /// Attempts to retrieve an instance of `Self` from a mutable reference to a
47
    /// [`Zval`].
48
    ///
49
    /// # Parameters
50
    ///
51
    /// * `zval` - Zval to get value from.
52
    fn from_zval_mut(zval: &'a mut Zval) -> Option<Self>;
53
}
54

55
impl<'a, T> FromZvalMut<'a> for T
56
where
57
    T: FromZval<'a>,
58
{
59
    const TYPE: DataType = <T as FromZval>::TYPE;
60

61
    #[inline]
62
    fn from_zval_mut(zval: &'a mut Zval) -> Option<Self> {
6✔
63
        Self::from_zval(zval)
12✔
64
    }
65
}
66

67
/// `FromZendObject` is implemented by types which can be extracted from a Zend
68
/// object.
69
///
70
/// Normal usage is through the helper method `ZendObject::extract`:
71
///
72
/// ```rust,ignore
73
/// let obj: ZendObject = ...;
74
/// let repr: String = obj.extract();
75
/// let props: HashMap = obj.extract();
76
/// ```
77
///
78
/// Should be functionally equivalent to casting an object to another compatible
79
/// type.
80
pub trait FromZendObject<'a>: Sized {
81
    /// Extracts `Self` from the source `ZendObject`.
82
    ///
83
    /// # Errors
84
    ///
85
    /// If the conversion fails, an [`Error`] is returned.
86
    ///
87
    /// [`Error`]: crate::error::Error
88
    // TODO: Expand on error information
89
    fn from_zend_object(obj: &'a ZendObject) -> Result<Self>;
90
}
91

92
/// Implemented on types which can be extracted from a mutable zend object.
93
///
94
/// If `Self` does not require the object to be mutable, it should implement
95
/// [`FromZendObject`] instead, as this trait is generically implemented for
96
/// any types that also implement [`FromZendObject`].
97
pub trait FromZendObjectMut<'a>: Sized {
98
    /// Extracts `Self` from the source `ZendObject`.
99
    ///
100
    /// # Errors
101
    ///
102
    /// If the conversion fails, an [`Error`] is returned.
103
    ///
104
    /// [`Error`]: crate::error::Error
105
    // TODO: Expand on error information
106
    fn from_zend_object_mut(obj: &'a mut ZendObject) -> Result<Self>;
107
}
108

109
impl<'a, T> FromZendObjectMut<'a> for T
110
where
111
    T: FromZendObject<'a>,
112
{
113
    #[inline]
114
    fn from_zend_object_mut(obj: &'a mut ZendObject) -> Result<Self> {
×
115
        Self::from_zend_object(obj)
×
116
    }
117
}
118

119
/// Implemented on types which can be converted into a Zend object. It is up to
120
/// the implementation to determine the type of object which is produced.
121
pub trait IntoZendObject {
122
    /// Attempts to convert `self` into a Zend object.
123
    ///
124
    /// # Errors
125
    ///
126
    /// If the conversion fails, an [`Error`] is returned.
127
    ///
128
    /// [`Error`]: crate::error::Error
129
    // TODO: Expand on error information
130
    fn into_zend_object(self) -> Result<ZBox<ZendObject>>;
131
}
132

133
/// Provides implementations for converting Rust primitive types into PHP zvals.
134
/// Alternative to the built-in Rust [`From`] and [`TryFrom`] implementations,
135
/// allowing the caller to specify whether the Zval contents will persist
136
/// between requests.
137
///
138
/// [`TryFrom`]: std::convert::TryFrom
139
pub trait IntoZval: Sized {
140
    /// The corresponding type of the implemented value in PHP.
141
    const TYPE: DataType;
142

143
    /// Whether converting into a [`Zval`] may result in null.
144
    const NULLABLE: bool;
145

146
    /// Converts a Rust primitive type into a Zval. Returns a result containing
147
    /// the Zval if successful.
148
    ///
149
    /// # Parameters
150
    ///
151
    /// * `persistent` - Whether the contents of the Zval will persist between
152
    ///   requests.
153
    ///
154
    /// # Errors
155
    ///
156
    /// If the conversion fails, an [`Error`] is returned.
157
    ///
158
    /// [`Error`]: crate::error::Error
159
    // TODO: Expand on error information
160
    fn into_zval(self, persistent: bool) -> Result<Zval> {
123✔
161
        let mut zval = Zval::new();
246✔
162
        self.set_zval(&mut zval, persistent)?;
492✔
163
        Ok(zval)
123✔
164
    }
165

166
    /// Sets the content of a pre-existing zval. Returns a result containing
167
    /// nothing if setting the content was successful.
168
    ///
169
    /// # Parameters
170
    ///
171
    /// * `zv` - The Zval to set the content of.
172
    /// * `persistent` - Whether the contents of the Zval will persist between
173
    ///   requests.
174
    ///
175
    /// # Errors
176
    ///
177
    /// If setting the content fails, an [`Error`] is returned.
178
    ///
179
    /// [`Error`]: crate::error::Error
180
    // TODO: Expand on error information
181
    fn set_zval(self, zv: &mut Zval, persistent: bool) -> Result<()>;
182
}
183

184
impl IntoZval for () {
185
    const TYPE: DataType = DataType::Void;
186
    const NULLABLE: bool = true;
187

188
    #[inline]
189
    fn set_zval(self, zv: &mut Zval, _: bool) -> Result<()> {
×
190
        zv.set_null();
×
191
        Ok(())
×
192
    }
193
}
194

195
impl<T> IntoZval for Option<T>
196
where
197
    T: IntoZval,
198
{
199
    const TYPE: DataType = T::TYPE;
200
    const NULLABLE: bool = true;
201

202
    #[inline]
203
    fn set_zval(self, zv: &mut Zval, persistent: bool) -> Result<()> {
×
204
        if let Some(val) = self {
×
205
            val.set_zval(zv, persistent)
×
206
        } else {
207
            zv.set_null();
×
208
            Ok(())
×
209
        }
210
    }
211
}
212

213
impl<T, E> IntoZval for std::result::Result<T, E>
214
where
215
    T: IntoZval,
216
    E: Into<PhpException>,
217
{
218
    const TYPE: DataType = T::TYPE;
219
    const NULLABLE: bool = T::NULLABLE;
220

221
    fn set_zval(self, zv: &mut Zval, persistent: bool) -> Result<()> {
×
222
        match self {
×
223
            Ok(val) => val.set_zval(zv, persistent),
×
224
            Err(e) => {
×
225
                let ex: PhpException = e.into();
×
226
                ex.throw()
×
227
            }
228
        }
229
    }
230
}
231

232
/// An object-safe version of the [`IntoZval`] trait.
233
///
234
/// This trait is automatically implemented on any type that implements both
235
/// [`IntoZval`] and [`Clone`]. You avoid implementing this trait directly,
236
/// rather implement these two other traits.
237
pub trait IntoZvalDyn {
238
    /// Converts a Rust primitive type into a Zval. Returns a result containing
239
    /// the Zval if successful. `self` is cloned before being converted into
240
    /// a zval.
241
    ///
242
    /// # Parameters
243
    ///
244
    /// * `persistent` - Whether the contents of the Zval will persist between
245
    ///   requests.
246
    ///
247
    /// # Errors
248
    ///
249
    /// If the conversion fails, an [`Error`] is returned.
250
    ///
251
    /// [`Error`]: crate::error::Error
252
    fn as_zval(&self, persistent: bool) -> Result<Zval>;
253

254
    /// Returns the PHP type of the type.
255
    fn get_type(&self) -> DataType;
256

257
    /// Returns the PHP stub representation of this value.
258
    ///
259
    /// This is used when generating PHP stub files for IDE autocompletion.
260
    /// The returned string should be a valid PHP literal.
261
    fn stub_value(&self) -> String {
1✔
262
        // Default implementation - convert to zval and format
263
        match self.as_zval(false) {
1✔
264
            Ok(zval) => zval_to_stub(&zval),
3✔
NEW
265
            Err(_) => "null".to_string(),
×
266
        }
267
    }
268
}
269

270
/// Converts a Zval to its PHP stub representation.
271
#[must_use]
272
#[allow(clippy::match_same_arms)]
273
pub fn zval_to_stub(zval: &Zval) -> String {
2✔
274
    use crate::flags::DataType;
275

276
    match zval.get_type() {
2✔
NEW
277
        DataType::Null | DataType::Undef => "null".to_string(),
×
NEW
278
        DataType::True => "true".to_string(),
×
NEW
279
        DataType::False => "false".to_string(),
×
280
        DataType::Long => zval
2✔
281
            .long()
282
            .map_or_else(|| "null".to_string(), |v| v.to_string()),
6✔
NEW
283
        DataType::Double => zval
×
284
            .double()
NEW
285
            .map_or_else(|| "null".to_string(), |v| v.to_string()),
×
286
        DataType::String => {
NEW
287
            if let Some(s) = zval.str() {
×
NEW
288
                let escaped = s
×
289
                    .replace('\\', "\\\\")
290
                    .replace('\'', "\\'")
291
                    .replace('\n', "\\n")
292
                    .replace('\r', "\\r")
293
                    .replace('\t', "\\t");
NEW
294
                format!("'{escaped}'")
×
295
            } else {
NEW
296
                "null".to_string()
×
297
            }
298
        }
299
        DataType::Array => {
300
            #[allow(clippy::explicit_iter_loop)]
NEW
301
            if let Some(arr) = zval.array() {
×
302
                // Check if array has sequential numeric keys starting from 0
NEW
303
                let is_sequential = arr.iter().enumerate().all(|(i, (key, _))| {
×
NEW
304
                    matches!(key, crate::types::ArrayKey::Long(idx) if i64::try_from(i).is_ok_and(|ii| idx == ii))
×
305
                });
306

NEW
307
                let mut parts = Vec::new();
×
NEW
308
                for (key, val) in arr.iter() {
×
NEW
309
                    let val_str = zval_to_stub(val);
×
NEW
310
                    if is_sequential {
×
NEW
311
                        parts.push(val_str);
×
312
                    } else {
NEW
313
                        match key {
×
NEW
314
                            crate::types::ArrayKey::Long(idx) => {
×
NEW
315
                                parts.push(format!("{idx} => {val_str}"));
×
316
                            }
NEW
317
                            crate::types::ArrayKey::String(key) => {
×
NEW
318
                                let key_escaped = key.replace('\\', "\\\\").replace('\'', "\\'");
×
NEW
319
                                parts.push(format!("'{key_escaped}' => {val_str}"));
×
320
                            }
NEW
321
                            crate::types::ArrayKey::Str(key) => {
×
NEW
322
                                let key_escaped = key.replace('\\', "\\\\").replace('\'', "\\'");
×
NEW
323
                                parts.push(format!("'{key_escaped}' => {val_str}"));
×
324
                            }
325
                        }
326
                    }
327
                }
NEW
328
                format!("[{}]", parts.join(", "))
×
329
            } else {
NEW
330
                "[]".to_string()
×
331
            }
332
        }
NEW
333
        _ => "null".to_string(),
×
334
    }
335
}
336

337
impl<T: IntoZval + Clone> IntoZvalDyn for T {
338
    fn as_zval(&self, persistent: bool) -> Result<Zval> {
1✔
339
        self.clone().into_zval(persistent)
4✔
340
    }
341

342
    fn get_type(&self) -> DataType {
×
343
        Self::TYPE
×
344
    }
345
}
346

347
impl IntoZvalDyn for Zval {
348
    fn as_zval(&self, _persistent: bool) -> Result<Zval> {
×
349
        Ok(self.shallow_clone())
×
350
    }
351

352
    fn get_type(&self) -> DataType {
×
353
        self.get_type()
×
354
    }
355
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc