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

extphprs / ext-php-rs / 25379163335

05 May 2026 01:25PM UTC coverage: 72.479% (+6.2%) from 66.241%
25379163335

Pull #734

github

web-flow
Merge 889e3cde4 into 0912e7c24
Pull Request #734: feat!: PHP 8 union, intersection, DNF, and class-union type hints

2871 of 3074 new or added lines in 21 files covered. (93.4%)

3 existing lines in 2 files now uncovered.

11514 of 15886 relevant lines covered (72.48%)

33.34 hits per line

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

37.39
/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::{PhpType, 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
    /// Returns the full PHP type expression for this value, used to register
18
    /// arguments on `#[php_function]` and `#[php_impl]` signatures.
19
    ///
20
    /// The default wraps [`Self::TYPE`] as [`PhpType::Simple`]; compound types
21
    /// such as [`crate::types::PhpUnion`]-derived enums override this to
22
    /// return the actual union shape.
23
    #[must_use]
24
    fn php_type() -> PhpType {
65✔
25
        PhpType::Simple(Self::TYPE)
65✔
26
    }
65✔
27

28
    /// Attempts to retrieve an instance of `Self` from a reference to a
29
    /// [`Zval`].
30
    ///
31
    /// # Parameters
32
    ///
33
    /// * `zval` - Zval to get value from.
34
    fn from_zval(zval: &'a Zval) -> Option<Self>;
35
}
36

37
impl<'a, T> FromZval<'a> for Option<T>
38
where
39
    T: FromZval<'a>,
40
{
41
    const TYPE: DataType = T::TYPE;
42

NEW
43
    fn php_type() -> PhpType {
×
NEW
44
        T::php_type()
×
NEW
45
    }
×
46

47
    fn from_zval(zval: &'a Zval) -> Option<Self> {
×
48
        Some(T::from_zval(zval))
×
49
    }
×
50
}
51

52
/// Allows mutable zvals to be converted into Rust types in a fallible way.
53
///
54
/// If `Self` does not require the zval to be mutable to be extracted, you
55
/// should implement [`FromZval`] instead, as this trait is generically
56
/// implemented for any type that implements [`FromZval`].
57
pub trait FromZvalMut<'a>: Sized {
58
    /// The corresponding type of the implemented value in PHP.
59
    const TYPE: DataType;
60

61
    /// Returns the full PHP type expression for this value, used to register
62
    /// arguments on `#[php_function]` and `#[php_impl]` signatures.
63
    ///
64
    /// The default wraps [`Self::TYPE`] as [`PhpType::Simple`]; types
65
    /// implementing [`crate::types::PhpUnion`] override this to return the
66
    /// actual union shape.
67
    #[must_use]
68
    fn php_type() -> PhpType {
15✔
69
        PhpType::Simple(Self::TYPE)
15✔
70
    }
15✔
71

72
    /// Attempts to retrieve an instance of `Self` from a mutable reference to a
73
    /// [`Zval`].
74
    ///
75
    /// # Parameters
76
    ///
77
    /// * `zval` - Zval to get value from.
78
    fn from_zval_mut(zval: &'a mut Zval) -> Option<Self>;
79
}
80

81
impl<'a, T> FromZvalMut<'a> for T
82
where
83
    T: FromZval<'a>,
84
{
85
    const TYPE: DataType = <T as FromZval>::TYPE;
86

87
    #[inline]
88
    fn php_type() -> PhpType {
66✔
89
        <T as FromZval>::php_type()
66✔
90
    }
66✔
91

92
    #[inline]
93
    fn from_zval_mut(zval: &'a mut Zval) -> Option<Self> {
13✔
94
        Self::from_zval(zval)
13✔
95
    }
13✔
96
}
97

98
/// `FromZendObject` is implemented by types which can be extracted from a Zend
99
/// object.
100
///
101
/// Normal usage is through the helper method `ZendObject::extract`:
102
///
103
/// ```rust,ignore
104
/// let obj: ZendObject = ...;
105
/// let repr: String = obj.extract();
106
/// let props: HashMap = obj.extract();
107
/// ```
108
///
109
/// Should be functionally equivalent to casting an object to another compatible
110
/// type.
111
pub trait FromZendObject<'a>: Sized {
112
    /// Extracts `Self` from the source `ZendObject`.
113
    ///
114
    /// # Errors
115
    ///
116
    /// If the conversion fails, an [`Error`] is returned.
117
    ///
118
    /// [`Error`]: crate::error::Error
119
    // TODO: Expand on error information
120
    fn from_zend_object(obj: &'a ZendObject) -> Result<Self>;
121
}
122

123
/// Implemented on types which can be extracted from a mutable zend object.
124
///
125
/// If `Self` does not require the object to be mutable, it should implement
126
/// [`FromZendObject`] instead, as this trait is generically implemented for
127
/// any types that also implement [`FromZendObject`].
128
pub trait FromZendObjectMut<'a>: Sized {
129
    /// Extracts `Self` from the source `ZendObject`.
130
    ///
131
    /// # Errors
132
    ///
133
    /// If the conversion fails, an [`Error`] is returned.
134
    ///
135
    /// [`Error`]: crate::error::Error
136
    // TODO: Expand on error information
137
    fn from_zend_object_mut(obj: &'a mut ZendObject) -> Result<Self>;
138
}
139

140
impl<'a, T> FromZendObjectMut<'a> for T
141
where
142
    T: FromZendObject<'a>,
143
{
144
    #[inline]
145
    fn from_zend_object_mut(obj: &'a mut ZendObject) -> Result<Self> {
×
146
        Self::from_zend_object(obj)
×
147
    }
×
148
}
149

150
/// Implemented on types which can be converted into a Zend object. It is up to
151
/// the implementation to determine the type of object which is produced.
152
pub trait IntoZendObject {
153
    /// Attempts to convert `self` into a Zend object.
154
    ///
155
    /// # Errors
156
    ///
157
    /// If the conversion fails, an [`Error`] is returned.
158
    ///
159
    /// [`Error`]: crate::error::Error
160
    // TODO: Expand on error information
161
    fn into_zend_object(self) -> Result<ZBox<ZendObject>>;
162
}
163

164
/// Provides implementations for converting Rust primitive types into PHP zvals.
165
/// Alternative to the built-in Rust [`From`] and [`TryFrom`] implementations,
166
/// allowing the caller to specify whether the Zval contents will persist
167
/// between requests.
168
///
169
/// [`TryFrom`]: std::convert::TryFrom
170
pub trait IntoZval: Sized {
171
    /// The corresponding type of the implemented value in PHP.
172
    const TYPE: DataType;
173

174
    /// Whether converting into a [`Zval`] may result in null.
175
    const NULLABLE: bool;
176

177
    /// Returns the full PHP type expression for this value, used to register
178
    /// return types on `#[php_function]` and `#[php_impl]` signatures.
179
    ///
180
    /// The default wraps [`Self::TYPE`] as [`PhpType::Simple`]; types
181
    /// implementing [`crate::types::PhpUnion`] override this to return the
182
    /// actual union shape.
183
    #[must_use]
184
    fn php_type() -> PhpType {
117✔
185
        PhpType::Simple(Self::TYPE)
117✔
186
    }
117✔
187

188
    /// Converts a Rust primitive type into a Zval. Returns a result containing
189
    /// the Zval if successful.
190
    ///
191
    /// # Parameters
192
    ///
193
    /// * `persistent` - Whether the contents of the Zval will persist between
194
    ///   requests.
195
    ///
196
    /// # Errors
197
    ///
198
    /// If the conversion fails, an [`Error`] is returned.
199
    ///
200
    /// [`Error`]: crate::error::Error
201
    // TODO: Expand on error information
202
    fn into_zval(self, persistent: bool) -> Result<Zval> {
216✔
203
        let mut zval = Zval::new();
216✔
204
        self.set_zval(&mut zval, persistent)?;
216✔
205
        Ok(zval)
216✔
206
    }
216✔
207

208
    /// Sets the content of a pre-existing zval. Returns a result containing
209
    /// nothing if setting the content was successful.
210
    ///
211
    /// # Parameters
212
    ///
213
    /// * `zv` - The Zval to set the content of.
214
    /// * `persistent` - Whether the contents of the Zval will persist between
215
    ///   requests.
216
    ///
217
    /// # Errors
218
    ///
219
    /// If setting the content fails, an [`Error`] is returned.
220
    ///
221
    /// [`Error`]: crate::error::Error
222
    // TODO: Expand on error information
223
    fn set_zval(self, zv: &mut Zval, persistent: bool) -> Result<()>;
224
}
225

226
impl IntoZval for () {
227
    const TYPE: DataType = DataType::Void;
228
    const NULLABLE: bool = true;
229

230
    #[inline]
231
    fn set_zval(self, zv: &mut Zval, _: bool) -> Result<()> {
5✔
232
        zv.set_null();
5✔
233
        Ok(())
5✔
234
    }
5✔
235
}
236

237
impl<T> IntoZval for Option<T>
238
where
239
    T: IntoZval,
240
{
241
    const TYPE: DataType = T::TYPE;
242
    const NULLABLE: bool = true;
243

244
    fn php_type() -> PhpType {
5✔
245
        T::php_type()
5✔
246
    }
5✔
247

248
    #[inline]
249
    fn set_zval(self, zv: &mut Zval, persistent: bool) -> Result<()> {
×
250
        if let Some(val) = self {
×
251
            val.set_zval(zv, persistent)
×
252
        } else {
253
            zv.set_null();
×
254
            Ok(())
×
255
        }
256
    }
×
257
}
258

259
impl<T, E> IntoZval for std::result::Result<T, E>
260
where
261
    T: IntoZval,
262
    E: Into<PhpException>,
263
{
264
    const TYPE: DataType = T::TYPE;
265
    const NULLABLE: bool = T::NULLABLE;
266

267
    fn php_type() -> PhpType {
5✔
268
        T::php_type()
5✔
269
    }
5✔
270

271
    fn set_zval(self, zv: &mut Zval, persistent: bool) -> Result<()> {
×
272
        match self {
×
273
            Ok(val) => val.set_zval(zv, persistent),
×
274
            Err(e) => {
×
275
                let ex: PhpException = e.into();
×
276
                ex.throw()
×
277
            }
278
        }
279
    }
×
280
}
281

282
/// An object-safe version of the [`IntoZval`] trait.
283
///
284
/// This trait is automatically implemented on any type that implements both
285
/// [`IntoZval`] and [`Clone`]. You avoid implementing this trait directly,
286
/// rather implement these two other traits.
287
pub trait IntoZvalDyn {
288
    /// Converts a Rust primitive type into a Zval. Returns a result containing
289
    /// the Zval if successful. `self` is cloned before being converted into
290
    /// a zval.
291
    ///
292
    /// # Parameters
293
    ///
294
    /// * `persistent` - Whether the contents of the Zval will persist between
295
    ///   requests.
296
    ///
297
    /// # Errors
298
    ///
299
    /// If the conversion fails, an [`Error`] is returned.
300
    ///
301
    /// [`Error`]: crate::error::Error
302
    fn as_zval(&self, persistent: bool) -> Result<Zval>;
303

304
    /// Returns the PHP type of the type.
305
    fn get_type(&self) -> DataType;
306

307
    /// Returns the PHP stub representation of this value.
308
    ///
309
    /// This is used when generating PHP stub files for IDE autocompletion.
310
    /// The returned string should be a valid PHP literal.
311
    fn stub_value(&self) -> String {
1✔
312
        // Default implementation - convert to zval and format
313
        match self.as_zval(false) {
1✔
314
            Ok(zval) => zval_to_stub(&zval),
1✔
315
            Err(_) => "null".to_string(),
×
316
        }
317
    }
1✔
318
}
319

320
/// Converts a Zval to its PHP stub representation.
321
#[must_use]
322
#[allow(clippy::match_same_arms)]
323
pub fn zval_to_stub(zval: &Zval) -> String {
2✔
324
    use crate::flags::DataType;
325

326
    match zval.get_type() {
2✔
327
        DataType::Null | DataType::Undef => "null".to_string(),
×
328
        DataType::True => "true".to_string(),
×
329
        DataType::False => "false".to_string(),
×
330
        DataType::Long => zval
2✔
331
            .long()
2✔
332
            .map_or_else(|| "null".to_string(), |v| v.to_string()),
2✔
333
        DataType::Double => zval
×
334
            .double()
×
335
            .map_or_else(|| "null".to_string(), |v| v.to_string()),
×
336
        DataType::String => {
337
            if let Some(s) = zval.str() {
×
338
                let escaped = s
×
339
                    .replace('\\', "\\\\")
×
340
                    .replace('\'', "\\'")
×
341
                    .replace('\n', "\\n")
×
342
                    .replace('\r', "\\r")
×
343
                    .replace('\t', "\\t");
×
344
                format!("'{escaped}'")
×
345
            } else {
346
                "null".to_string()
×
347
            }
348
        }
349
        DataType::Array => {
350
            #[allow(clippy::explicit_iter_loop)]
351
            if let Some(arr) = zval.array() {
×
352
                // Check if array has sequential numeric keys starting from 0
353
                let is_sequential = arr.iter().enumerate().all(|(i, (key, _))| {
×
354
                    matches!(key, crate::types::ArrayKey::Long(idx) if i64::try_from(i).is_ok_and(|ii| idx == ii))
×
355
                });
×
356

357
                let mut parts = Vec::new();
×
358
                for (key, val) in arr.iter() {
×
359
                    let val_str = zval_to_stub(val);
×
360
                    if is_sequential {
×
361
                        parts.push(val_str);
×
362
                    } else {
×
363
                        match key {
×
364
                            crate::types::ArrayKey::Long(idx) => {
×
365
                                parts.push(format!("{idx} => {val_str}"));
×
366
                            }
×
367
                            crate::types::ArrayKey::String(key) => {
×
368
                                let key_escaped = key.replace('\\', "\\\\").replace('\'', "\\'");
×
369
                                parts.push(format!("'{key_escaped}' => {val_str}"));
×
370
                            }
×
371
                            crate::types::ArrayKey::Str(key) => {
×
372
                                let key_escaped = key.replace('\\', "\\\\").replace('\'', "\\'");
×
373
                                parts.push(format!("'{key_escaped}' => {val_str}"));
×
374
                            }
×
375
                        }
376
                    }
377
                }
378
                format!("[{}]", parts.join(", "))
×
379
            } else {
380
                "[]".to_string()
×
381
            }
382
        }
383
        _ => "null".to_string(),
×
384
    }
385
}
2✔
386

387
impl<T: IntoZval + Clone> IntoZvalDyn for T {
388
    fn as_zval(&self, persistent: bool) -> Result<Zval> {
1✔
389
        self.clone().into_zval(persistent)
1✔
390
    }
1✔
391

392
    fn get_type(&self) -> DataType {
×
393
        Self::TYPE
×
394
    }
×
395
}
396

397
impl IntoZvalDyn for Zval {
398
    fn as_zval(&self, _persistent: bool) -> Result<Zval> {
×
399
        Ok(self.shallow_clone())
×
400
    }
×
401

402
    fn get_type(&self) -> DataType {
×
403
        self.get_type()
×
404
    }
×
405
}
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