1use crate::{
4 boxed::ZBox,
5 error::{Error, Result},
6 exception::PhpException,
7 flags::DataType,
8 types::{ZendObject, Zval},
9};
10
11#[diagnostic::on_unimplemented(
14 message = "`{Self}` cannot be extracted from a PHP value",
15 label = "no `FromZval` implementation for `{Self}`"
16)]
17pub trait FromZval<'a>: Sized {
18 const TYPE: DataType;
20
21 const NULLABLE: bool = false;
26
27 fn from_zval(zval: &'a Zval) -> Option<Self>;
34
35 #[must_use]
41 fn from_missing() -> Option<Self> {
42 None
43 }
44}
45
46impl<'a, T> FromZval<'a> for Option<T>
47where
48 T: FromZval<'a>,
49{
50 const TYPE: DataType = T::TYPE;
51 const NULLABLE: bool = true;
52
53 fn from_zval(zval: &'a Zval) -> Option<Self> {
54 Some(T::from_zval(zval))
55 }
56
57 fn from_missing() -> Option<Self> {
58 Some(None)
59 }
60}
61
62#[diagnostic::on_unimplemented(
68 message = "`{Self}` cannot be used as a PHP function argument",
69 label = "no `FromZval` or `FromZvalMut` implementation for `{Self}`",
70 note = "`Option<&mut T>` is not supported for object arguments, use `&mut T`: PHP objects are handles and the caller sees every mutation",
71 note = "a variadic parameter must be spelled `&[T]` in the signature, a type alias hiding the slice is not detected"
72)]
73pub trait FromZvalMut<'a>: Sized {
74 const TYPE: DataType;
76
77 const NULLABLE: bool = false;
79
80 const BY_REF: bool = false;
84
85 fn from_zval_mut(zval: &'a mut Zval) -> Option<Self>;
92
93 #[must_use]
99 fn from_missing() -> Option<Self> {
100 None
101 }
102}
103
104impl<'a, T> FromZvalMut<'a> for T
105where
106 T: FromZval<'a>,
107{
108 const TYPE: DataType = <T as FromZval>::TYPE;
109 const NULLABLE: bool = <T as FromZval>::NULLABLE;
110
111 #[inline]
112 fn from_zval_mut(zval: &'a mut Zval) -> Option<Self> {
113 Self::from_zval(zval)
114 }
115
116 #[inline]
117 fn from_missing() -> Option<Self> {
118 <T as FromZval>::from_missing()
119 }
120}
121
122pub trait FromZendObject<'a>: Sized {
136 fn from_zend_object(obj: &'a ZendObject) -> Result<Self>;
148}
149
150pub trait FromZendObjectMut<'a>: Sized {
156 fn from_zend_object_mut(obj: &'a mut ZendObject) -> Result<Self>;
168}
169
170impl<'a, T> FromZendObjectMut<'a> for T
171where
172 T: FromZendObject<'a>,
173{
174 #[inline]
175 fn from_zend_object_mut(obj: &'a mut ZendObject) -> Result<Self> {
176 Self::from_zend_object(obj)
177 }
178}
179
180pub trait IntoZendObject {
183 fn into_zend_object(self) -> Result<ZBox<ZendObject>>;
195}
196
197pub trait IntoZval: Sized {
204 const TYPE: DataType;
206
207 const NULLABLE: bool;
209
210 fn into_zval(self, persistent: bool) -> Result<Zval> {
231 let mut zval = Zval::new();
232 self.set_zval(&mut zval, persistent)?;
233 Ok(zval)
234 }
235
236 fn set_zval(self, zv: &mut Zval, persistent: bool) -> Result<()>;
258}
259
260impl IntoZval for () {
261 const TYPE: DataType = DataType::Void;
262 const NULLABLE: bool = true;
263
264 #[inline]
265 fn set_zval(self, zv: &mut Zval, _: bool) -> Result<()> {
266 zv.set_null();
267 Ok(())
268 }
269}
270
271impl<T> IntoZval for Option<T>
272where
273 T: IntoZval,
274{
275 const TYPE: DataType = T::TYPE;
276 const NULLABLE: bool = true;
277
278 #[inline]
279 fn set_zval(self, zv: &mut Zval, persistent: bool) -> Result<()> {
280 if let Some(val) = self {
281 val.set_zval(zv, persistent)
282 } else {
283 zv.set_null();
284 Ok(())
285 }
286 }
287}
288
289impl<T, E> IntoZval for std::result::Result<T, E>
290where
291 T: IntoZval,
292 E: Into<PhpException>,
293{
294 const TYPE: DataType = T::TYPE;
295 const NULLABLE: bool = T::NULLABLE;
296
297 fn set_zval(self, zv: &mut Zval, persistent: bool) -> Result<()> {
298 match self {
299 Ok(val) => val.set_zval(zv, persistent),
300 Err(e) => {
301 let ex: PhpException = e.into();
302 ex.throw();
303 Err(Error::pending_exception().unwrap_or(Error::Callable))
304 }
305 }
306 }
307}
308
309pub trait IntoZvalDyn {
315 fn as_zval(&self, persistent: bool) -> Result<Zval>;
330
331 fn get_type(&self) -> DataType;
333
334 fn stub_value(&self) -> String {
339 match self.as_zval(false) {
341 Ok(zval) => zval_to_stub(&zval),
342 Err(_) => "null".to_string(),
343 }
344 }
345}
346
347#[must_use]
349#[allow(clippy::match_same_arms)]
350pub fn zval_to_stub(zval: &Zval) -> String {
351 use crate::flags::DataType;
352
353 match zval.get_type() {
354 DataType::Null | DataType::Undef => "null".to_string(),
355 DataType::True => "true".to_string(),
356 DataType::False => "false".to_string(),
357 DataType::Long => zval
358 .long()
359 .map_or_else(|| "null".to_string(), |v| v.to_string()),
360 DataType::Double => zval
361 .double()
362 .map_or_else(|| "null".to_string(), |v| v.to_string()),
363 DataType::String => {
364 if let Some(s) = zval.str() {
365 let escaped = s
366 .replace('\\', "\\\\")
367 .replace('\'', "\\'")
368 .replace('\n', "\\n")
369 .replace('\r', "\\r")
370 .replace('\t', "\\t");
371 format!("'{escaped}'")
372 } else {
373 "null".to_string()
374 }
375 }
376 DataType::Array => {
377 #[allow(clippy::explicit_iter_loop)]
378 if let Some(arr) = zval.array() {
379 let is_sequential = arr.iter().enumerate().all(|(i, (key, _))| {
381 matches!(key, crate::types::ArrayKey::Long(idx) if i64::try_from(i).is_ok_and(|ii| idx == ii))
382 });
383
384 let mut parts = Vec::new();
385 for (key, val) in arr.iter() {
386 let val_str = zval_to_stub(val);
387 if is_sequential {
388 parts.push(val_str);
389 } else {
390 match key {
391 crate::types::ArrayKey::Long(idx) => {
392 parts.push(format!("{idx} => {val_str}"));
393 }
394 crate::types::ArrayKey::String(key) => {
395 let key_escaped = key.replace('\\', "\\\\").replace('\'', "\\'");
396 parts.push(format!("'{key_escaped}' => {val_str}"));
397 }
398 crate::types::ArrayKey::Str(key) => {
399 let key_escaped = key.replace('\\', "\\\\").replace('\'', "\\'");
400 parts.push(format!("'{key_escaped}' => {val_str}"));
401 }
402 crate::types::ArrayKey::ZendString(key) => {
403 let decoded = match key.as_str() {
404 Ok(s) => std::borrow::Cow::Borrowed(s),
405 Err(_) => String::from_utf8_lossy(key.as_bytes()),
406 };
407 let key_escaped =
408 decoded.replace('\\', "\\\\").replace('\'', "\\'");
409 parts.push(format!("'{key_escaped}' => {val_str}"));
410 }
411 }
412 }
413 }
414 format!("[{}]", parts.join(", "))
415 } else {
416 "[]".to_string()
417 }
418 }
419 _ => "null".to_string(),
420 }
421}
422
423impl<T: IntoZval + Clone> IntoZvalDyn for T {
424 fn as_zval(&self, persistent: bool) -> Result<Zval> {
425 self.clone().into_zval(persistent)
426 }
427
428 fn get_type(&self) -> DataType {
429 Self::TYPE
430 }
431}
432
433impl IntoZvalDyn for Zval {
434 fn as_zval(&self, _persistent: bool) -> Result<Zval> {
435 Ok(self.shallow_clone())
436 }
437
438 fn get_type(&self) -> DataType {
439 self.get_type()
440 }
441}
442
443pub trait StubLiteral {
451 fn stub_literal(&self) -> String;
453}
454
455macro_rules! stub_literal_display {
456 ($($ty:ty),*) => {
457 $(
458 impl StubLiteral for $ty {
459 fn stub_literal(&self) -> String {
460 self.to_string()
461 }
462 }
463 )*
464 };
465}
466
467stub_literal_display!(bool, i8, i16, i32, i64, isize, u8, u16, u32, u64, usize);
468
469macro_rules! stub_literal_float {
470 ($($ty:ty),*) => {
471 $(
472 impl StubLiteral for $ty {
473 fn stub_literal(&self) -> String {
474 if self.is_nan() {
475 "NAN".to_string()
476 } else if self.is_infinite() {
477 if *self > 0.0 { "INF" } else { "-INF" }.to_string()
478 } else {
479 format!("{self:?}")
480 }
481 }
482 }
483 )*
484 };
485}
486
487stub_literal_float!(f32, f64);
488
489impl StubLiteral for str {
490 fn stub_literal(&self) -> String {
491 let escaped = self.replace('\\', "\\\\").replace('\'', "\\'");
492 format!("'{escaped}'")
493 }
494}
495
496impl StubLiteral for &str {
497 fn stub_literal(&self) -> String {
498 (**self).stub_literal()
499 }
500}
501
502impl StubLiteral for String {
503 fn stub_literal(&self) -> String {
504 self.as_str().stub_literal()
505 }
506}
507
508impl StubLiteral for std::borrow::Cow<'_, str> {
509 fn stub_literal(&self) -> String {
510 self.as_ref().stub_literal()
511 }
512}
513
514impl<T: StubLiteral> StubLiteral for Option<T> {
515 fn stub_literal(&self) -> String {
516 self.as_ref()
517 .map_or_else(|| "null".to_string(), StubLiteral::stub_literal)
518 }
519}
520
521impl<T: StubLiteral> StubLiteral for [T] {
522 fn stub_literal(&self) -> String {
523 let items: Vec<String> = self.iter().map(StubLiteral::stub_literal).collect();
524 format!("[{}]", items.join(", "))
525 }
526}
527
528impl<T: StubLiteral> StubLiteral for Vec<T> {
529 fn stub_literal(&self) -> String {
530 self.as_slice().stub_literal()
531 }
532}
533
534impl<T: StubLiteral, const N: usize> StubLiteral for [T; N] {
535 fn stub_literal(&self) -> String {
536 self.as_slice().stub_literal()
537 }
538}
539
540impl<K: StubLiteral, V: StubLiteral, S> StubLiteral for std::collections::HashMap<K, V, S> {
541 fn stub_literal(&self) -> String {
542 let mut entries: Vec<String> = self
543 .iter()
544 .map(|(key, value)| format!("{} => {}", key.stub_literal(), value.stub_literal()))
545 .collect();
546 entries.sort();
547 format!("[{}]", entries.join(", "))
548 }
549}
550
551#[cfg(test)]
552mod tests {
553 use super::*;
554 use crate::types::{PhpRef, Separated, ZendHashTable, ZendObject};
555
556 const _: () = {
557 assert!(<Option<i64> as FromZval>::NULLABLE);
558 assert!(<Option<i64> as FromZvalMut>::NULLABLE);
559 assert!(!<i64 as FromZval>::NULLABLE);
560 assert!(!<i64 as FromZvalMut>::NULLABLE);
561 assert!(!<i64 as FromZvalMut>::BY_REF);
562 assert!(!<&Zval as FromZvalMut>::BY_REF);
563 assert!(<&mut Zval as FromZvalMut>::BY_REF);
564 assert!(<PhpRef<'_> as FromZvalMut>::BY_REF);
565 assert!(!<Separated<'_> as FromZvalMut>::BY_REF);
566 assert!(<&mut ZendHashTable as FromZvalMut>::BY_REF);
567 assert!(!<&mut ZendObject as FromZvalMut>::BY_REF);
568 };
569
570 #[test]
571 fn stub_literals_are_php_source() {
572 assert_eq!(42_i32.stub_literal(), "42");
573 assert_eq!(true.stub_literal(), "true");
574 assert_eq!(1.5_f64.stub_literal(), "1.5");
575 assert_eq!(3.0_f64.stub_literal(), "3.0");
576 assert_eq!(f64::INFINITY.stub_literal(), "INF");
577 assert_eq!("it's".stub_literal(), "'it\\'s'");
578 assert_eq!(String::from("a\\b").stub_literal(), "'a\\\\b'");
579 assert_eq!(None::<i64>.stub_literal(), "null");
580 assert_eq!(Some("x".to_string()).stub_literal(), "'x'");
581 assert_eq!(Vec::<i64>::new().stub_literal(), "[]");
582 assert_eq!(vec![1, 2].stub_literal(), "[1, 2]");
583 let map: std::collections::HashMap<&str, i64> = [("b", 2), ("a", 1)].into();
584 assert_eq!(map.stub_literal(), "['a' => 1, 'b' => 2]");
585 }
586
587 #[test]
588 fn omitted_argument_is_none_for_option_and_missing_otherwise() {
589 assert_eq!(<Option<i64> as FromZvalMut>::from_missing(), Some(None));
590 assert_eq!(
591 <Option<Option<i64>> as FromZvalMut>::from_missing(),
592 Some(None)
593 );
594 assert_eq!(<i64 as FromZvalMut>::from_missing(), None);
595 }
596}