use crate::php_type::PhpType;
use crate::types::{ClassInfo, ClassLikeKind};
use crate::util::short_name;
use std::collections::HashMap;
use std::sync::{Arc, LazyLock};
const CASTS_ATTRIBUTES_SHORT: &str = "CastsAttributes";
const CASTS_ATTRIBUTES_FQN: &str = "Illuminate\\Contracts\\Database\\Eloquent\\CastsAttributes";
static CAST_TYPE_MAP: LazyLock<HashMap<&'static str, PhpType>> = LazyLock::new(|| {
HashMap::from([
("datetime", PhpType::Named("Carbon\\Carbon".to_owned())),
("date", PhpType::Named("Carbon\\Carbon".to_owned())),
("timestamp", PhpType::int()),
(
"immutable_datetime",
PhpType::Named("Carbon\\CarbonImmutable".to_owned()),
),
(
"immutable_date",
PhpType::Named("Carbon\\CarbonImmutable".to_owned()),
),
("boolean", PhpType::bool()),
("bool", PhpType::bool()),
("integer", PhpType::int()),
("int", PhpType::int()),
("float", PhpType::float()),
("double", PhpType::float()),
("real", PhpType::float()),
("string", PhpType::string()),
("array", PhpType::array()),
("json", PhpType::array()),
("object", PhpType::object()),
(
"collection",
PhpType::Named("Illuminate\\Support\\Collection".to_owned()),
),
("encrypted", PhpType::string()),
("encrypted:array", PhpType::array()),
(
"encrypted:collection",
PhpType::Named("Illuminate\\Support\\Collection".to_owned()),
),
("encrypted:object", PhpType::object()),
("hashed", PhpType::string()),
])
});
fn carbon_type() -> PhpType {
PhpType::Named("Carbon\\Carbon".to_owned())
}
fn carbon_immutable_type() -> PhpType {
PhpType::Named("Carbon\\CarbonImmutable".to_owned())
}
const CASTABLE_FQN: &str = "Illuminate\\Contracts\\Database\\Eloquent\\Castable";
pub(super) fn cast_type_to_php_type(
cast_type: &str,
class_loader: &dyn Fn(&str) -> Option<Arc<ClassInfo>>,
) -> PhpType {
let lower = cast_type.to_lowercase();
if let Some(php_type) = CAST_TYPE_MAP.get(lower.as_str()) {
return php_type.clone();
}
if lower.starts_with("decimal:") || lower == "decimal" {
return PhpType::float();
}
if lower.starts_with("datetime:") {
return carbon_type();
}
if lower.starts_with("date:") {
return carbon_type();
}
if lower.starts_with("immutable_datetime:") {
return carbon_immutable_type();
}
if lower.starts_with("immutable_date:") {
return carbon_immutable_type();
}
let class_name = cast_type.split(':').next().unwrap_or(cast_type);
if let Some(cast_class) = class_loader(class_name) {
if cast_class.kind == ClassLikeKind::Enum {
return PhpType::Named(class_name.to_string());
}
if is_castable(&cast_class) {
return PhpType::Named(class_name.to_string());
}
if let Some(tget) = extract_tget_from_implements_generics(&cast_class) {
return tget;
}
if let Some(get_method) = cast_class.methods.iter().find(|m| m.name == "get")
&& let Some(ref rt) = get_method.return_type
&& !rt.is_mixed()
{
return rt.clone();
}
}
PhpType::mixed()
}
fn extract_tget_from_implements_generics(class: &ClassInfo) -> Option<PhpType> {
for (name, args) in &class.implements_generics {
if (name == CASTS_ATTRIBUTES_FQN
|| name == CASTS_ATTRIBUTES_SHORT
|| short_name(name) == CASTS_ATTRIBUTES_SHORT)
&& let Some(tget) = args.first()
{
if matches!(tget, PhpType::Named(s) | PhpType::Raw(s) if s.is_empty()) {
continue;
}
return Some(tget.clone());
}
}
None
}
fn is_castable(class: &ClassInfo) -> bool {
class
.interfaces
.iter()
.any(|iface| iface == CASTABLE_FQN || iface == "Castable")
}
#[cfg(test)]
#[path = "casts_tests.rs"]
mod tests;