mago_analyzer/plugin/libraries/stdlib/object/
get_object_vars.rs1use std::collections::BTreeMap;
2use std::sync::Arc;
3
4use mago_codex::ttype::atomic::TAtomic;
5use mago_codex::ttype::atomic::array::TArray;
6use mago_codex::ttype::atomic::array::key::ArrayKey;
7use mago_codex::ttype::atomic::array::keyed::TKeyedArray;
8use mago_codex::ttype::get_arraykey;
9use mago_codex::ttype::get_mixed;
10use mago_codex::ttype::union::TUnion;
11
12use crate::plugin::context::InvocationInfo;
13use crate::plugin::context::ProviderContext;
14use crate::plugin::provider::Provider;
15use crate::plugin::provider::ProviderMeta;
16use crate::plugin::provider::function::FunctionReturnTypeProvider;
17use crate::plugin::provider::function::FunctionTarget;
18use crate::visibility::is_visible_from_scope;
19
20static META: ProviderMeta = ProviderMeta::new(
21 "php::object::get_object_vars",
22 "get_object_vars",
23 "Returns the object's properties visible from the calling scope as a keyed array",
24);
25
26#[derive(Default)]
27pub struct GetObjectVarsProvider;
28
29impl Provider for GetObjectVarsProvider {
30 fn meta() -> &'static ProviderMeta {
31 &META
32 }
33}
34
35impl FunctionReturnTypeProvider for GetObjectVarsProvider {
36 fn targets() -> FunctionTarget {
37 FunctionTarget::Exact(b"get_object_vars")
38 }
39
40 fn get_return_type(
41 &self,
42 context: &ProviderContext<'_, '_, '_>,
43 invocation: &InvocationInfo<'_, '_, '_>,
44 ) -> Option<TUnion> {
45 let argument = invocation.get_argument(0, &[b"object"])?;
46 let argument_type = context.get_expression_type(argument)?;
47 let object = argument_type.get_single_named_object()?;
48
49 let class_metadata = context.get_class_like(object.name)?;
50 if class_metadata.kind.is_enum() {
51 return None;
52 }
53
54 let current_class = context.current_class_name();
55
56 let mut known_items: BTreeMap<ArrayKey, (bool, TUnion)> = BTreeMap::new();
57 for (property_name, declaring_class) in class_metadata.appearing_property_ids.iter() {
58 let Some(property) = context.codebase().get_property(declaring_class.as_bytes(), property_name.as_bytes())
59 else {
60 continue;
61 };
62
63 if property.flags.is_static() || property.flags.is_virtual_property() {
64 continue;
65 }
66
67 if !is_visible_from_scope(
68 context.codebase(),
69 property.read_visibility,
70 declaring_class.as_bytes(),
71 current_class,
72 ) {
73 continue;
74 }
75
76 let Some(name) = property_name.as_bytes().strip_prefix(b"$") else {
77 continue;
78 };
79
80 let property_type =
81 property.type_metadata.as_ref().map(|metadata| metadata.type_union.clone()).unwrap_or_else(get_mixed);
82
83 known_items.insert(ArrayKey::String(mago_word::word(name)), (false, property_type));
84 }
85
86 if known_items.is_empty() {
87 return None;
88 }
89
90 let mut keyed_array = TKeyedArray::new();
91 keyed_array.known_items = Some(known_items);
92 keyed_array.non_empty = true;
93
94 if !class_metadata.flags.is_final() || !class_metadata.flags.is_readonly() {
95 keyed_array.parameters = Some((Arc::new(get_arraykey()), Arc::new(get_mixed())));
96 }
97
98 Some(TUnion::from_atomic(TAtomic::Array(TArray::Keyed(keyed_array))))
99 }
100}