Skip to main content

ext_php_rs/
args.rs

1//! Builder and objects relating to function and method arguments.
2
3use std::{ffi::CString, ptr};
4
5use crate::{
6    convert::{FromZvalMut, IntoZvalDyn},
7    describe::{Parameter, abi},
8    error::{Error, Result},
9    ffi::{zend_internal_arg_info, zend_wrong_parameters_count_error},
10    flags::DataType,
11    types::Zval,
12    zend::ZendType,
13};
14
15/// Argument info tables of a class's or enum's methods, one per method.
16///
17/// The Zend engine borrows these for the life of the process, so they must be
18/// parked in something that lives that long.
19pub type ArgInfoTables = Box<[Box<[ArgInfo]>]>;
20
21/// Represents an argument to a function.
22#[must_use]
23#[derive(Debug)]
24pub struct Arg<'a> {
25    name: String,
26    r#type: DataType,
27    as_ref: bool,
28    allow_null: bool,
29    pub(crate) variadic: bool,
30    default_value: Option<String>,
31    zval: Option<&'a mut Zval>,
32    variadic_zvals: Vec<Option<&'a mut Zval>>,
33}
34
35impl<'a> Arg<'a> {
36    /// Creates a new argument.
37    ///
38    /// # Parameters
39    ///
40    /// * `name` - The name of the parameter.
41    /// * `_type` - The type of the parameter.
42    pub fn new<T: Into<String>>(name: T, r#type: DataType) -> Self {
43        Arg {
44            name: name.into(),
45            r#type,
46            as_ref: false,
47            allow_null: false,
48            variadic: false,
49            default_value: None,
50            zval: None,
51            variadic_zvals: vec![],
52        }
53    }
54
55    /// Creates an argument whose type, nullability and pass-by-reference
56    /// flag come from the Rust type it is extracted into.
57    ///
58    /// # Parameters
59    ///
60    /// * `name` - The name of the parameter.
61    pub fn of<T: FromZvalMut<'a>>(name: impl Into<String>) -> Self {
62        Arg {
63            name: name.into(),
64            r#type: T::TYPE,
65            as_ref: T::BY_REF,
66            allow_null: T::NULLABLE,
67            variadic: false,
68            default_value: None,
69            zval: None,
70            variadic_zvals: vec![],
71        }
72    }
73
74    /// Sets the argument as a reference.
75    #[allow(clippy::wrong_self_convention)]
76    pub fn as_ref(mut self) -> Self {
77        self.as_ref = true;
78        self
79    }
80
81    /// Sets the argument as variadic.
82    pub fn is_variadic(mut self) -> Self {
83        self.variadic = true;
84        self
85    }
86
87    /// Sets the argument as nullable.
88    pub fn allow_null(mut self) -> Self {
89        self.allow_null = true;
90        self
91    }
92
93    /// Sets the default value for the argument.
94    pub fn default<T: Into<String>>(mut self, default: T) -> Self {
95        self.default_value = Some(default.into());
96        self
97    }
98
99    /// Attempts to consume the argument, converting the inner type into `T`.
100    /// Upon success, the result is returned in a [`Result`].
101    ///
102    /// As this function consumes, it cannot return a reference to the
103    /// underlying zval.
104    ///
105    /// # Errors
106    ///
107    /// If the conversion fails (or the argument contains no value), the
108    /// argument is returned in an [`Err`] variant.
109    pub fn consume<T>(mut self) -> Result<T, Self>
110    where
111        for<'b> T: FromZvalMut<'b>,
112    {
113        self.zval
114            .as_mut()
115            .and_then(|zv| T::from_zval_mut(zv.dereference_mut()))
116            .ok_or(self)
117    }
118
119    /// Attempts to retrieve the value of the argument.
120    /// This will be None until the [`ArgParser`] is used to parse
121    /// the arguments.
122    pub fn val<T>(&'a mut self) -> Option<T>
123    where
124        T: FromZvalMut<'a>,
125    {
126        self.zval
127            .as_mut()
128            .and_then(|zv| T::from_zval_mut(zv.dereference_mut()))
129    }
130
131    /// Gets all the variadic values for this Rust argument. The result does not
132    /// contain a value that cannot be converted into `T`.
133    /// [`try_variadic_vals`](Self::try_variadic_vals) rejects it.
134    pub fn variadic_vals<T>(&'a mut self) -> Vec<T>
135    where
136        T: FromZvalMut<'a>,
137    {
138        self.variadic_zvals
139            .iter_mut()
140            .filter_map(|zv| zv.as_mut())
141            .filter_map(|zv| T::from_zval_mut(zv.dereference_mut()))
142            .collect()
143    }
144
145    /// Gets all the variadic values for this Rust argument. Returns `None` if
146    /// one of the values cannot be converted into `T`.
147    pub fn try_variadic_vals<T>(&'a mut self) -> Option<Vec<T>>
148    where
149        T: FromZvalMut<'a>,
150    {
151        self.variadic_zvals
152            .iter_mut()
153            .filter_map(|zv| zv.as_mut())
154            .map(|zv| T::from_zval_mut(zv.dereference_mut()))
155            .collect()
156    }
157
158    /// Attempts to return a reference to the arguments internal Zval.
159    ///
160    /// # Returns
161    ///
162    /// * `Some(&Zval)` - The internal zval.
163    /// * `None` - The argument was empty.
164    // TODO: Figure out if we can change this
165    #[allow(clippy::mut_mut)]
166    pub fn zval(&mut self) -> Option<&mut &'a mut Zval> {
167        self.zval.as_mut()
168    }
169
170    /// Attempts to call the argument as a callable with a list of arguments to
171    /// pass to the function. Note that a thrown exception inside the
172    /// callable is not detectable, therefore you should check if the return
173    /// value is valid rather than unwrapping. Returns a result containing the
174    /// return value of the function, or an error.
175    ///
176    /// You should not call this function directly, rather through the
177    /// [`call_user_func`](crate::call_user_func) macro.
178    ///
179    /// # Parameters
180    ///
181    /// * `params` - A list of parameters to call the function with.
182    ///
183    /// # Errors
184    ///
185    /// * `Error::Callable` - The argument is not callable.
186    // TODO: Measure this
187    #[allow(clippy::inline_always)]
188    #[inline(always)]
189    pub fn try_call(&self, params: Vec<&dyn IntoZvalDyn>) -> Result<Zval> {
190        self.zval.as_ref().ok_or(Error::Callable)?.try_call(params)
191    }
192
193    /// Returns the internal PHP argument info.
194    ///
195    /// An optional nullable argument without an explicit default reports
196    /// `null` as its default, the same way PHP declares `?T $x = null`.
197    pub(crate) fn as_arg_info(&self, optional: bool) -> Result<ArgInfo> {
198        let default_value = match &self.default_value {
199            Some(val) => Some(val.as_str()),
200            None if optional && self.allow_null && !self.variadic => Some("null"),
201            None => None,
202        };
203        Ok(ArgInfo {
204            name: CString::new(self.name.as_str())?.into_raw(),
205            type_: ZendType::empty_from_type(
206                self.r#type,
207                self.as_ref,
208                self.variadic,
209                self.allow_null,
210            )
211            .ok_or(Error::ZvalConversion(self.r#type))?,
212            default_value: match default_value {
213                Some(val) => CString::new(val)?.into_raw(),
214                None => ptr::null(),
215            },
216        })
217    }
218}
219
220impl From<Arg<'_>> for Parameter {
221    fn from(val: Arg<'_>) -> Self {
222        Parameter {
223            name: val.name.into(),
224            ty: Some(val.r#type).into(),
225            nullable: val.allow_null,
226            variadic: val.variadic,
227            default: val.default_value.map(abi::RString::from).into(),
228        }
229    }
230}
231
232/// Internal argument information used by Zend.
233pub type ArgInfo = zend_internal_arg_info;
234
235/// Number of required parameters for a signature, given for every parameter
236/// whether it may be omitted (nullable or defaulted).
237///
238/// Follows the PHP rule: only the trailing run of omittable parameters is
239/// optional, an omittable parameter followed by a required one stays required.
240#[must_use]
241pub const fn required_count(omittable: &[bool]) -> usize {
242    let mut required = omittable.len();
243    while required > 0 && omittable[required - 1] {
244        required -= 1;
245    }
246    required
247}
248
249/// Parses the arguments of a function.
250#[must_use]
251pub struct ArgParser<'a, 'b> {
252    args: Vec<&'b mut Arg<'a>>,
253    min_num_args: Option<usize>,
254    arg_zvals: Vec<Option<&'a mut Zval>>,
255}
256
257impl<'a, 'b> ArgParser<'a, 'b> {
258    /// Builds a new function argument parser.
259    pub fn new(arg_zvals: Vec<Option<&'a mut Zval>>) -> Self {
260        ArgParser {
261            args: vec![],
262            min_num_args: None,
263            arg_zvals,
264        }
265    }
266
267    /// Adds a new argument to the parser.
268    ///
269    /// # Parameters
270    ///
271    /// * `arg` - The argument to add to the parser.
272    pub fn arg(mut self, arg: &'b mut Arg<'a>) -> Self {
273        self.args.push(arg);
274        self
275    }
276
277    /// Sets the next arguments to be added as not required.
278    pub fn not_required(mut self) -> Self {
279        self.min_num_args = Some(self.args.len());
280        self
281    }
282
283    /// Sets how many leading arguments are required, see [`required_count`].
284    pub fn required_args(mut self, count: usize) -> Self {
285        self.min_num_args = Some(count);
286        self
287    }
288
289    /// Uses the argument parser to parse the arguments contained in the given
290    /// `ExecuteData` object. Returns successfully if the arguments were
291    /// parsed.
292    ///
293    /// This function can only be safely called from within an exported PHP
294    /// function.
295    ///
296    /// # Parameters
297    ///
298    /// * `execute_data` - The execution data from the function.
299    ///
300    /// # Errors
301    ///
302    /// Returns an [`Error`] type if there were too many or too little arguments
303    /// passed to the function. The user has already been notified so you
304    /// should break execution after seeing an error type.
305    ///
306    /// Also returns an error if the number of min/max arguments exceeds
307    /// `u32::MAX`
308    pub fn parse(mut self) -> Result<()> {
309        let max_num_args = self.args.len();
310        let mut min_num_args = self.min_num_args.unwrap_or(max_num_args);
311        let num_args = self.arg_zvals.len();
312        let has_variadic = self.args.last().is_some_and(|arg| arg.variadic);
313        if has_variadic {
314            min_num_args = min_num_args.saturating_sub(1);
315        }
316
317        if num_args < min_num_args || (!has_variadic && num_args > max_num_args) {
318            // SAFETY: Exported C function is safe, return value is unused and parameters
319            // are copied.
320            unsafe {
321                zend_wrong_parameters_count_error(
322                    min_num_args.try_into()?,
323                    max_num_args.try_into()?,
324                );
325            };
326            return Err(Error::IncorrectArguments(num_args, min_num_args));
327        }
328
329        for (i, arg_zval) in self.arg_zvals.into_iter().enumerate() {
330            let arg = match self.args.get_mut(i) {
331                Some(arg) => Some(arg),
332                // Only select the last item if it's variadic
333                None => self.args.last_mut().filter(|arg| arg.variadic),
334            };
335            if let Some(arg) = arg {
336                if arg.variadic {
337                    arg.variadic_zvals.push(arg_zval);
338                } else {
339                    arg.zval = arg_zval;
340                }
341            }
342        }
343
344        Ok(())
345    }
346}
347
348#[cfg(test)]
349mod tests {
350    #![allow(clippy::unwrap_used)]
351    #[cfg(feature = "embed")]
352    use crate::embed::Embed;
353
354    use super::*;
355
356    #[test]
357    fn required_count_keeps_omittable_args_before_a_required_one() {
358        assert_eq!(required_count(&[]), 0);
359        assert_eq!(required_count(&[false, false]), 2);
360        assert_eq!(required_count(&[false, true]), 1);
361        assert_eq!(required_count(&[true, false, true, true]), 2);
362        assert_eq!(required_count(&[true, true]), 0);
363    }
364
365    #[test]
366    fn of_reads_metadata_from_the_type() {
367        let arg = Arg::of::<Option<i64>>("maybe");
368        assert_eq!(arg.r#type, DataType::Long);
369        assert!(arg.allow_null);
370        assert!(!arg.as_ref);
371        let arg = Arg::of::<&mut Zval>("target");
372        assert_eq!(arg.r#type, DataType::Mixed);
373        assert!(!arg.allow_null);
374        assert!(arg.as_ref);
375    }
376
377    fn default_of(arg: &Arg<'_>, optional: bool) -> Option<String> {
378        let info = arg.as_arg_info(optional).unwrap();
379        unsafe { drop(CString::from_raw(info.name.cast_mut())) };
380        if info.default_value.is_null() {
381            return None;
382        }
383        let default = unsafe { CString::from_raw(info.default_value.cast_mut()) };
384        Some(default.to_str().unwrap().to_string())
385    }
386
387    #[test]
388    fn optional_nullable_arg_reports_a_null_default() {
389        assert_eq!(
390            default_of(&Arg::of::<Option<i64>>("a"), true).as_deref(),
391            Some("null")
392        );
393        assert_eq!(default_of(&Arg::of::<Option<i64>>("a"), false), None);
394        assert_eq!(default_of(&Arg::of::<i64>("a"), true), None);
395        assert_eq!(
396            default_of(&Arg::of::<Option<i64>>("a").is_variadic(), true),
397            None
398        );
399        assert_eq!(
400            default_of(&Arg::of::<Option<i64>>("a").default("5"), true).as_deref(),
401            Some("5")
402        );
403    }
404
405    #[test]
406    fn test_new() {
407        let arg = Arg::new("test", DataType::Long);
408        assert_eq!(arg.name, "test");
409        assert_eq!(arg.r#type, DataType::Long);
410        assert!(!arg.as_ref);
411        assert!(!arg.allow_null);
412        assert!(!arg.variadic);
413        assert!(arg.default_value.is_none());
414        assert!(arg.zval.is_none());
415        assert!(arg.variadic_zvals.is_empty());
416    }
417
418    #[test]
419    fn test_as_ref() {
420        let arg = Arg::new("test", DataType::Long).as_ref();
421        assert!(arg.as_ref);
422    }
423
424    #[test]
425    fn test_is_variadic() {
426        let arg = Arg::new("test", DataType::Long).is_variadic();
427        assert!(arg.variadic);
428    }
429
430    #[test]
431    fn test_allow_null() {
432        let arg = Arg::new("test", DataType::Long).allow_null();
433        assert!(arg.allow_null);
434    }
435
436    #[test]
437    fn test_default() {
438        let arg = Arg::new("test", DataType::Long).default("default");
439        assert_eq!(arg.default_value, Some("default".to_string()));
440
441        // TODO: Validate type
442    }
443
444    #[test]
445    fn test_consume_no_value() {
446        let arg = Arg::new("test", DataType::Long);
447        let result: Result<i32, _> = arg.consume();
448        assert!(result.is_err());
449        assert_eq!(result.unwrap_err().name, "test");
450    }
451
452    #[test]
453    #[cfg(feature = "embed")]
454    fn test_consume() {
455        let mut arg = Arg::new("test", DataType::Long);
456        let mut zval = Zval::from(42);
457        arg.zval = Some(&mut zval);
458
459        let result: Result<i32, _> = arg.consume();
460        assert_eq!(result.unwrap(), 42);
461    }
462
463    #[test]
464    fn test_val_no_value() {
465        let mut arg = Arg::new("test", DataType::Long);
466        let result: Option<i32> = arg.val();
467        assert!(result.is_none());
468    }
469
470    #[test]
471    #[cfg(feature = "embed")]
472    fn test_val() {
473        let mut arg = Arg::new("test", DataType::Long);
474        let mut zval = Zval::from(42);
475        arg.zval = Some(&mut zval);
476
477        let result: Option<i32> = arg.val();
478        assert_eq!(result.unwrap(), 42);
479    }
480
481    #[test]
482    #[cfg(feature = "embed")]
483    fn test_variadic_vals() {
484        let mut arg = Arg::new("test", DataType::Long).is_variadic();
485        let mut zval1 = Zval::from(42);
486        let mut zval2 = Zval::from(43);
487        arg.variadic_zvals.push(Some(&mut zval1));
488        arg.variadic_zvals.push(Some(&mut zval2));
489
490        let result: Vec<i32> = arg.variadic_vals();
491        assert_eq!(result, vec![42, 43]);
492    }
493
494    #[test]
495    #[cfg(feature = "embed")]
496    fn try_variadic_vals_fails_when_a_value_does_not_convert() {
497        let mut arg = Arg::new("test", DataType::Long).is_variadic();
498        let mut long = Zval::from(42);
499        let mut double = Zval::from(1.5);
500        arg.variadic_zvals.push(Some(&mut long));
501        arg.variadic_zvals.push(Some(&mut double));
502
503        assert_eq!(arg.try_variadic_vals::<i64>(), None);
504    }
505
506    #[test]
507    fn test_zval_no_value() {
508        let mut arg = Arg::new("test", DataType::Long);
509        let result = arg.zval();
510        assert!(result.is_none());
511    }
512
513    #[test]
514    #[cfg(feature = "embed")]
515    fn test_zval() {
516        let mut arg = Arg::new("test", DataType::Long);
517        let mut zval = Zval::from(42);
518        arg.zval = Some(&mut zval);
519
520        let result = arg.zval();
521        assert!(result.is_some());
522        assert_eq!(result.unwrap().dereference_mut().long(), Some(42));
523    }
524
525    #[cfg(feature = "embed")]
526    #[test]
527    fn test_try_call_no_value() {
528        let arg = Arg::new("test", DataType::Long);
529        let result = arg.try_call(vec![]);
530        assert!(result.is_err());
531    }
532
533    #[test]
534    #[cfg(feature = "embed")]
535    fn test_try_call_not_callable() {
536        Embed::run(|| {
537            let mut arg = Arg::new("test", DataType::Long);
538            let mut zval = Zval::from(42);
539            arg.zval = Some(&mut zval);
540
541            let result = arg.try_call(vec![]);
542            assert!(result.is_err());
543        });
544    }
545
546    // TODO: Test the callable case
547
548    #[test]
549    #[cfg(feature = "embed")]
550    fn test_as_arg_info() {
551        let arg = Arg::new("test", DataType::Long);
552        let arg_info = arg.as_arg_info(false);
553        assert!(arg_info.is_ok());
554
555        let arg_info = arg_info.unwrap();
556        assert!(arg_info.default_value.is_null());
557
558        let r#type = arg_info.type_;
559        assert_eq!(r#type.type_mask, 16);
560
561        unsafe { drop(CString::from_raw(arg_info.name.cast_mut())) };
562    }
563
564    #[test]
565    #[cfg(feature = "embed")]
566    fn test_as_arg_info_with_default() {
567        let arg = Arg::new("test", DataType::Long).default("default");
568        let arg_info = arg.as_arg_info(false);
569        assert!(arg_info.is_ok());
570
571        let arg_info = arg_info.unwrap();
572        assert!(!arg_info.default_value.is_null());
573
574        let r#type = arg_info.type_;
575        assert_eq!(r#type.type_mask, 16);
576
577        unsafe {
578            drop(CString::from_raw(arg_info.name.cast_mut()));
579            drop(CString::from_raw(arg_info.default_value.cast_mut()));
580        }
581    }
582
583    #[test]
584    fn test_param_from_arg() {
585        let arg = Arg::new("test", DataType::Long)
586            .default("default")
587            .allow_null();
588        let param: Parameter = arg.into();
589        assert_eq!(param.name, "test".into());
590        assert_eq!(param.ty, abi::Option::Some(DataType::Long));
591        assert!(param.nullable);
592        assert_eq!(param.default, abi::Option::Some("default".into()));
593    }
594
595    #[test]
596    fn test_arg_parser_new() {
597        let arg_zvals = vec![None, None];
598        let parser = ArgParser::new(arg_zvals);
599        assert_eq!(parser.arg_zvals.len(), 2);
600        assert!(parser.args.is_empty());
601        assert!(parser.min_num_args.is_none());
602    }
603
604    #[test]
605    fn test_arg_parser_arg() {
606        let arg_zvals = vec![None, None];
607        let mut parser = ArgParser::new(arg_zvals);
608        let mut arg = Arg::new("test", DataType::Long);
609        parser = parser.arg(&mut arg);
610        assert_eq!(parser.args.len(), 1);
611        assert_eq!(parser.args[0].name, "test");
612        assert_eq!(parser.args[0].r#type, DataType::Long);
613    }
614
615    // TODO: test parse
616}