1use alef_codegen::generators::trait_bridge::{TraitBridgeGenerator, TraitBridgeSpec, gen_bridge_all};
7use alef_core::config::TraitBridgeConfig;
8use alef_core::ir::{ApiSurface, MethodDef, TypeDef, TypeRef};
9use std::collections::HashMap;
10use std::fmt::Write;
11
12pub struct PhpBridgeGenerator {
15 pub core_import: String,
17 pub type_paths: HashMap<String, String>,
19}
20
21impl TraitBridgeGenerator for PhpBridgeGenerator {
22 fn foreign_object_type(&self) -> &str {
23 "&mut ext_php_rs::types::ZendObject"
24 }
25
26 fn bridge_imports(&self) -> Vec<String> {
27 vec!["use std::rc::Rc;".to_string(), "use std::cell::RefCell;".to_string()]
28 }
29
30 fn gen_sync_method_body(&self, method: &MethodDef, _spec: &TraitBridgeSpec) -> String {
31 let name = &method.name;
32 let mut out = String::with_capacity(512);
33
34 writeln!(
36 out,
37 "// SAFETY: PHP objects are single-threaded; method calls are safe within a request."
38 )
39 .ok();
40
41 let has_args = !method.params.is_empty();
42 if has_args {
43 writeln!(out, "let mut args: Vec<ext_php_rs::types::Zval> = Vec::new();").ok();
44 for p in &method.params {
45 writeln!(
46 out,
47 "args.push(ext_php_rs::types::Zval::try_from({}).unwrap_or_default());",
48 p.name
49 )
50 .ok();
51 }
52 }
53
54 let args_expr = if has_args {
55 "args.iter().map(|z| z as &dyn ext_php_rs::convert::IntoZvalDyn).collect()"
56 } else {
57 "vec![]"
58 };
59
60 writeln!(out, "let result = self.inner.try_call_method(\"{name}\", {args_expr});").ok();
61 writeln!(out, "match result {{").ok();
62 writeln!(
63 out,
64 " Ok(val) => val.string().unwrap_or_default().parse().unwrap_or_default(),"
65 )
66 .ok();
67 writeln!(out, " Err(_) => Default::default(),").ok();
68 writeln!(out, "}}").ok();
69
70 out
71 }
72
73 fn gen_async_method_body(&self, method: &MethodDef, spec: &TraitBridgeSpec) -> String {
74 let name = &method.name;
75 let mut out = String::with_capacity(1024);
76
77 writeln!(out, "let inner_obj = self.inner.clone();").ok();
78 writeln!(out, "let cached_name = self.cached_name.clone();").ok();
79
80 for p in &method.params {
82 match &p.ty {
83 TypeRef::String => {
84 writeln!(out, "let {} = {}.clone();", p.name, p.name).ok();
85 }
86 _ => {
87 writeln!(out, "let {} = {};", p.name, p.name).ok();
88 }
89 }
90 }
91
92 writeln!(out).ok();
93 writeln!(out, "Box::pin(async move {{").ok();
94 writeln!(out, " // SAFETY: PHP objects are single-threaded within a request.").ok();
95 writeln!(out, " // The block_on executes within the async runtime.").ok();
96 writeln!(out, " let result = WORKER_RUNTIME.block_on(async {{").ok();
97
98 let has_args = !method.params.is_empty();
99 if has_args {
100 writeln!(out, " let mut args: Vec<ext_php_rs::types::Zval> = Vec::new();").ok();
101 for p in &method.params {
102 writeln!(
103 out,
104 " args.push(ext_php_rs::types::Zval::try_from({}).unwrap_or_default());",
105 p.name
106 )
107 .ok();
108 }
109 }
110
111 let args_expr = if has_args {
112 "args.iter().map(|z| z as &dyn ext_php_rs::convert::IntoZvalDyn).collect()"
113 } else {
114 "vec![]"
115 };
116
117 writeln!(
118 out,
119 " match inner_obj.try_call_method(\"{name}\", {args_expr}) {{"
120 )
121 .ok();
122 writeln!(
123 out,
124 " Ok(val) => val.string().unwrap_or_default().parse().unwrap_or_default(),"
125 )
126 .ok();
127 writeln!(
128 out,
129 " Err(e) => Err({}::KreuzbergError::Plugin {{",
130 spec.core_import
131 )
132 .ok();
133 writeln!(
134 out,
135 " message: format!(\"Plugin '{{}}' method '{name}' failed: {{}}\", cached_name, e),"
136 )
137 .ok();
138 writeln!(out, " plugin_name: cached_name.clone(),").ok();
139 writeln!(out, " }}),").ok();
140 writeln!(out, " }}").ok();
141 writeln!(out, " }});").ok();
142 writeln!(out, " result").ok();
143 writeln!(out, "}})").ok();
144
145 out
146 }
147
148 fn gen_constructor(&self, spec: &TraitBridgeSpec) -> String {
149 let wrapper = spec.wrapper_name();
150 let mut out = String::with_capacity(512);
151
152 writeln!(out, "impl {wrapper} {{").ok();
153 writeln!(out, " /// Create a new bridge wrapping a PHP object.").ok();
154 writeln!(out, " ///").ok();
155 writeln!(
156 out,
157 " /// Validates that the PHP object provides all required methods."
158 )
159 .ok();
160 writeln!(
161 out,
162 " pub fn new(php_obj: &mut ext_php_rs::types::ZendObject) -> Self {{"
163 )
164 .ok();
165
166 for req_method in spec.required_methods() {
168 writeln!(
169 out,
170 " debug_assert!(php_obj.get_property(\"{}\").is_some(),",
171 req_method.name
172 )
173 .ok();
174 writeln!(
175 out,
176 " \"PHP object missing required method: {}\");",
177 req_method.name
178 )
179 .ok();
180 }
181
182 writeln!(out, " let cached_name = php_obj").ok();
184 writeln!(out, " .try_call_method(\"name\", vec![])").ok();
185 writeln!(out, " .ok()").ok();
186 writeln!(out, " .and_then(|v| v.string())").ok();
187 writeln!(out, " .unwrap_or(\"unknown\".into())").ok();
188 writeln!(out, " .to_string();").ok();
189
190 writeln!(out).ok();
191 writeln!(out, " Self {{").ok();
192 writeln!(out, " inner: php_obj,").ok();
193 writeln!(out, " cached_name,").ok();
194 writeln!(out, " }}").ok();
195 writeln!(out, " }}").ok();
196 writeln!(out, "}}").ok();
197
198 out
199 }
200
201 fn gen_registration_fn(&self, spec: &TraitBridgeSpec) -> String {
202 let Some(register_fn) = spec.bridge_config.register_fn.as_deref() else {
203 return String::new();
204 };
205 let Some(registry_getter) = spec.bridge_config.registry_getter.as_deref() else {
206 return String::new();
207 };
208 let wrapper = spec.wrapper_name();
209 let trait_path = spec.trait_path();
210
211 let mut out = String::with_capacity(1024);
212
213 writeln!(out, "#[php_function]").ok();
214 writeln!(
215 out,
216 "pub fn {register_fn}(backend: &mut ext_php_rs::types::ZendObject) -> ext_php_rs::prelude::PhpResult<()> {{"
217 )
218 .ok();
219
220 let req_methods: Vec<&MethodDef> = spec.required_methods();
222 if !req_methods.is_empty() {
223 for method in &req_methods {
224 writeln!(out, " if backend.get_property(\"{}\").is_none() {{", method.name).ok();
225 writeln!(out, " return Err(ext_php_rs::exception::PhpException::default(").ok();
226 writeln!(
227 out,
228 " format!(\"Backend missing required method: {{}}\", \"{}\")",
229 method.name
230 )
231 .ok();
232 writeln!(out, " ).into());").ok();
233 writeln!(out, " }}").ok();
234 }
235 }
236
237 writeln!(out).ok();
238 writeln!(out, " let wrapper = {wrapper}::new(backend);").ok();
239 writeln!(
240 out,
241 " let arc: Rc<RefCell<dyn {trait_path}>> = Rc::new(RefCell::new(wrapper));"
242 )
243 .ok();
244 writeln!(out).ok();
245
246 writeln!(out, " let registry = {registry_getter}();").ok();
247 writeln!(out, " let mut registry = registry;").ok();
248 writeln!(
249 out,
250 " registry.register(arc).map_err(|e| ext_php_rs::exception::PhpException::default("
251 )
252 .ok();
253 writeln!(out, " format!(\"Failed to register backend: {{}}\", e)").ok();
254 writeln!(out, " ))?;").ok();
255 writeln!(out, " Ok(())").ok();
256 writeln!(out, "}}").ok();
257
258 out
259 }
260}
261
262pub fn gen_trait_bridge(
264 trait_type: &TypeDef,
265 bridge_cfg: &TraitBridgeConfig,
266 core_import: &str,
267 api: &ApiSurface,
268) -> String {
269 let type_paths: HashMap<String, String> = api
271 .types
272 .iter()
273 .map(|t| (t.name.clone(), t.rust_path.replace('-', "_")))
274 .chain(
275 api.enums
276 .iter()
277 .map(|e| (e.name.clone(), e.rust_path.replace('-', "_"))),
278 )
279 .collect();
280
281 let is_visitor_bridge = bridge_cfg.type_alias.is_some()
283 && bridge_cfg.register_fn.is_none()
284 && bridge_cfg.super_trait.is_none()
285 && trait_type.methods.iter().all(|m| m.has_default_impl);
286
287 if is_visitor_bridge {
288 let struct_name = format!("Php{}Bridge", bridge_cfg.trait_name);
289 let trait_path = trait_type.rust_path.replace('-', "_");
290 gen_visitor_bridge(trait_type, bridge_cfg, &struct_name, &trait_path, &type_paths)
291 } else {
292 let generator = PhpBridgeGenerator {
294 core_import: core_import.to_string(),
295 type_paths: type_paths.clone(),
296 };
297 let spec = TraitBridgeSpec {
298 trait_def: trait_type,
299 bridge_config: bridge_cfg,
300 core_import,
301 wrapper_prefix: "Php",
302 type_paths,
303 };
304 gen_bridge_all(&spec, &generator)
305 }
306}
307
308fn gen_visitor_bridge(
313 trait_type: &TypeDef,
314 _bridge_cfg: &TraitBridgeConfig,
315 struct_name: &str,
316 trait_path: &str,
317 type_paths: &HashMap<String, String>,
318) -> String {
319 let mut out = String::with_capacity(4096);
320 let core_crate = trait_path
321 .split("::")
322 .next()
323 .unwrap_or("html_to_markdown_rs")
324 .to_string();
325 writeln!(out, "fn nodecontext_to_php_array(").unwrap();
327 writeln!(out, " ctx: &{core_crate}::visitor::NodeContext,").unwrap();
328 writeln!(out, ") -> ext_php_rs::boxed::ZBox<ext_php_rs::types::ZendHashTable> {{").unwrap();
329 writeln!(out, " let mut arr = ext_php_rs::types::ZendHashTable::new();").unwrap();
330 writeln!(
331 out,
332 " arr.insert(\"nodeType\", ext_php_rs::types::Zval::try_from(format!(\"{{:?}}\", ctx.node_type)).unwrap_or_default()).ok();"
333 )
334 .unwrap();
335 writeln!(
336 out,
337 " arr.insert(\"tagName\", ext_php_rs::types::Zval::try_from(ctx.tag_name.clone()).unwrap_or_default()).ok();"
338 )
339 .unwrap();
340 writeln!(
341 out,
342 " arr.insert(\"depth\", ext_php_rs::types::Zval::try_from(ctx.depth as i64).unwrap_or_default()).ok();"
343 )
344 .unwrap();
345 writeln!(
346 out,
347 " arr.insert(\"indexInParent\", ext_php_rs::types::Zval::try_from(ctx.index_in_parent as i64).unwrap_or_default()).ok();"
348 )
349 .unwrap();
350 writeln!(
351 out,
352 " arr.insert(\"isInline\", ext_php_rs::types::Zval::try_from(ctx.is_inline).unwrap_or_default()).ok();"
353 )
354 .unwrap();
355 writeln!(out, " if let Some(ref pt) = ctx.parent_tag {{").unwrap();
356 writeln!(
357 out,
358 " arr.insert(\"parentTag\", ext_php_rs::types::Zval::try_from(pt.clone()).unwrap_or_default()).ok();"
359 )
360 .unwrap();
361 writeln!(out, " }}").unwrap();
362 writeln!(out, " let mut attrs = ext_php_rs::types::ZendHashTable::new();").unwrap();
363 writeln!(out, " for (k, v) in &ctx.attributes {{").unwrap();
364 writeln!(
365 out,
366 " attrs.insert(k.as_str(), ext_php_rs::types::Zval::try_from(v.clone()).unwrap_or_default()).ok();"
367 )
368 .unwrap();
369 writeln!(out, " }}").unwrap();
370 writeln!(out, " let mut attrs_zval = ext_php_rs::types::Zval::new();").unwrap();
371 writeln!(out, " attrs_zval.set_hashtable(attrs);").unwrap();
372 writeln!(out, " arr.insert(\"attributes\", attrs_zval).ok();").unwrap();
373 writeln!(out, " arr").unwrap();
374 writeln!(out, "}}").unwrap();
375 writeln!(out).unwrap();
376
377 writeln!(out, "pub struct {struct_name} {{").unwrap();
381 writeln!(out, " php_obj: *mut ext_php_rs::types::ZendObject,").unwrap();
382 writeln!(out, " cached_name: String,").unwrap();
383 writeln!(out, "}}").unwrap();
384 writeln!(out).unwrap();
385
386 writeln!(out, "// SAFETY: PHP objects are single-threaded; the bridge is used").unwrap();
389 writeln!(out, "// only within a single PHP request, never across threads.").unwrap();
390 writeln!(out, "unsafe impl Send for {struct_name} {{}}").unwrap();
391 writeln!(out, "unsafe impl Sync for {struct_name} {{}}").unwrap();
392 writeln!(out).unwrap();
393
394 writeln!(out, "impl Clone for {struct_name} {{").unwrap();
395 writeln!(out, " fn clone(&self) -> Self {{").unwrap();
396 writeln!(out, " Self {{").unwrap();
397 writeln!(out, " php_obj: self.php_obj,").unwrap();
398 writeln!(out, " cached_name: self.cached_name.clone(),").unwrap();
399 writeln!(out, " }}").unwrap();
400 writeln!(out, " }}").unwrap();
401 writeln!(out, "}}").unwrap();
402 writeln!(out).unwrap();
403
404 writeln!(out, "impl std::fmt::Debug for {struct_name} {{").unwrap();
406 writeln!(
407 out,
408 " fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {{"
409 )
410 .unwrap();
411 writeln!(out, " write!(f, \"{struct_name}\")").unwrap();
412 writeln!(out, " }}").unwrap();
413 writeln!(out, "}}").unwrap();
414 writeln!(out).unwrap();
415
416 writeln!(out, "impl {struct_name} {{").unwrap();
420 writeln!(
421 out,
422 " pub fn new(php_obj: &mut ext_php_rs::types::ZendObject) -> Self {{"
423 )
424 .unwrap();
425 writeln!(out, " let cached_name = php_obj").unwrap();
426 writeln!(out, " .try_call_method(\"name\", vec![])").unwrap();
427 writeln!(out, " .ok()").unwrap();
428 writeln!(out, " .and_then(|v| v.string())").unwrap();
429 writeln!(out, " .unwrap_or(\"unknown\".into())").unwrap();
430 writeln!(out, " .to_string();").unwrap();
431 writeln!(out, " Self {{ php_obj: php_obj as *mut _, cached_name }}").unwrap();
432 writeln!(out, " }}").unwrap();
433 writeln!(out, "}}").unwrap();
434 writeln!(out).unwrap();
435
436 writeln!(out, "impl {trait_path} for {struct_name} {{").unwrap();
438 for method in &trait_type.methods {
439 if method.trait_source.is_some() {
440 continue;
441 }
442 gen_visitor_method_php(&mut out, method, type_paths);
443 }
444 writeln!(out, "}}").unwrap();
445 writeln!(out).unwrap();
446
447 out
448}
449
450fn visitor_param_type(ty: &TypeRef, is_ref: bool, optional: bool, tp: &HashMap<String, String>) -> String {
452 if optional && matches!(ty, TypeRef::String) && is_ref {
453 return "Option<&str>".to_string();
454 }
455 if is_ref {
456 if let TypeRef::Vec(inner) = ty {
457 let inner_str = param_type(inner, "", false, tp);
458 return format!("&[{inner_str}]");
459 }
460 }
461 param_type(ty, "", is_ref, tp)
462}
463
464fn gen_visitor_method_php(out: &mut String, method: &MethodDef, type_paths: &HashMap<String, String>) {
466 let name = &method.name;
467 let php_name = to_camel_case(name);
468
469 let mut sig_parts = vec!["&mut self".to_string()];
470 for p in &method.params {
471 let ty_str = visitor_param_type(&p.ty, p.is_ref, p.optional, type_paths);
472 sig_parts.push(format!("{}: {}", p.name, ty_str));
473 }
474 let sig = sig_parts.join(", ");
475
476 let ret_ty = match &method.return_type {
477 TypeRef::Named(n) => type_paths.get(n).cloned().unwrap_or_else(|| n.clone()),
478 other => param_type(other, "", false, type_paths),
479 };
480
481 writeln!(out, " fn {name}({sig}) -> {ret_ty} {{").unwrap();
482
483 writeln!(
485 out,
486 " // SAFETY: php_obj is a valid ZendObject pointer for the duration of this call."
487 )
488 .unwrap();
489 writeln!(out, " let php_obj_ref = unsafe {{ &mut *self.php_obj }};").unwrap();
490
491 let has_args = !method.params.is_empty();
493 if has_args {
494 writeln!(out, " let mut args: Vec<ext_php_rs::types::Zval> = Vec::new();").unwrap();
495 for p in &method.params {
496 if let TypeRef::Named(n) = &p.ty {
497 if n == "NodeContext" {
498 writeln!(
499 out,
500 " let ctx_arr = nodecontext_to_php_array({}{});",
501 if p.is_ref { "" } else { "&" },
502 p.name
503 )
504 .unwrap();
505 writeln!(
506 out,
507 " args.push(ext_php_rs::convert::IntoZval::into_zval(ctx_arr, false).unwrap_or_default());"
508 )
509 .unwrap();
510 continue;
511 }
512 }
513 if p.optional && matches!(&p.ty, TypeRef::String) && p.is_ref {
516 writeln!(
517 out,
518 " args.push(match {0} {{ Some(s) => ext_php_rs::types::Zval::try_from(s.to_string()).unwrap_or_default(), None => ext_php_rs::types::Zval::new() }});",
519 p.name
520 )
521 .unwrap();
522 continue;
523 }
524 if matches!(&p.ty, TypeRef::String) {
525 if p.is_ref {
526 writeln!(
527 out,
528 " args.push(ext_php_rs::types::Zval::try_from({}.to_string()).unwrap_or_default());",
529 p.name
530 )
531 .unwrap();
532 } else {
533 writeln!(
534 out,
535 " args.push(ext_php_rs::types::Zval::try_from({}.clone()).unwrap_or_default());",
536 p.name
537 )
538 .unwrap();
539 }
540 continue;
541 }
542 if matches!(&p.ty, TypeRef::Primitive(alef_core::ir::PrimitiveType::Bool)) {
543 writeln!(
544 out,
545 " {{ let mut _zv = ext_php_rs::types::Zval::new(); _zv.set_bool({}); args.push(_zv); }}",
546 p.name
547 )
548 .unwrap();
549 continue;
550 }
551 writeln!(
553 out,
554 " args.push(ext_php_rs::types::Zval::try_from(format!(\"{{:?}}\", {})).unwrap_or_default());",
555 p.name
556 )
557 .unwrap();
558 }
559 }
560
561 if has_args {
565 writeln!(
566 out,
567 " let dyn_args: Vec<&dyn ext_php_rs::convert::IntoZvalDyn> = args.iter().map(|z| z as &dyn ext_php_rs::convert::IntoZvalDyn).collect();"
568 )
569 .unwrap();
570 }
571 let args_expr = if has_args { "dyn_args" } else { "vec![]" };
572 writeln!(
573 out,
574 " let result = php_obj_ref.try_call_method(\"{php_name}\", {args_expr});"
575 )
576 .unwrap();
577
578 writeln!(out, " match result {{").unwrap();
580 writeln!(out, " Err(_) => {ret_ty}::Continue,").unwrap();
581 writeln!(out, " Ok(val) => {{").unwrap();
582 writeln!(
583 out,
584 " let s = val.string().unwrap_or_default().to_lowercase();"
585 )
586 .unwrap();
587 writeln!(out, " match s.as_str() {{").unwrap();
588 writeln!(out, " \"continue\" => {ret_ty}::Continue,").unwrap();
589 writeln!(out, " \"skip\" => {ret_ty}::Skip,").unwrap();
590 writeln!(
591 out,
592 " \"preserve_html\" | \"preservehtml\" => {ret_ty}::PreserveHtml,"
593 )
594 .unwrap();
595 writeln!(out, " other => {ret_ty}::Custom(other.to_string()),").unwrap();
596 writeln!(out, " }}").unwrap();
597 writeln!(out, " }}").unwrap();
598 writeln!(out, " }}").unwrap();
599 writeln!(out, " }}").unwrap();
600 writeln!(out).unwrap();
601}
602
603fn to_camel_case(name: &str) -> String {
605 let mut result = String::with_capacity(name.len());
606 let mut capitalize_next = false;
607 for (i, c) in name.chars().enumerate() {
608 if c == '_' {
609 capitalize_next = true;
610 } else if capitalize_next {
611 result.extend(c.to_uppercase());
612 capitalize_next = false;
613 } else if i == 0 {
614 result.extend(c.to_lowercase());
615 } else {
616 result.push(c);
617 }
618 }
619 result
620}
621
622fn param_type(ty: &TypeRef, ci: &str, is_ref: bool, tp: &HashMap<String, String>) -> String {
624 match ty {
625 TypeRef::Bytes if is_ref => "&[u8]".into(),
626 TypeRef::Bytes => "Vec<u8>".into(),
627 TypeRef::String if is_ref => "&str".into(),
628 TypeRef::String => "String".into(),
629 TypeRef::Path if is_ref => "&std::path::Path".into(),
630 TypeRef::Path => "std::path::PathBuf".into(),
631 TypeRef::Named(n) => {
632 let qualified = tp.get(n).cloned().unwrap_or_else(|| format!("{ci}::{n}"));
633 if is_ref { format!("&{qualified}") } else { qualified }
634 }
635 TypeRef::Vec(inner) => format!("Vec<{}>", param_type(inner, ci, false, tp)),
636 TypeRef::Optional(inner) => format!("Option<{}>", param_type(inner, ci, false, tp)),
637 TypeRef::Primitive(p) => prim(p).into(),
638 TypeRef::Unit => "()".into(),
639 TypeRef::Char => "char".into(),
640 TypeRef::Map(k, v) => format!(
641 "std::collections::HashMap<{}, {}>",
642 param_type(k, ci, false, tp),
643 param_type(v, ci, false, tp)
644 ),
645 TypeRef::Json => "serde_json::Value".into(),
646 TypeRef::Duration => "std::time::Duration".into(),
647 }
648}
649
650fn prim(p: &alef_core::ir::PrimitiveType) -> &'static str {
651 use alef_core::ir::PrimitiveType::*;
652 match p {
653 Bool => "bool",
654 U8 => "u8",
655 U16 => "u16",
656 U32 => "u32",
657 U64 => "u64",
658 I8 => "i8",
659 I16 => "i16",
660 I32 => "i32",
661 I64 => "i64",
662 F32 => "f32",
663 F64 => "f64",
664 Usize => "usize",
665 Isize => "isize",
666 }
667}
668
669pub fn find_bridge_param<'a>(
674 func: &alef_core::ir::FunctionDef,
675 bridges: &'a [TraitBridgeConfig],
676) -> Option<(usize, &'a TraitBridgeConfig)> {
677 for (idx, param) in func.params.iter().enumerate() {
678 let named = match ¶m.ty {
679 TypeRef::Named(n) => Some(n.as_str()),
680 TypeRef::Optional(inner) => {
681 if let TypeRef::Named(n) = inner.as_ref() {
682 Some(n.as_str())
683 } else {
684 None
685 }
686 }
687 _ => None,
688 };
689 for bridge in bridges {
690 if let Some(type_name) = named {
691 if bridge.type_alias.as_deref() == Some(type_name) {
692 return Some((idx, bridge));
693 }
694 }
695 if bridge.param_name.as_deref() == Some(param.name.as_str()) {
696 return Some((idx, bridge));
697 }
698 }
699 }
700 None
701}
702
703#[allow(clippy::too_many_arguments)]
706pub fn gen_bridge_function(
707 func: &alef_core::ir::FunctionDef,
708 bridge_param_idx: usize,
709 bridge_cfg: &TraitBridgeConfig,
710 mapper: &dyn alef_codegen::type_mapper::TypeMapper,
711 opaque_types: &ahash::AHashSet<String>,
712 core_import: &str,
713) -> String {
714 use alef_core::ir::TypeRef;
715
716 let struct_name = format!("Php{}Bridge", bridge_cfg.trait_name);
717 let handle_path = format!("{core_import}::visitor::VisitorHandle");
718 let param_name = &func.params[bridge_param_idx].name;
719 let bridge_param = &func.params[bridge_param_idx];
720 let is_optional = bridge_param.optional || matches!(&bridge_param.ty, TypeRef::Optional(_));
721
722 let mut sig_parts = Vec::new();
724 for (idx, p) in func.params.iter().enumerate() {
725 if idx == bridge_param_idx {
726 let php_obj_ty = "&mut ext_php_rs::types::ZendObject";
730 if is_optional {
731 sig_parts.push(format!("{}: Option<{php_obj_ty}>", p.name));
732 } else {
733 sig_parts.push(format!("{}: {php_obj_ty}", p.name));
734 }
735 } else {
736 let promoted = idx > bridge_param_idx || func.params[..idx].iter().any(|pp| pp.optional);
737 let base = mapper.map_type(&p.ty);
738 let ty = match &p.ty {
741 TypeRef::Named(n) if !opaque_types.contains(n.as_str()) => {
742 if p.optional || promoted {
743 format!("Option<&mut {base}>")
744 } else {
745 format!("&mut {base}")
746 }
747 }
748 TypeRef::Optional(inner) => {
749 if let TypeRef::Named(n) = inner.as_ref() {
750 if !opaque_types.contains(n.as_str()) {
751 format!("Option<&mut {base}>")
752 } else if p.optional || promoted {
753 format!("Option<{base}>")
754 } else {
755 base
756 }
757 } else if p.optional || promoted {
758 format!("Option<{base}>")
759 } else {
760 base
761 }
762 }
763 _ => {
764 if p.optional || promoted {
765 format!("Option<{base}>")
766 } else {
767 base
768 }
769 }
770 };
771 sig_parts.push(format!("{}: {}", p.name, ty));
772 }
773 }
774
775 let params_str = sig_parts.join(", ");
776 let return_type = mapper.map_type(&func.return_type);
777 let ret = mapper.wrap_return(&return_type, func.error_type.is_some());
778
779 let err_conv = ".map_err(|e| ext_php_rs::exception::PhpException::default(e.to_string()))";
780
781 let bridge_wrap = if is_optional {
783 format!(
784 "let {param_name} = {param_name}.map(|v| {{\n \
785 let bridge = {struct_name}::new(v);\n \
786 std::rc::Rc::new(std::cell::RefCell::new(bridge)) as {handle_path}\n \
787 }});"
788 )
789 } else {
790 format!(
791 "let {param_name} = {{\n \
792 let bridge = {struct_name}::new({param_name});\n \
793 std::rc::Rc::new(std::cell::RefCell::new(bridge)) as {handle_path}\n \
794 }};"
795 )
796 };
797
798 let serde_bindings: String = func
800 .params
801 .iter()
802 .enumerate()
803 .filter(|(idx, p)| {
804 if *idx == bridge_param_idx {
805 return false;
806 }
807 let named = match &p.ty {
808 TypeRef::Named(n) => Some(n.as_str()),
809 TypeRef::Optional(inner) => {
810 if let TypeRef::Named(n) = inner.as_ref() {
811 Some(n.as_str())
812 } else {
813 None
814 }
815 }
816 _ => None,
817 };
818 named.is_some_and(|n| !opaque_types.contains(n))
819 })
820 .map(|(_, p)| {
821 let name = &p.name;
822 let core_path = format!(
823 "{core_import}::{}",
824 match &p.ty {
825 TypeRef::Named(n) => n.clone(),
826 TypeRef::Optional(inner) =>
827 if let TypeRef::Named(n) = inner.as_ref() {
828 n.clone()
829 } else {
830 String::new()
831 },
832 _ => String::new(),
833 }
834 );
835 if p.optional || matches!(&p.ty, TypeRef::Optional(_)) {
836 format!(
837 "let {name}_core: Option<{core_path}> = {name}.map(|v| {{\n \
838 let json = serde_json::to_string(&v){err_conv}?;\n \
839 serde_json::from_str(&json){err_conv}\n \
840 }}).transpose()?;\n "
841 )
842 } else {
843 format!(
844 "let {name}_json = serde_json::to_string(&{name}){err_conv}?;\n \
845 let {name}_core: {core_path} = serde_json::from_str(&{name}_json){err_conv}?;\n "
846 )
847 }
848 })
849 .collect();
850
851 let call_args: Vec<String> = func
853 .params
854 .iter()
855 .enumerate()
856 .map(|(idx, p)| {
857 if idx == bridge_param_idx {
858 return p.name.clone();
859 }
860 match &p.ty {
861 TypeRef::Named(n) if opaque_types.contains(n.as_str()) => {
862 if p.optional {
863 format!("{}.as_ref().map(|v| &v.inner)", p.name)
864 } else {
865 format!("&{}.inner", p.name)
866 }
867 }
868 TypeRef::Named(_) => format!("{}_core", p.name),
869 TypeRef::Optional(inner) => {
870 if let TypeRef::Named(n) = inner.as_ref() {
871 if opaque_types.contains(n.as_str()) {
872 format!("{}.as_ref().map(|v| &v.inner)", p.name)
873 } else {
874 format!("{}_core", p.name)
875 }
876 } else {
877 p.name.clone()
878 }
879 }
880 TypeRef::String | TypeRef::Char => {
881 if p.is_ref {
882 format!("&{}", p.name)
883 } else {
884 p.name.clone()
885 }
886 }
887 _ => p.name.clone(),
888 }
889 })
890 .collect();
891 let call_args_str = call_args.join(", ");
892
893 let core_fn_path = {
894 let path = func.rust_path.replace('-', "_");
895 if path.starts_with(core_import) {
896 path
897 } else {
898 format!("{core_import}::{}", func.name)
899 }
900 };
901 let core_call = format!("{core_fn_path}({call_args_str})");
902
903 let return_wrap = match &func.return_type {
904 TypeRef::Named(name) if opaque_types.contains(name.as_str()) => {
905 format!("{name} {{ inner: std::sync::Arc::new(val) }}")
906 }
907 TypeRef::Named(_) => "val.into()".to_string(),
908 TypeRef::String | TypeRef::Bytes => "val.into()".to_string(),
909 _ => "val".to_string(),
910 };
911
912 let body = if func.error_type.is_some() {
913 if return_wrap == "val" {
914 format!("{bridge_wrap}\n {serde_bindings}{core_call}{err_conv}")
915 } else {
916 format!("{bridge_wrap}\n {serde_bindings}{core_call}.map(|val| {return_wrap}){err_conv}")
917 }
918 } else {
919 format!("{bridge_wrap}\n {serde_bindings}{core_call}")
920 };
921
922 let func_name = &func.name;
923 let mut out = String::with_capacity(1024);
924 if func.error_type.is_some() {
925 writeln!(out, "#[allow(clippy::missing_errors_doc)]").ok();
926 }
927 writeln!(out, "pub fn {func_name}({params_str}) -> {ret} {{").ok();
928 writeln!(out, " {body}").ok();
929 writeln!(out, "}}").ok();
930
931 out
932}