@@ -10,7 +10,7 @@ use crate::{
1010 zend:: ExecutorGlobals ,
1111} ;
1212
13- use super :: Zval ;
13+ use super :: { ZendHashTable , Zval } ;
1414
1515/// Acts as a wrapper around a callable [`Zval`]. Allows the owner to call the
1616/// [`Zval`] as if it was a PHP function through the [`try_call`] method.
@@ -148,6 +148,137 @@ impl<'a> ZendCallable<'a> {
148148 Ok ( retval)
149149 }
150150 }
151+
152+ /// Attempts to call the callable with both positional and named arguments.
153+ ///
154+ /// This method supports PHP 8.0+ named arguments, allowing you to pass
155+ /// arguments by name rather than position. Named arguments are passed
156+ /// after positional arguments.
157+ ///
158+ /// # Parameters
159+ ///
160+ /// * `params` - A list of positional parameters to call the function with.
161+ /// * `named_params` - A list of named parameters as (name, value) tuples.
162+ ///
163+ /// # Returns
164+ ///
165+ /// Returns the result wrapped in [`Ok`] upon success.
166+ ///
167+ /// # Errors
168+ ///
169+ /// * If calling the callable fails, or an exception is thrown, an [`Err`]
170+ /// is returned.
171+ /// * If the number of parameters exceeds `u32::MAX`.
172+ /// * If a parameter name contains a NUL byte.
173+ ///
174+ /// # Example
175+ ///
176+ /// ```no_run
177+ /// use ext_php_rs::types::ZendCallable;
178+ ///
179+ /// // Call str_replace with named arguments
180+ /// let str_replace = ZendCallable::try_from_name("str_replace").unwrap();
181+ /// let result = str_replace.try_call_with_named(
182+ /// vec![], // no positional args
183+ /// vec![("search", &"world"), ("replace", &"PHP"), ("subject", &"Hello world")],
184+ /// ).unwrap();
185+ /// assert_eq!(result.string(), Some("Hello PHP".into()));
186+ /// ```
187+ // TODO: Measure this
188+ #[ allow( clippy:: inline_always) ]
189+ #[ inline( always) ]
190+ pub fn try_call_with_named (
191+ & self ,
192+ params : Vec < & dyn IntoZvalDyn > ,
193+ named_params : Vec < ( & str , & dyn IntoZvalDyn ) > ,
194+ ) -> Result < Zval > {
195+ if !self . 0 . is_callable ( ) {
196+ return Err ( Error :: Callable ) ;
197+ }
198+
199+ let mut retval = Zval :: new ( ) ;
200+ let len = params. len ( ) ;
201+ let params = params
202+ . into_iter ( )
203+ . map ( |val| val. as_zval ( false ) )
204+ . collect :: < Result < Vec < _ > > > ( ) ?;
205+ let packed = params. into_boxed_slice ( ) ;
206+
207+ // Build the named parameters hash table
208+ let named_ht = if named_params. is_empty ( ) {
209+ None
210+ } else {
211+ let mut ht = ZendHashTable :: new ( ) ;
212+ for ( name, val) in named_params {
213+ let zval = val. as_zval ( false ) ?;
214+ ht. insert ( name, zval) ?;
215+ }
216+ Some ( ht)
217+ } ;
218+
219+ let named_ptr = named_ht
220+ . as_ref ( )
221+ . map_or ( ptr:: null_mut ( ) , |ht| ptr:: from_ref ( & * * ht) . cast_mut ( ) ) ;
222+
223+ let result = unsafe {
224+ #[ allow( clippy:: used_underscore_items) ]
225+ _call_user_function_impl (
226+ ptr:: null_mut ( ) ,
227+ ptr:: from_ref ( self . 0 . as_ref ( ) ) . cast_mut ( ) ,
228+ & raw mut retval,
229+ len. try_into ( ) ?,
230+ packed. as_ptr ( ) . cast_mut ( ) ,
231+ named_ptr,
232+ )
233+ } ;
234+
235+ if result < 0 {
236+ Err ( Error :: Callable )
237+ } else if let Some ( e) = ExecutorGlobals :: take_exception ( ) {
238+ Err ( Error :: Exception ( e) )
239+ } else {
240+ Ok ( retval)
241+ }
242+ }
243+
244+ /// Attempts to call the callable with only named arguments.
245+ ///
246+ /// This is a convenience method equivalent to calling
247+ /// [`try_call_with_named`] with an empty positional arguments vector.
248+ ///
249+ /// # Parameters
250+ ///
251+ /// * `named_params` - A list of named parameters as (name, value) tuples.
252+ ///
253+ /// # Returns
254+ ///
255+ /// Returns the result wrapped in [`Ok`] upon success.
256+ ///
257+ /// # Errors
258+ ///
259+ /// * If calling the callable fails, or an exception is thrown, an [`Err`]
260+ /// is returned.
261+ /// * If a parameter name contains a NUL byte.
262+ ///
263+ /// # Example
264+ ///
265+ /// ```no_run
266+ /// use ext_php_rs::types::ZendCallable;
267+ ///
268+ /// // Call array_fill with named arguments only
269+ /// let array_fill = ZendCallable::try_from_name("array_fill").unwrap();
270+ /// let result = array_fill.try_call_named(vec![
271+ /// ("start_index", &0i64),
272+ /// ("count", &3i64),
273+ /// ("value", &"PHP"),
274+ /// ]).unwrap();
275+ /// ```
276+ ///
277+ /// [`try_call_with_named`]: #method.try_call_with_named
278+ #[ inline]
279+ pub fn try_call_named ( & self , named_params : Vec < ( & str , & dyn IntoZvalDyn ) > ) -> Result < Zval > {
280+ self . try_call_with_named ( vec ! [ ] , named_params)
281+ }
151282}
152283
153284impl < ' a > FromZval < ' a > for ZendCallable < ' a > {
0 commit comments