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

davidcole1340 / ext-php-rs / 14501981872

16 Apr 2025 08:30PM UTC coverage: 14.129% (+0.7%) from 13.479%
14501981872

push

github

web-flow
style(clippy): apply pedantic rules

Refs: #418

41 of 345 new or added lines in 46 files covered. (11.88%)

48 existing lines in 25 files now uncovered.

553 of 3914 relevant lines covered (14.13%)

1.3 hits per line

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

10.16
/src/types/object.rs
1
//! Represents an object in PHP. Allows for overriding the internal object used
2
//! by classes, allowing users to store Rust data inside a PHP object.
3

4
use std::{convert::TryInto, fmt::Debug, os::raw::c_char, ptr};
5

6
use crate::{
7
    boxed::{ZBox, ZBoxable},
8
    class::RegisteredClass,
9
    convert::{FromZendObject, FromZval, FromZvalMut, IntoZval, IntoZvalDyn},
10
    error::{Error, Result},
11
    ffi::{
12
        ext_php_rs_zend_object_release, object_properties_init, zend_call_known_function,
13
        zend_function, zend_hash_str_find_ptr_lc, zend_object, zend_objects_new, HashTable,
14
        ZEND_ISEMPTY, ZEND_PROPERTY_EXISTS, ZEND_PROPERTY_ISSET,
15
    },
16
    flags::DataType,
17
    rc::PhpRc,
18
    types::{ZendClassObject, ZendStr, Zval},
19
    zend::{ce, ClassEntry, ExecutorGlobals, ZendObjectHandlers},
20
};
21

22
/// A PHP object.
23
///
24
/// This type does not maintain any information about its type, for example,
25
/// classes with have associated Rust structs cannot be accessed through this
26
/// type. [`ZendClassObject`] is used for this purpose, and you can convert
27
/// between the two.
28
pub type ZendObject = zend_object;
29

30
impl ZendObject {
31
    /// Creates a new [`ZendObject`], returned inside an [`ZBox<ZendObject>`]
32
    /// wrapper.
33
    ///
34
    /// # Parameters
35
    ///
36
    /// * `ce` - The type of class the new object should be an instance of.
37
    ///
38
    /// # Panics
39
    ///
40
    /// Panics when allocating memory for the new object fails.
41
    #[must_use]
UNCOV
42
    pub fn new(ce: &ClassEntry) -> ZBox<Self> {
×
43
        // SAFETY: Using emalloc to allocate memory inside Zend arena. Casting `ce` to
44
        // `*mut` is valid as the function will not mutate `ce`.
45
        unsafe {
46
            let ptr = match ce.__bindgen_anon_2.create_object {
×
47
                None => {
NEW
48
                    let ptr = zend_objects_new(ptr::from_ref(ce).cast_mut());
×
NEW
49
                    assert!(!ptr.is_null(), "Failed to allocate memory for Zend object");
×
50

NEW
51
                    object_properties_init(ptr, ptr::from_ref(ce).cast_mut());
×
UNCOV
52
                    ptr
×
53
                }
NEW
54
                Some(v) => v(ptr::from_ref(ce).cast_mut()),
×
55
            };
56

57
            ZBox::from_raw(
58
                ptr.as_mut()
×
59
                    .expect("Failed to allocate memory for Zend object"),
×
60
            )
61
        }
62
    }
63

64
    /// Creates a new `stdClass` instance, returned inside an
65
    /// [`ZBox<ZendObject>`] wrapper.
66
    ///
67
    /// # Panics
68
    ///
69
    /// Panics if allocating memory for the object fails, or if the `stdClass`
70
    /// class entry has not been registered with PHP yet.
71
    ///
72
    /// # Example
73
    ///
74
    /// ```no_run
75
    /// use ext_php_rs::types::ZendObject;
76
    ///
77
    /// let mut obj = ZendObject::new_stdclass();
78
    ///
79
    /// obj.set_property("hello", "world");
80
    /// ```
81
    #[must_use]
UNCOV
82
    pub fn new_stdclass() -> ZBox<Self> {
×
83
        // SAFETY: This will be `NULL` until it is initialized. `as_ref()` checks for
84
        // null, so we can panic if it's null.
85
        Self::new(ce::stdclass())
×
86
    }
87

88
    /// Converts a class object into an owned [`ZendObject`]. This removes any
89
    /// possibility of accessing the underlying attached Rust struct.
90
    #[must_use]
91
    pub fn from_class_object<T: RegisteredClass>(obj: ZBox<ZendClassObject<T>>) -> ZBox<Self> {
×
92
        let this = obj.into_raw();
×
93
        // SAFETY: Consumed box must produce a well-aligned non-null pointer.
94
        unsafe { ZBox::from_raw(this.get_mut_zend_obj()) }
×
95
    }
96

97
    /// Returns the [`ClassEntry`] associated with this object.
98
    ///
99
    /// # Panics
100
    ///
101
    /// Panics if the class entry is invalid.
102
    #[must_use]
103
    pub fn get_class_entry(&self) -> &'static ClassEntry {
6✔
104
        // SAFETY: it is OK to panic here since PHP would segfault anyway
105
        // when encountering an object with no class entry.
106
        unsafe { self.ce.as_ref() }.expect("Could not retrieve class entry.")
6✔
107
    }
108

109
    /// Attempts to retrieve the class name of the object.
110
    ///
111
    /// # Errors
112
    ///
113
    /// * `Error::InvalidScope` - If the object handlers or the class name
114
    ///   cannot be retrieved.
115
    pub fn get_class_name(&self) -> Result<String> {
1✔
116
        unsafe {
117
            self.handlers()?
1✔
118
                .get_class_name
119
                .and_then(|f| f(self).as_ref())
2✔
120
                .ok_or(Error::InvalidScope)
121
                .and_then(TryInto::try_into)
122
        }
123
    }
124

125
    /// Returns whether this object is an instance of the given [`ClassEntry`].
126
    ///
127
    /// This method checks the class and interface inheritance chain.
128
    ///
129
    /// # Panics
130
    ///
131
    /// Panics if the class entry is invalid.
132
    #[must_use]
133
    pub fn instance_of(&self, ce: &ClassEntry) -> bool {
4✔
134
        self.get_class_entry().instance_of(ce)
4✔
135
    }
136

137
    /// Checks if the given object is an instance of a registered class with
138
    /// Rust type `T`.
139
    ///
140
    /// This method doesn't check the class and interface inheritance chain.
141
    #[must_use]
142
    pub fn is_instance<T: RegisteredClass>(&self) -> bool {
×
NEW
143
        (self.ce.cast_const()).eq(&ptr::from_ref(T::get_metadata().ce()))
×
144
    }
145

146
    /// Returns whether this object is an instance of \Traversable
147
    ///
148
    /// # Panics
149
    ///
150
    /// Panics if the class entry is invalid.
151
    #[must_use]
152
    pub fn is_traversable(&self) -> bool {
4✔
153
        self.instance_of(ce::traversable())
4✔
154
    }
155

156
    /// Tries to call a method on the object.
157
    ///
158
    /// # Returns
159
    ///
160
    /// Returns the return value of the method, or an error if the method
161
    /// could not be found or called.
162
    ///
163
    /// # Errors
164
    ///
165
    /// * `Error::Callable` - If the method could not be found.
166
    /// * If a parameter could not be converted to a zval.
167
    /// * If the parameter count is bigger than `u32::MAX`.
168
    // TODO: Measure this
169
    #[allow(clippy::inline_always)]
170
    #[inline(always)]
171
    pub fn try_call_method(&self, name: &str, params: Vec<&dyn IntoZvalDyn>) -> Result<Zval> {
×
172
        let mut retval = Zval::new();
×
173
        let len = params.len();
×
174
        let params = params
×
175
            .into_iter()
176
            .map(|val| val.as_zval(false))
×
177
            .collect::<Result<Vec<_>>>()?;
178
        let packed = params.into_boxed_slice();
×
179

180
        unsafe {
181
            let res = zend_hash_str_find_ptr_lc(
182
                &(*self.ce).function_table,
×
NEW
183
                name.as_ptr().cast::<c_char>(),
×
184
                name.len(),
×
185
            )
186
            .cast::<zend_function>();
187

188
            if res.is_null() {
×
189
                return Err(Error::Callable);
×
190
            }
191

192
            zend_call_known_function(
193
                res,
×
NEW
194
                ptr::from_ref(self).cast_mut(),
×
195
                self.ce,
×
196
                &mut retval,
×
NEW
197
                len.try_into()?,
×
NEW
198
                packed.as_ptr().cast_mut(),
×
UNCOV
199
                std::ptr::null_mut(),
×
200
            );
201
        };
202

203
        Ok(retval)
×
204
    }
205

206
    /// Attempts to read a property from the Object. Returns a result containing
207
    /// the value of the property if it exists and can be read, and an
208
    /// [`Error`] otherwise.
209
    ///
210
    /// # Parameters
211
    ///
212
    /// * `name` - The name of the property.
213
    /// * `query` - The type of query to use when attempting to get a property.
214
    ///
215
    /// # Errors
216
    ///
217
    /// * `Error::InvalidScope` - If the object handlers or the properties
218
    ///   cannot be retrieved.
UNCOV
219
    pub fn get_property<'a, T>(&'a self, name: &str) -> Result<T>
×
220
    where
221
        T: FromZval<'a>,
222
    {
223
        if !self.has_property(name, PropertyQuery::Exists)? {
×
224
            return Err(Error::InvalidProperty);
×
225
        }
226

227
        let mut name = ZendStr::new(name, false);
×
228
        let mut rv = Zval::new();
×
229

230
        let zv = unsafe {
231
            self.handlers()?.read_property.ok_or(Error::InvalidScope)?(
232
                self.mut_ptr(),
×
NEW
233
                &mut *name,
×
234
                1,
235
                std::ptr::null_mut(),
×
236
                &mut rv,
×
237
            )
238
            .as_ref()
239
        }
240
        .ok_or(Error::InvalidScope)?;
×
241

242
        T::from_zval(zv).ok_or_else(|| Error::ZvalConversion(zv.get_type()))
×
243
    }
244

245
    /// Attempts to set a property on the object.
246
    ///
247
    /// # Parameters
248
    ///
249
    /// * `name` - The name of the property.
250
    /// * `value` - The value to set the property to.
251
    ///
252
    /// # Errors
253
    ///
254
    /// * `Error::InvalidScope` - If the object handlers or the properties
255
    ///   cannot be retrieved.
256
    pub fn set_property(&mut self, name: &str, value: impl IntoZval) -> Result<()> {
×
257
        let mut name = ZendStr::new(name, false);
×
258
        let mut value = value.into_zval(false)?;
×
259

260
        unsafe {
261
            self.handlers()?.write_property.ok_or(Error::InvalidScope)?(
262
                self,
×
NEW
263
                &mut *name,
×
264
                &mut value,
×
265
                std::ptr::null_mut(),
×
266
            )
267
            .as_ref()
268
        }
269
        .ok_or(Error::InvalidScope)?;
×
270
        Ok(())
×
271
    }
272

273
    /// Checks if a property exists on an object. Takes a property name and
274
    /// query parameter, which defines what classifies if a property exists
275
    /// or not. See [`PropertyQuery`] for more information.
276
    ///
277
    /// # Parameters
278
    ///
279
    /// * `name` - The name of the property.
280
    /// * `query` - The 'query' to classify if a property exists.
281
    ///
282
    /// # Errors
283
    ///
284
    /// * `Error::InvalidScope` - If the object handlers or the properties
285
    ///   cannot be retrieved.
286
    pub fn has_property(&self, name: &str, query: PropertyQuery) -> Result<bool> {
×
287
        let mut name = ZendStr::new(name, false);
×
288

289
        Ok(unsafe {
×
290
            self.handlers()?.has_property.ok_or(Error::InvalidScope)?(
×
291
                self.mut_ptr(),
×
NEW
292
                &mut *name,
×
293
                query as _,
×
294
                std::ptr::null_mut(),
×
295
            )
296
        } > 0)
297
    }
298

299
    /// Attempts to retrieve the properties of the object. Returned inside a
300
    /// Zend Hashtable.
301
    ///
302
    /// # Errors
303
    ///
304
    /// * `Error::InvalidScope` - If the object handlers or the properties
305
    ///   cannot be retrieved.
UNCOV
306
    pub fn get_properties(&self) -> Result<&HashTable> {
×
307
        unsafe {
308
            self.handlers()?
×
309
                .get_properties
310
                .and_then(|props| props(self.mut_ptr()).as_ref())
×
311
                .ok_or(Error::InvalidScope)
312
        }
313
    }
314

315
    /// Extracts some type from a Zend object.
316
    ///
317
    /// This is a wrapper function around `FromZendObject::extract()`.
318
    ///
319
    /// # Errors
320
    ///
321
    /// Returns an error if the conversion fails.
UNCOV
322
    pub fn extract<'a, T>(&'a self) -> Result<T>
×
323
    where
324
        T: FromZendObject<'a>,
325
    {
326
        T::from_zend_object(self)
×
327
    }
328

329
    /// Returns an unique identifier for the object.
330
    ///
331
    /// The id is guaranteed to be unique for the lifetime of the object.
332
    /// Once the object is destroyed, it may be reused for other objects.
333
    /// This is equivalent to calling the [`spl_object_id`] PHP function.
334
    ///
335
    /// [`spl_object_id`]: https://www.php.net/manual/function.spl-object-id
336
    #[inline]
337
    #[must_use]
338
    pub fn get_id(&self) -> u32 {
×
339
        self.handle
×
340
    }
341

342
    /// Computes an unique hash for the object.
343
    ///
344
    /// The hash is guaranteed to be unique for the lifetime of the object.
345
    /// Once the object is destroyed, it may be reused for other objects.
346
    /// This is equivalent to calling the [`spl_object_hash`] PHP function.
347
    ///
348
    /// [`spl_object_hash`]: https://www.php.net/manual/function.spl-object-hash.php
349
    #[must_use]
350
    pub fn hash(&self) -> String {
×
351
        format!("{:016x}0000000000000000", self.handle)
×
352
    }
353

354
    /// Attempts to retrieve a reference to the object handlers.
355
    #[inline]
356
    unsafe fn handlers(&self) -> Result<&ZendObjectHandlers> {
1✔
357
        self.handlers.as_ref().ok_or(Error::InvalidScope)
1✔
358
    }
359

360
    /// Returns a mutable pointer to `self`, regardless of the type of
361
    /// reference. Only to be used in situations where a C function requires
362
    /// a mutable pointer but does not modify the underlying data.
363
    #[inline]
364
    fn mut_ptr(&self) -> *mut Self {
×
NEW
365
        ptr::from_ref(self).cast_mut()
×
366
    }
367
}
368

369
unsafe impl ZBoxable for ZendObject {
370
    fn free(&mut self) {
1✔
371
        unsafe { ext_php_rs_zend_object_release(self) }
1✔
372
    }
373
}
374

375
impl Debug for ZendObject {
376
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
×
377
        let mut dbg = f.debug_struct(
×
378
            self.get_class_name()
×
379
                .unwrap_or_else(|_| "ZendObject".to_string())
×
380
                .as_str(),
×
381
        );
382

383
        if let Ok(props) = self.get_properties() {
×
NEW
384
            for (key, val) in props {
×
385
                dbg.field(key.to_string().as_str(), val);
386
            }
387
        }
388

389
        dbg.finish()
×
390
    }
391
}
392

393
impl<'a> FromZval<'a> for &'a ZendObject {
394
    const TYPE: DataType = DataType::Object(None);
395

396
    fn from_zval(zval: &'a Zval) -> Option<Self> {
×
397
        zval.object()
×
398
    }
399
}
400

401
impl<'a> FromZvalMut<'a> for &'a mut ZendObject {
402
    const TYPE: DataType = DataType::Object(None);
403

404
    fn from_zval_mut(zval: &'a mut Zval) -> Option<Self> {
×
405
        zval.object_mut()
×
406
    }
407
}
408

409
impl IntoZval for ZBox<ZendObject> {
410
    const TYPE: DataType = DataType::Object(None);
411
    const NULLABLE: bool = false;
412

413
    #[inline]
414
    fn set_zval(mut self, zv: &mut Zval, _: bool) -> Result<()> {
×
415
        // We must decrement the refcounter on the object before inserting into the
416
        // zval, as the reference counter will be incremented on add.
417
        // NOTE(david): again is this needed, we increment in `set_object`.
418
        self.dec_count();
×
419
        zv.set_object(self.into_raw());
×
420
        Ok(())
×
421
    }
422
}
423

424
impl IntoZval for &mut ZendObject {
425
    const TYPE: DataType = DataType::Object(None);
426
    const NULLABLE: bool = false;
427

428
    #[inline]
429
    fn set_zval(self, zv: &mut Zval, _: bool) -> Result<()> {
×
430
        zv.set_object(self);
×
431
        Ok(())
×
432
    }
433
}
434

435
impl FromZendObject<'_> for String {
436
    fn from_zend_object(obj: &ZendObject) -> Result<Self> {
×
437
        let mut ret = Zval::new();
×
438
        unsafe {
439
            zend_call_known_function(
440
                (*obj.ce).__tostring,
×
NEW
441
                ptr::from_ref(obj).cast_mut(),
×
442
                obj.ce,
×
443
                &mut ret,
×
444
                0,
445
                std::ptr::null_mut(),
×
446
                std::ptr::null_mut(),
×
447
            );
448
        }
449

450
        if let Some(err) = ExecutorGlobals::take_exception() {
×
451
            // TODO: become an error
452
            let class_name = obj.get_class_name();
×
453
            panic!(
×
454
                "Uncaught exception during call to {}::__toString(): {:?}",
×
455
                class_name.expect("unable to determine class name"),
×
456
                err
×
457
            );
458
        } else if let Some(output) = ret.extract() {
×
459
            Ok(output)
×
460
        } else {
461
            // TODO: become an error
462
            let class_name = obj.get_class_name();
×
463
            panic!(
×
464
                "{}::__toString() must return a string",
×
465
                class_name.expect("unable to determine class name"),
×
466
            );
467
        }
468
    }
469
}
470

471
impl<T: RegisteredClass> From<ZBox<ZendClassObject<T>>> for ZBox<ZendObject> {
472
    #[inline]
473
    fn from(obj: ZBox<ZendClassObject<T>>) -> Self {
×
474
        ZendObject::from_class_object(obj)
×
475
    }
476
}
477

478
/// Different ways to query if a property exists.
479
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
480
#[repr(u32)]
481
pub enum PropertyQuery {
482
    /// Property exists and is not NULL.
483
    Isset = ZEND_PROPERTY_ISSET,
484
    /// Property is not empty.
485
    NotEmpty = ZEND_ISEMPTY,
486
    /// Property exists.
487
    Exists = ZEND_PROPERTY_EXISTS,
488
}
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