1use std::{ffi::c_void, mem::MaybeUninit, os::raw::c_int, ptr};
2
3use crate::{
4 class::RegisteredClass,
5 exception::PhpResult,
6 ffi::{
7 std_object_handlers, zend_is_true, zend_object_handlers, zend_object_std_dtor,
8 zend_std_get_properties, zend_std_has_property, zend_std_read_property,
9 zend_std_write_property,
10 },
11 flags::ZvalTypeFlags,
12 types::{ZendClassObject, ZendHashTable, ZendObject, ZendStr, Zval},
13};
14
15pub type ZendObjectHandlers = zend_object_handlers;
17
18impl ZendObjectHandlers {
19 #[must_use]
22 pub fn new<T: RegisteredClass>() -> ZendObjectHandlers {
23 let mut this = MaybeUninit::uninit();
24
25 unsafe { Self::init::<T>(&raw mut *this.as_mut_ptr()) };
27
28 unsafe { this.assume_init() }
31 }
32
33 pub unsafe fn init<T: RegisteredClass>(ptr: *mut ZendObjectHandlers) {
49 ptr::copy_nonoverlapping(&raw const std_object_handlers, ptr, 1);
50 let offset = ZendClassObject::<T>::std_offset();
51 (*ptr).offset = offset.try_into().expect("Invalid offset");
52 (*ptr).free_obj = Some(Self::free_obj::<T>);
53 (*ptr).read_property = Some(Self::read_property::<T>);
54 (*ptr).write_property = Some(Self::write_property::<T>);
55 (*ptr).get_properties = Some(Self::get_properties::<T>);
56 (*ptr).has_property = Some(Self::has_property::<T>);
57 }
58
59 unsafe extern "C" fn free_obj<T: RegisteredClass>(object: *mut ZendObject) {
60 let obj = object
61 .as_mut()
62 .and_then(|obj| ZendClassObject::<T>::from_zend_obj_mut(obj))
63 .expect("Invalid object pointer given for `free_obj`");
64
65 ptr::drop_in_place(&raw mut obj.obj);
67
68 zend_object_std_dtor(object);
69 }
70
71 unsafe extern "C" fn read_property<T: RegisteredClass>(
72 object: *mut ZendObject,
73 member: *mut ZendStr,
74 type_: c_int,
75 cache_slot: *mut *mut c_void,
76 rv: *mut Zval,
77 ) -> *mut Zval {
78 #[allow(clippy::inline_always)]
80 #[inline(always)]
81 unsafe fn internal<T: RegisteredClass>(
82 object: *mut ZendObject,
83 member: *mut ZendStr,
84 type_: c_int,
85 cache_slot: *mut *mut c_void,
86 rv: *mut Zval,
87 ) -> PhpResult<*mut Zval> {
88 let obj = object
89 .as_mut()
90 .and_then(|obj| ZendClassObject::<T>::from_zend_obj_mut(obj))
91 .ok_or("Invalid object pointer given")?;
92 let prop_name = member
93 .as_ref()
94 .ok_or("Invalid property name pointer given")?;
95 let self_ = &mut *obj;
96 let props = T::get_metadata().get_properties();
97 let prop = props.get(prop_name.as_str()?);
98
99 let rv_mut = rv.as_mut().ok_or("Invalid return zval given")?;
101 rv_mut.u1.type_info = ZvalTypeFlags::Null.bits();
102
103 Ok(match prop {
104 Some(prop_info) => {
105 prop_info.prop.get(self_, rv_mut)?;
106 rv
107 }
108 None => zend_std_read_property(object, member, type_, cache_slot, rv),
109 })
110 }
111
112 match internal::<T>(object, member, type_, cache_slot, rv) {
113 Ok(rv) => rv,
114 Err(e) => {
115 let _ = e.throw();
116 (*rv).set_null();
117 rv
118 }
119 }
120 }
121
122 unsafe extern "C" fn write_property<T: RegisteredClass>(
123 object: *mut ZendObject,
124 member: *mut ZendStr,
125 value: *mut Zval,
126 cache_slot: *mut *mut c_void,
127 ) -> *mut Zval {
128 #[allow(clippy::inline_always)]
130 #[inline(always)]
131 unsafe fn internal<T: RegisteredClass>(
132 object: *mut ZendObject,
133 member: *mut ZendStr,
134 value: *mut Zval,
135 cache_slot: *mut *mut c_void,
136 ) -> PhpResult<*mut Zval> {
137 let obj = object
138 .as_mut()
139 .and_then(|obj| ZendClassObject::<T>::from_zend_obj_mut(obj))
140 .ok_or("Invalid object pointer given")?;
141 let prop_name = member
142 .as_ref()
143 .ok_or("Invalid property name pointer given")?;
144 let self_ = &mut *obj;
145 let props = T::get_metadata().get_properties();
146 let prop = props.get(prop_name.as_str()?);
147 let value_mut = value.as_mut().ok_or("Invalid return zval given")?;
148
149 Ok(match prop {
150 Some(prop_info) => {
151 prop_info.prop.set(self_, value_mut)?;
152 value
153 }
154 None => zend_std_write_property(object, member, value, cache_slot),
155 })
156 }
157
158 match internal::<T>(object, member, value, cache_slot) {
159 Ok(rv) => rv,
160 Err(e) => {
161 let _ = e.throw();
162 value
163 }
164 }
165 }
166
167 unsafe extern "C" fn get_properties<T: RegisteredClass>(
168 object: *mut ZendObject,
169 ) -> *mut ZendHashTable {
170 #[allow(clippy::inline_always)]
172 #[inline(always)]
173 unsafe fn internal<T: RegisteredClass>(
174 object: *mut ZendObject,
175 props: &mut ZendHashTable,
176 ) -> PhpResult {
177 let obj = object
178 .as_mut()
179 .and_then(|obj| ZendClassObject::<T>::from_zend_obj_mut(obj))
180 .ok_or("Invalid object pointer given")?;
181 let self_ = &mut *obj;
182 let struct_props = T::get_metadata().get_properties();
183
184 for (&name, val) in struct_props {
185 let mut zv = Zval::new();
186 if val.prop.get(self_, &mut zv).is_err() {
187 continue;
188 }
189 props.insert(name, zv).map_err(|e| {
190 format!("Failed to insert value into properties hashtable: {e:?}")
191 })?;
192 }
193
194 Ok(())
195 }
196
197 let props = zend_std_get_properties(object)
198 .as_mut()
199 .or_else(|| Some(ZendHashTable::new().into_raw()))
200 .expect("Failed to get property hashtable");
201
202 if let Err(e) = internal::<T>(object, props) {
203 let _ = e.throw();
204 }
205
206 props
207 }
208
209 unsafe extern "C" fn has_property<T: RegisteredClass>(
210 object: *mut ZendObject,
211 member: *mut ZendStr,
212 has_set_exists: c_int,
213 cache_slot: *mut *mut c_void,
214 ) -> c_int {
215 #[allow(clippy::inline_always)]
217 #[inline(always)]
218 unsafe fn internal<T: RegisteredClass>(
219 object: *mut ZendObject,
220 member: *mut ZendStr,
221 has_set_exists: c_int,
222 cache_slot: *mut *mut c_void,
223 ) -> PhpResult<c_int> {
224 let obj = object
225 .as_mut()
226 .and_then(|obj| ZendClassObject::<T>::from_zend_obj_mut(obj))
227 .ok_or("Invalid object pointer given")?;
228 let prop_name = member
229 .as_ref()
230 .ok_or("Invalid property name pointer given")?;
231 let props = T::get_metadata().get_properties();
232 let prop = props.get(prop_name.as_str()?);
233 let self_ = &mut *obj;
234
235 match has_set_exists {
236 0 => {
239 if let Some(val) = prop {
240 let mut zv = Zval::new();
241 val.prop.get(self_, &mut zv)?;
242 if !zv.is_null() {
243 return Ok(1);
244 }
245 }
246 }
247 1 => {
250 if let Some(val) = prop {
251 let mut zv = Zval::new();
252 val.prop.get(self_, &mut zv)?;
253
254 cfg_if::cfg_if! {
255 if #[cfg(php84)] {
256 #[allow(clippy::unnecessary_mut_passed)]
257 if zend_is_true(&raw mut zv) {
258 return Ok(1);
259 }
260 } else {
261 #[allow(clippy::unnecessary_mut_passed)]
262 if zend_is_true(&raw mut zv) == 1 {
263 return Ok(1);
264 }
265 }
266 }
267 }
268 }
269 2 => {
272 if prop.is_some() {
273 return Ok(1);
274 }
275 }
276 _ => return Err(
277 "Invalid value given for `has_set_exists` in struct `has_property` function."
278 .into(),
279 ),
280 }
281
282 Ok(zend_std_has_property(
283 object,
284 member,
285 has_set_exists,
286 cache_slot,
287 ))
288 }
289
290 match internal::<T>(object, member, has_set_exists, cache_slot) {
291 Ok(rv) => rv,
292 Err(e) => {
293 let _ = e.throw();
294 0
295 }
296 }
297 }
298}