use crate::php_type::PhpType;
use crate::types::{ClassInfo, MethodInfo};
use std::sync::Arc;
use super::helpers::walks_parent_chain;
use super::super::{VirtualMemberProvider, VirtualMembers};
const FACTORY_FQN: &str = "Illuminate\\Database\\Eloquent\\Factories\\Factory";
pub(crate) fn model_to_factory_fqn(model_fqn: &str) -> String {
let (ns, short) = match model_fqn.rsplit_once('\\') {
Some((ns, short)) => (ns, short),
None => return format!("Database\\Factories\\{model_fqn}Factory"),
};
if let Some((_prefix, suffix)) = ns.split_once("\\Models\\") {
return format!("Database\\Factories\\{suffix}\\{short}Factory");
}
if ns.ends_with("\\Models") || ns == "Models" {
return format!("Database\\Factories\\{short}Factory");
}
format!("Database\\Factories\\{short}Factory")
}
pub(crate) fn factory_to_model_fqn(factory_fqn: &str) -> Option<String> {
let short = factory_fqn.rsplit('\\').next().unwrap_or(factory_fqn);
let model_short = short.strip_suffix("Factory")?;
if model_short.is_empty() {
return None;
}
let ns = factory_fqn
.rsplit_once('\\')
.map(|(ns, _)| ns)
.unwrap_or("");
let sub_ns = if let Some(after) = ns.strip_prefix("Database\\Factories\\") {
Some(after)
} else if ns == "Database\\Factories" {
None
} else {
None
};
match sub_ns {
Some(sub) => Some(format!("App\\Models\\{sub}\\{model_short}")),
None => Some(format!("App\\Models\\{model_short}")),
}
}
fn is_eloquent_factory(class_name: &str) -> bool {
class_name == FACTORY_FQN
}
fn extends_eloquent_factory(
class: &ClassInfo,
class_loader: &dyn Fn(&str) -> Option<Arc<ClassInfo>>,
) -> bool {
walks_parent_chain(class, class_loader, is_eloquent_factory)
}
fn has_factory_extends_generic(class: &ClassInfo) -> bool {
class.extends_generics.iter().any(|(name, args)| {
let short = name.rsplit('\\').next().unwrap_or(name);
short == "Factory" && !args.is_empty()
})
}
fn build_factory_model_methods(
class: &ClassInfo,
class_loader: &dyn Fn(&str) -> Option<Arc<ClassInfo>>,
) -> Vec<MethodInfo> {
let model_fqn = match factory_to_model_fqn(&class.name) {
Some(fqn) => fqn,
None => return Vec::new(),
};
if class_loader(&model_fqn).is_none() {
return Vec::new();
}
let model_type = PhpType::Named(model_fqn.to_string());
vec![
MethodInfo::virtual_method_typed("create", Some(&model_type)),
MethodInfo::virtual_method_typed("make", Some(&model_type)),
]
}
pub struct LaravelFactoryProvider;
impl VirtualMemberProvider for LaravelFactoryProvider {
fn applies_to(
&self,
class: &ClassInfo,
class_loader: &dyn Fn(&str) -> Option<Arc<ClassInfo>>,
) -> bool {
!is_eloquent_factory(&class.name)
&& !has_factory_extends_generic(class)
&& extends_eloquent_factory(class, class_loader)
}
fn provide(
&self,
class: &ClassInfo,
class_loader: &dyn Fn(&str) -> Option<Arc<ClassInfo>>,
_cache: Option<&crate::virtual_members::ResolvedClassCache>,
) -> VirtualMembers {
let methods = build_factory_model_methods(class, class_loader);
VirtualMembers {
methods,
properties: Vec::new(),
constants: Vec::new(),
}
}
}
#[cfg(test)]
#[path = "factory_tests.rs"]
mod tests;