use crate::backends::php::gen_bindings::functions::{
PhpParamTypeSets, gen_async_function_as_static_method, gen_function_as_static_method,
};
use crate::backends::php::gen_bindings::helpers::{
gen_enum_tainted_from_binding_to_core, gen_tokio_runtime, has_enum_named_field, references_named_type,
};
use crate::backends::php::gen_bindings::rust_items::{
gen_streaming_adapter_facade_method, generate_config_m4, has_no_arg_new_returning_self,
php_variant_wrapper_constructor,
};
use crate::backends::php::gen_bindings::serde_defaults::gen_serde_defaults_module;
use crate::backends::php::gen_bindings::types::{
self, gen_enum_constants, gen_flat_data_enum, gen_flat_data_enum_from_impls, gen_flat_data_enum_methods,
gen_php_struct, is_tagged_data_enum, is_untagged_data_enum,
};
use crate::backends::php::naming::php_autoload_namespace;
use crate::backends::php::type_map::PhpMapper;
use crate::codegen::builder::RustFileBuilder;
use crate::codegen::conversions::ConversionConfig;
use crate::codegen::generators::{self, AsyncPattern, RustBindingConfig};
use crate::codegen::shared::binding_fields;
use crate::core::backend::GeneratedFile;
use crate::core::config::{Language, ResolvedCrateConfig, detect_serde_available, resolve_output_dir};
use crate::core::ir::{ApiSurface, TypeRef};
use ahash::AHashSet;
use heck::{ToLowerCamelCase, ToPascalCase};
use minijinja::context;
use std::collections::HashMap;
use std::path::PathBuf;
fn binding_config(core_import: &str, has_serde: bool) -> RustBindingConfig<'_> {
RustBindingConfig {
struct_attrs: &["php_class"],
field_attrs: &[],
struct_derives: &["Clone"],
method_block_attr: Some("php_impl"),
constructor_attr: "",
static_attr: None,
function_attr: "#[php_function]",
enum_attrs: &[],
enum_derives: &[],
needs_signature: false,
signature_prefix: "",
signature_suffix: "",
core_import,
async_pattern: AsyncPattern::TokioBlockOn,
has_serde,
type_name_prefix: "",
option_duration_on_defaults: true,
opaque_type_names: &[],
skip_impl_constructor: false,
cast_uints_to_i32: false,
cast_large_ints_to_f64: false,
named_non_opaque_params_by_ref: false,
lossy_skip_types: &[],
serializable_opaque_type_names: &[],
never_skip_cfg_field_names: &[],
emit_delegating_default_impl: true,
skip_methods_when_not_delegatable: false,
}
}
pub(super) fn generate_bindings(api: &ApiSurface, config: &ResolvedCrateConfig) -> anyhow::Result<Vec<GeneratedFile>> {
let data_enum_names: AHashSet<String> = api
.enums
.iter()
.filter(|e| is_tagged_data_enum(e))
.map(|e| e.name.clone())
.collect();
let untagged_data_enum_names: AHashSet<String> = api
.enums
.iter()
.filter(|e| is_untagged_data_enum(e))
.map(|e| e.name.clone())
.collect();
let enum_names: AHashSet<String> = api
.enums
.iter()
.filter(|e| !is_tagged_data_enum(e) && !is_untagged_data_enum(e))
.map(|e| e.name.clone())
.collect();
let mapper = PhpMapper {
enum_names: enum_names.clone(),
data_enum_names: data_enum_names.clone(),
untagged_data_enum_names: untagged_data_enum_names.clone(),
};
let default_types: AHashSet<String> = api
.types
.iter()
.filter(|t| t.has_default && !t.is_opaque)
.map(|t| t.name.clone())
.collect();
let core_import = config.core_import_name();
let lang_rename_all = config.serde_rename_all_for_language(Language::Php);
let php_config = config.php.as_ref();
let exclude_functions = php_config.map(|c| c.exclude_functions.clone()).unwrap_or_default();
let exclude_types = php_config.map(|c| c.exclude_types.clone()).unwrap_or_default();
let output_dir = resolve_output_dir(config.output_paths.get("php"), &config.name, "crates/{name}-php/src/");
let has_serde = detect_serde_available(&output_dir);
let bridge_type_aliases_php: Vec<String> = config
.trait_bridges
.iter()
.filter_map(|b| b.type_alias.clone())
.collect();
let bridge_type_aliases_set: AHashSet<String> = bridge_type_aliases_php.iter().cloned().collect();
let mut opaque_names_vec_php: Vec<String> = api
.types
.iter()
.filter(|t| t.is_opaque)
.map(|t| t.name.clone())
.collect();
opaque_names_vec_php.extend(bridge_type_aliases_php);
let mut cfg = binding_config(&core_import, has_serde);
cfg.opaque_type_names = &opaque_names_vec_php;
let never_skip_cfg_field_names: Vec<String> = config
.trait_bridges
.iter()
.filter_map(|b| {
if b.bind_via == crate::core::config::BridgeBinding::OptionsField {
b.resolved_options_field().map(|s| s.to_string())
} else {
None
}
})
.collect();
cfg.never_skip_cfg_field_names = &never_skip_cfg_field_names;
let mut builder = RustFileBuilder::new().with_generated_header();
builder.add_inner_attribute("allow(dead_code, unused_imports, unused_variables, missing_docs)");
builder.add_inner_attribute("allow(unsafe_code)");
builder.add_inner_attribute("allow(non_snake_case)");
builder.add_inner_attribute("allow(clippy::too_many_arguments, clippy::let_unit_value, clippy::needless_borrow, clippy::map_identity, clippy::just_underscores_and_digits, clippy::unnecessary_cast, clippy::unused_unit, clippy::unwrap_or_default, clippy::derivable_impls, clippy::needless_borrows_for_generic_args, clippy::unnecessary_fallible_conversions, clippy::arc_with_non_send_sync, clippy::collapsible_if, clippy::clone_on_copy, clippy::should_implement_trait, clippy::useless_conversion)");
builder.add_import("ext_php_rs::prelude::*");
if has_serde {
builder.add_import("serde_json");
}
for trait_path in generators::collect_trait_imports(api) {
builder.add_import(&trait_path);
}
let has_maps = api.types.iter().any(|t| {
t.fields
.iter()
.any(|f| matches!(&f.ty, crate::core::ir::TypeRef::Map(_, _)))
}) || api
.functions
.iter()
.any(|f| matches!(&f.return_type, crate::core::ir::TypeRef::Map(_, _)));
if has_maps {
builder.add_import("std::collections::HashMap");
}
builder.add_item(
"#[derive(Debug, Clone, Default)]\n\
pub struct PhpBytes(pub Vec<u8>);\n\
\n\
impl<'a> ext_php_rs::convert::FromZval<'a> for PhpBytes {\n \
const TYPE: ext_php_rs::flags::DataType = ext_php_rs::flags::DataType::String;\n \
fn from_zval(zval: &'a ext_php_rs::types::Zval) -> Option<Self> {\n \
zval.zend_str().map(|zs| PhpBytes(zs.as_bytes().to_vec()))\n \
}\n\
}\n\
\n\
impl From<PhpBytes> for Vec<u8> {\n \
fn from(b: PhpBytes) -> Self { b.0 }\n\
}\n\
\n\
impl From<Vec<u8>> for PhpBytes {\n \
fn from(v: Vec<u8>) -> Self { PhpBytes(v) }\n\
}\n",
);
let custom_mods = config.custom_modules.for_language(Language::Php);
for module in custom_mods {
builder.add_item(&format!("pub mod {module};"));
}
let has_async =
api.functions.iter().any(|f| f.is_async) || api.types.iter().any(|t| t.methods.iter().any(|m| m.is_async));
if has_async {
builder.add_item(&gen_tokio_runtime());
}
let opaque_types: AHashSet<String> = api
.types
.iter()
.filter(|t| t.is_opaque)
.map(|t| t.name.clone())
.collect();
if !opaque_types.is_empty() {
builder.add_import("std::sync::Arc");
}
let mutex_types: AHashSet<String> = api
.types
.iter()
.filter(|t| t.is_opaque && crate::codegen::generators::type_needs_mutex(t))
.map(|t| t.name.clone())
.collect();
if !mutex_types.is_empty() {
builder.add_import("std::sync::Mutex");
}
let extension_name = config.php_extension_name();
let php_namespace = php_autoload_namespace(config);
let adapter_bodies = crate::adapters::build_adapter_bodies(config, Language::Php)?;
let streaming_method_keys: AHashSet<String> = config
.adapters
.iter()
.filter(|a| matches!(a.pattern, crate::core::config::AdapterPattern::Streaming))
.filter_map(|a| a.owner_type.as_deref().map(|owner| format!("{owner}.{}", a.name)))
.collect();
for adapter in &config.adapters {
match adapter.pattern {
crate::core::config::AdapterPattern::Streaming => {
let key = crate::adapters::stream_struct_key(adapter);
if let Some(struct_code) = adapter_bodies.get(&key) {
builder.add_item(struct_code);
}
}
crate::core::config::AdapterPattern::CallbackBridge => {
let struct_key = format!("{}.__bridge_struct__", adapter.name);
let impl_key = format!("{}.__bridge_impl__", adapter.name);
if let Some(struct_code) = adapter_bodies.get(&struct_key) {
builder.add_item(struct_code);
}
if let Some(impl_code) = adapter_bodies.get(&impl_key) {
builder.add_item(impl_code);
}
}
_ => {}
}
}
for typ in api
.types
.iter()
.filter(|typ| !typ.is_trait && !exclude_types.contains(&typ.name))
{
if typ.is_opaque {
let ns_escaped = php_namespace.replace('\\', "\\\\");
let php_name_attr = format!("php(name = \"{}\\\\{}\")", ns_escaped, typ.name);
let opaque_attr_arr = ["php_class", php_name_attr.as_str()];
let opaque_cfg = RustBindingConfig {
struct_attrs: &opaque_attr_arr,
..cfg
};
builder.add_item(&generators::gen_opaque_struct(typ, &opaque_cfg));
builder.add_item(&types::gen_opaque_struct_methods_with_exclude(
api,
typ,
&mapper,
&opaque_types,
&core_import,
&adapter_bodies,
&mutex_types,
&streaming_method_keys,
&config.trait_bridges,
));
if has_no_arg_new_returning_self(typ) {
let default_impl = format!(
"impl Default for {} {{\n fn default() -> Self {{\n Self::new()\n }}\n}}",
typ.name
);
builder.add_item(&default_impl);
}
if let Some(ctor) = config.client_constructors.get(&typ.name) {
let ctor_body = generators::gen_opaque_constructor(ctor, &typ.name, &core_import, "#[php_method]");
let ctor_impl = format!("#[php_impl]\nimpl {} {{\n{}}}", typ.name, ctor_body);
builder.add_item(&ctor_impl);
} else if typ.is_variant_wrapper {
if let Some(ctor) = php_variant_wrapper_constructor(typ, &mapper, &core_import) {
builder.add_item(&ctor);
}
}
} else {
builder.add_item(&gen_php_struct(
typ,
&mapper,
&cfg,
Some(&php_namespace),
&enum_names,
&lang_rename_all,
));
builder.add_item(&types::gen_struct_methods_with_exclude(
typ,
&mapper,
has_serde,
&core_import,
&opaque_types,
&enum_names,
&api.enums,
&exclude_functions,
&bridge_type_aliases_set,
&never_skip_cfg_field_names,
&mutex_types,
));
}
}
for enum_def in &api.enums {
if is_tagged_data_enum(enum_def) {
builder.add_item(&gen_flat_data_enum(enum_def, &mapper, Some(&php_namespace)));
builder.add_item(&gen_flat_data_enum_methods(enum_def, &mapper));
} else {
builder.add_item(&gen_enum_constants(enum_def, Some(&php_namespace)));
}
}
let included_functions: Vec<_> = api
.functions
.iter()
.filter(|f| !exclude_functions.contains(&f.name))
.collect();
if !included_functions.is_empty() || !config.trait_bridges.is_empty() {
let facade_class_name = extension_name.to_pascal_case();
let mut method_items: Vec<String> = Vec::new();
for func in included_functions {
if crate::codegen::generators::trait_bridge::is_trait_bridge_managed_fn(&func.name, &config.trait_bridges) {
continue;
}
let bridge_param = crate::backends::php::trait_bridge::find_bridge_param(func, &config.trait_bridges);
if let Some((param_idx, bridge_cfg)) = bridge_param {
let bridge_handle_path =
crate::codegen::generators::trait_bridge::bridge_handle_path(api, bridge_cfg, &core_import);
method_items.push(crate::backends::php::trait_bridge::gen_bridge_function(
func,
param_idx,
bridge_cfg,
&mapper,
&opaque_types,
&core_import,
&bridge_handle_path,
));
} else if func.is_async {
method_items.push(gen_async_function_as_static_method(
func,
&mapper,
PhpParamTypeSets {
opaque: &opaque_types,
default: &default_types,
enums: &enum_names,
},
&core_import,
&config.trait_bridges,
&mutex_types,
));
} else {
method_items.push(gen_function_as_static_method(
func,
&mapper,
PhpParamTypeSets {
opaque: &opaque_types,
default: &default_types,
enums: &enum_names,
},
&core_import,
&config.trait_bridges,
has_serde,
&mutex_types,
));
}
}
for adapter in &config.adapters {
if !matches!(adapter.pattern, crate::core::config::AdapterPattern::Streaming) {
continue;
}
if adapter.owner_type.is_none() {
continue;
}
method_items.push(gen_streaming_adapter_facade_method(
adapter,
&mapper,
&opaque_types,
&core_import,
));
}
for bridge_cfg in &config.trait_bridges {
if let Some(register_fn) = bridge_cfg.register_fn.as_deref() {
let php_name = register_fn.to_lower_camel_case();
method_items.push(format!(
"#[php(name = \"{php_name}\")]\n\
pub fn {register_fn}(backend: &mut ext_php_rs::types::ZendObject) -> ext_php_rs::prelude::PhpResult<()> {{\n \
crate::{}(backend)\n}}",
register_fn
));
}
if let Some(unregister_fn) = bridge_cfg.unregister_fn.as_deref() {
let php_name = unregister_fn.to_lower_camel_case();
method_items.push(format!(
"#[php(name = \"{php_name}\")]\n\
pub fn {unregister_fn}(name: String) -> ext_php_rs::prelude::PhpResult<()> {{\n \
crate::{unregister_fn}(name)\n}}",
));
}
if let Some(clear_fn) = bridge_cfg.clear_fn.as_deref() {
let php_name = clear_fn.to_lower_camel_case();
method_items.push(format!(
"#[php(name = \"{php_name}\")]\n\
pub fn {clear_fn}() -> ext_php_rs::prelude::PhpResult<()> {{\n \
crate::{clear_fn}()\n}}",
));
}
}
let methods_joined = method_items
.iter()
.map(|m| {
m.lines()
.map(|l| {
if l.is_empty() {
String::new()
} else {
format!(" {l}")
}
})
.collect::<Vec<_>>()
.join("\n")
})
.collect::<Vec<_>>()
.join("\n\n");
let php_api_class_name = format!("{facade_class_name}Api");
let ns_escaped_facade = php_namespace.replace('\\', "\\\\");
let php_name_attr = format!("php(name = \"{}\\\\{}\")", ns_escaped_facade, php_api_class_name);
let facade_struct = format!(
"#[php_class]\n#[{php_name_attr}]\npub struct {facade_class_name}Api;\n\n#[php_impl]\nimpl {facade_class_name}Api {{\n{methods_joined}\n}}"
);
builder.add_item(&facade_struct);
for bridge_cfg in &config.trait_bridges {
if let Some(trait_type) = api.types.iter().find(|t| t.is_trait && t.name == bridge_cfg.trait_name) {
let bridge = crate::backends::php::trait_bridge::gen_trait_bridge(
trait_type,
bridge_cfg,
&core_import,
&config.error_type_name(),
&config.error_constructor_expr(),
api,
);
for imp in &bridge.imports {
builder.add_import(imp);
}
builder.add_item(&bridge.code);
}
}
}
let convertible = crate::codegen::conversions::convertible_types(api);
let core_to_binding = crate::codegen::conversions::core_to_binding_convertible_types(api);
let input_types = crate::codegen::conversions::input_type_names(api);
let enum_names_ref = &mapper.enum_names;
let bridge_skip_types: Vec<String> = config
.trait_bridges
.iter()
.filter(|b| !matches!(b.bind_via, crate::core::config::BridgeBinding::OptionsField))
.filter_map(|b| b.type_alias.clone())
.collect();
let trait_bridge_arc_wrapper_field_names: Vec<String> = config
.trait_bridges
.iter()
.filter(|b| b.bind_via == crate::core::config::BridgeBinding::OptionsField)
.filter_map(|b| b.resolved_options_field().map(String::from))
.collect();
let mut conv_opaque_types: AHashSet<String> = opaque_types.clone();
for bridge in &config.trait_bridges {
if let Some(alias) = &bridge.type_alias {
conv_opaque_types.insert(alias.clone());
}
}
let php_conv_config = ConversionConfig {
cast_large_ints_to_i64: true,
enum_string_names: Some(enum_names_ref),
untagged_data_enum_names: Some(&mapper.untagged_data_enum_names),
json_as_value: true,
include_cfg_metadata: false,
option_duration_on_defaults: true,
from_binding_skip_types: &bridge_skip_types,
never_skip_cfg_field_names: &never_skip_cfg_field_names,
opaque_types: Some(&conv_opaque_types),
trait_bridge_arc_wrapper_field_names: &trait_bridge_arc_wrapper_field_names,
..Default::default()
};
let mut enum_tainted: AHashSet<String> = AHashSet::new();
for typ in api.types.iter().filter(|typ| !typ.is_trait) {
if has_enum_named_field(typ, enum_names_ref) {
enum_tainted.insert(typ.name.clone());
}
}
let mut changed = true;
while changed {
changed = false;
for typ in api.types.iter().filter(|typ| !typ.is_trait) {
if !enum_tainted.contains(&typ.name)
&& binding_fields(&typ.fields).any(|f| references_named_type(&f.ty, &enum_tainted))
{
enum_tainted.insert(typ.name.clone());
changed = true;
}
}
}
for typ in api.types.iter().filter(|typ| !typ.is_trait) {
if input_types.contains(&typ.name)
&& !enum_tainted.contains(&typ.name)
&& crate::codegen::conversions::can_generate_conversion(typ, &convertible)
{
builder.add_item(&crate::codegen::conversions::gen_from_binding_to_core_cfg(
typ,
&core_import,
&php_conv_config,
));
} else if input_types.contains(&typ.name) && enum_tainted.contains(&typ.name) {
builder.add_item(&gen_enum_tainted_from_binding_to_core(
typ,
&core_import,
enum_names_ref,
&enum_tainted,
&php_conv_config,
&api.enums,
&bridge_type_aliases_set,
));
}
if crate::codegen::conversions::can_generate_conversion(typ, &core_to_binding) {
builder.add_item(&crate::codegen::conversions::gen_from_core_to_binding_cfg(
typ,
&core_import,
&opaque_types,
&php_conv_config,
));
}
}
let mut emitted_binding_to_core: AHashSet<String> = api
.types
.iter()
.filter(|typ| !typ.is_trait && input_types.contains(&typ.name))
.filter(|typ| {
(enum_tainted.contains(&typ.name))
|| crate::codegen::conversions::can_generate_conversion(typ, &convertible)
})
.map(|typ| typ.name.clone())
.collect();
for enum_def in api.enums.iter().filter(|e| is_tagged_data_enum(e)) {
builder.add_item(&gen_flat_data_enum_from_impls(enum_def, &core_import));
for variant in &enum_def.variants {
for field in &variant.fields {
if let TypeRef::Named(type_name) = &field.ty {
if let Some(typ) = api.types.iter().find(|t| &t.name == type_name) {
if emitted_binding_to_core.contains(&typ.name) {
continue;
}
if enum_tainted.contains(&typ.name) {
builder.add_item(&gen_enum_tainted_from_binding_to_core(
typ,
&core_import,
enum_names_ref,
&enum_tainted,
&php_conv_config,
&api.enums,
&bridge_type_aliases_set,
));
emitted_binding_to_core.insert(typ.name.clone());
} else if crate::codegen::conversions::can_generate_conversion(typ, &convertible) {
builder.add_item(&crate::codegen::conversions::gen_from_binding_to_core_cfg(
typ,
&core_import,
&php_conv_config,
));
emitted_binding_to_core.insert(typ.name.clone());
}
}
}
}
}
}
for typ in api.types.iter().filter(|t| !t.is_trait) {
if !emitted_binding_to_core.contains(&typ.name) {
if enum_tainted.contains(&typ.name) {
builder.add_item(&gen_enum_tainted_from_binding_to_core(
typ,
&core_import,
enum_names_ref,
&enum_tainted,
&php_conv_config,
&api.enums,
&bridge_type_aliases_set,
));
emitted_binding_to_core.insert(typ.name.clone());
} else if crate::codegen::conversions::can_generate_conversion(typ, &convertible) {
builder.add_item(&crate::codegen::conversions::gen_from_binding_to_core_cfg(
typ,
&core_import,
&php_conv_config,
));
emitted_binding_to_core.insert(typ.name.clone());
}
}
}
for error in &api.errors {
builder.add_item(&crate::codegen::error_gen::gen_php_error_converter(error, &core_import));
let methods_impl = crate::codegen::error_gen::gen_php_error_methods_impl(error, &core_import);
if !methods_impl.is_empty() {
builder.add_item(&methods_impl);
}
}
if has_serde {
if let Some(serde_module) = gen_serde_defaults_module(api) {
builder.add_item(&serde_module);
}
}
let php_config = config.php.as_ref();
builder.add_inner_attribute("cfg_attr(windows, feature(abi_vectorcall))");
if let Some(feature_name) = php_config.and_then(|c| c.feature_gate.as_deref()) {
builder.add_inner_attribute(&format!("cfg(feature = \"{feature_name}\")"));
}
let mut class_registrations = String::new();
for typ in api
.types
.iter()
.filter(|typ| !typ.is_trait && !exclude_types.contains(&typ.name))
{
class_registrations.push_str(&crate::backends::php::template_env::render(
"php_class_registration.jinja",
context! { class_name => &typ.name },
));
}
if api.functions.iter().any(|f| !exclude_functions.contains(&f.name)) || !config.trait_bridges.is_empty() {
let facade_class_name = extension_name.to_pascal_case();
class_registrations.push_str(&crate::backends::php::template_env::render(
"php_class_registration.jinja",
context! { class_name => &format!("{facade_class_name}Api") },
));
}
for enum_def in api.enums.iter() {
class_registrations.push_str(&crate::backends::php::template_env::render(
"php_class_registration.jinja",
context! { class_name => &enum_def.name },
));
}
for error in api.errors.iter().filter(|e| !e.methods.is_empty()) {
let info_class = format!("{}Info", error.name);
class_registrations.push_str(&crate::backends::php::template_env::render(
"php_class_registration.jinja",
context! { class_name => &info_class },
));
}
builder.add_item(&format!(
"#[php_module]\npub fn get_module(module: ModuleBuilder) -> ModuleBuilder {{\n module{class_registrations}\n}}"
));
let mut content = builder.build();
for bridge in &config.trait_bridges {
if let Some(field_name) = bridge.resolved_options_field() {
let param_name = bridge.param_name.as_deref().unwrap_or(field_name);
let Some(type_alias) = bridge.type_alias.as_deref() else {
continue;
};
let Some(options_type) = bridge.options_type.as_deref() else {
continue;
};
let builder_type = format!("{}Builder", options_type);
let bridge_struct = format!("Php{}Bridge", bridge.trait_name);
let bridge_handle_path =
crate::codegen::generators::trait_bridge::bridge_handle_path(api, bridge, &core_import);
let old_method = format!(
" pub fn {field_name}(&self, {param_name}: Option<&{type_alias}>) -> {builder_type} {{\n Self {{ inner: Arc::new((*self.inner).clone().{field_name}({param_name}.as_ref().map(|v| &v.inner))) }}\n }}"
);
let new_method = format!(
" pub fn {field_name}(&self, {param_name}: &mut ext_php_rs::types::ZendObject) -> {builder_type} {{\n let bridge = {bridge_struct}::new({param_name});\n let handle: {bridge_handle_path} = std::sync::Arc::new(std::sync::Mutex::new(bridge));\n Self {{ inner: Arc::new((*self.inner).clone().{field_name}(Some(handle))) }}\n }}"
);
content = content.replace(&old_method, &new_method);
}
}
let php_stubs_dir = config
.php
.as_ref()
.and_then(|p| p.stubs.as_ref())
.map(|s| s.output.to_string_lossy().to_string())
.unwrap_or_else(|| "packages/php/src/".to_string());
let php_namespace = php_autoload_namespace(config);
let mut generated_files = vec![GeneratedFile {
path: PathBuf::from(&output_dir).join("lib.rs"),
content,
generated_header: false,
}];
let extension_name = config.php_extension_name();
let config_m4 = generate_config_m4(&extension_name, &config.name);
let mut config_m4_path = PathBuf::from(&output_dir);
config_m4_path.pop(); config_m4_path.pop(); config_m4_path.pop(); config_m4_path.push("config.m4");
generated_files.push(GeneratedFile {
path: config_m4_path,
content: config_m4,
generated_header: false,
});
for bridge_cfg in &config.trait_bridges {
if let Some(trait_type) = api.types.iter().find(|t| t.is_trait && t.name == bridge_cfg.trait_name) {
let is_visitor_bridge = bridge_cfg.type_alias.is_some()
&& bridge_cfg.register_fn.is_none()
&& bridge_cfg.super_trait.is_none()
&& bridge_cfg.context_type.is_some()
&& bridge_cfg.result_type.is_some()
&& trait_type.methods.iter().all(|m| m.has_default_impl);
let interface_content = if is_visitor_bridge {
crate::backends::php::trait_bridge::gen_visitor_interface(
trait_type,
bridge_cfg,
&php_namespace,
&HashMap::new(), )
} else {
crate::backends::php::trait_bridge::gen_registration_interface(
trait_type,
bridge_cfg,
&php_namespace,
&HashMap::new(), )
};
let interface_filename = format!("{}.php", bridge_cfg.trait_name);
generated_files.push(GeneratedFile {
path: PathBuf::from(&php_stubs_dir).join(&interface_filename),
content: interface_content,
generated_header: false,
});
}
}
Ok(generated_files)
}