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