use std::sync::Arc;
use crate::php_type::PhpType;
use crate::types::{ClassInfo, MethodInfo};
use crate::util::short_name;
use super::ELOQUENT_BUILDER_FQN;
use super::helpers::extends_eloquent_model;
fn default_scope_return_type() -> PhpType {
PhpType::Generic(
"Illuminate\\Database\\Eloquent\\Builder".to_string(),
vec![PhpType::static_()],
)
}
pub(super) fn is_scope_method(method: &MethodInfo) -> bool {
method.has_scope_attribute || (method.name.starts_with("scope") && method.name.len() > 5)
}
pub(super) fn is_attribute_scope(method: &MethodInfo) -> bool {
method.has_scope_attribute
}
pub(super) fn scope_name_for(method: &MethodInfo) -> String {
if is_attribute_scope(method) {
method.name.clone()
} else {
scope_name(&method.name)
}
}
pub(super) fn scope_name(method_name: &str) -> String {
let after_prefix = &method_name[5..]; let mut chars = after_prefix.chars();
match chars.next() {
Some(c) => {
let lower: String = c.to_lowercase().collect();
format!("{lower}{}", chars.as_str())
}
None => String::new(),
}
}
pub(super) fn scope_return_type(method: &MethodInfo) -> PhpType {
match &method.return_type {
Some(t) if t.is_void() => default_scope_return_type(),
Some(t) => t.clone(),
None => default_scope_return_type(),
}
}
pub(super) fn build_scope_methods(method: &MethodInfo) -> [MethodInfo; 2] {
let name = scope_name_for(method);
let return_type = scope_return_type(method);
let parameters: Vec<_> = if method.parameters.is_empty() {
Vec::new()
} else {
method.parameters[1..].to_vec()
};
let instance_method = MethodInfo {
parameters: parameters.clone(),
deprecation_message: method.deprecation_message.clone(),
return_type: Some(return_type.clone()),
..MethodInfo::virtual_method(&name, None)
};
let static_method = MethodInfo {
parameters,
is_static: true,
deprecation_message: method.deprecation_message.clone(),
return_type: Some(return_type),
..MethodInfo::virtual_method(&name, None)
};
[instance_method, static_method]
}
pub fn build_scope_methods_for_builder(
model_name: &str,
class_loader: &dyn Fn(&str) -> Option<Arc<ClassInfo>>,
) -> Vec<MethodInfo> {
let model_class = match class_loader(model_name) {
Some(c) => c,
None => {
return Vec::new();
}
};
if !extends_eloquent_model(&model_class, class_loader) {
return Vec::new();
}
let resolved_model =
crate::inheritance::resolve_class_with_inheritance(&model_class, class_loader);
let model_type = PhpType::Named(model_name.to_owned());
let subs = super::self_ref_subs(model_type);
let mut methods = Vec::new();
for method in &resolved_model.methods {
if !is_scope_method(method) {
continue;
}
let [instance_method, _static_method] = build_scope_methods(method);
let mut m = instance_method;
if let Some(ref mut ret) = m.return_type {
*ret = ret.substitute(&subs);
if is_bare_builder_type(ret) {
*ret = PhpType::Generic(
ELOQUENT_BUILDER_FQN.to_string(),
vec![PhpType::Named(model_name.to_string())],
);
}
}
methods.push(m);
}
methods
}
fn is_bare_builder_type(ty: &PhpType) -> bool {
match ty {
PhpType::Named(name) => name == ELOQUENT_BUILDER_FQN || short_name(name) == "Builder",
_ => false,
}
}
#[cfg(test)]
#[path = "scopes_tests.rs"]
mod tests;