Skip to content

Commit 33c82c5

Browse files
committed
feat(interfaces): added support for short form of php(implements("\\...")) macro
1 parent 08e3a47 commit 33c82c5

3 files changed

Lines changed: 54 additions & 5 deletions

File tree

crates/macros/src/class.rs

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,9 @@ pub struct StructAttributes {
3030
attrs: Vec<Attribute>,
3131
}
3232

33-
/// Represents a class entry reference, either explicit (with `ce` and `stub`)
34-
/// or a simple type reference to a Rust type implementing `RegisteredClass`.
33+
/// Represents a class entry reference, either explicit (with `ce` and `stub`),
34+
/// a simple type reference to a Rust type implementing `RegisteredClass`,
35+
/// or a string literal for runtime class lookup.
3536
///
3637
/// # Examples
3738
///
@@ -44,13 +45,20 @@ pub struct StructAttributes {
4445
/// ```ignore
4546
/// #[php(extends(Base))]
4647
/// ```
48+
///
49+
/// String literal form (for runtime class lookup):
50+
/// ```ignore
51+
/// #[php(implements("\\JsonSerializable"))]
52+
/// ```
4753
#[derive(Debug)]
4854
pub enum ClassEntryAttribute {
4955
/// Explicit class entry with a function returning `&'static ClassEntry` and
5056
/// a stub name
5157
Explicit { ce: syn::Expr, stub: String },
5258
/// A Rust type that implements `RegisteredClass`
5359
Type(syn::Path),
60+
/// A PHP class name to be looked up at runtime via `ClassEntry::try_find`
61+
Name(String),
5462
}
5563

5664
impl FromMeta for ClassEntryAttribute {
@@ -73,10 +81,15 @@ impl FromMeta for ClassEntryAttribute {
7381
stub: explicit.stub,
7482
})
7583
} else {
84+
// Try to parse as string literal first: implements("\\JsonSerializable")
85+
if let Ok(lit) = list.parse_args::<syn::LitStr>() {
86+
return Ok(ClassEntryAttribute::Name(lit.value()));
87+
}
7688
// Parse as simple type form: extends(TypeName)
7789
let path: syn::Path = list.parse_args().map_err(|e| {
7890
darling::Error::custom(format!(
79-
"Expected a type path (e.g., `MyClass`) or explicit form \
91+
"Expected a type path (e.g., `MyClass`), string literal \
92+
(e.g., `\"\\\\JsonSerializable\"`), or explicit form \
8093
(e.g., `ce = expr, stub = \"Name\"`): {e}"
8194
))
8295
})?;
@@ -105,6 +118,17 @@ impl ToTokens for ClassEntryAttribute {
105118
)
106119
}
107120
}
121+
ClassEntryAttribute::Name(name) => {
122+
// For a string literal, generate a function that looks up the class at runtime
123+
// Uses try_find_no_autoload to avoid triggering autoloading during MINIT
124+
quote! {
125+
(
126+
|| ::ext_php_rs::zend::ClassEntry::try_find_no_autoload(#name)
127+
.expect(concat!("Failed to find class entry for ", #name)),
128+
#name
129+
)
130+
}
131+
}
108132
};
109133
tokens.append_all(token);
110134
}

src/zend/ce.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,7 @@ use crate::ffi::{
77
zend_ce_compile_error, zend_ce_countable, zend_ce_division_by_zero_error,
88
zend_ce_error_exception, zend_ce_exception, zend_ce_iterator, zend_ce_parse_error,
99
zend_ce_serializable, zend_ce_stringable, zend_ce_throwable, zend_ce_traversable,
10-
zend_ce_type_error, zend_ce_unhandled_match_error, zend_ce_value_error,
11-
zend_standard_class_def,
10+
zend_ce_type_error, zend_ce_unhandled_match_error, zend_ce_value_error, zend_standard_class_def,
1211
};
1312

1413
use super::ClassEntry;

src/zend/class.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,32 @@ impl ClassEntry {
3535
unsafe { crate::ffi::zend_lookup_class_ex(&raw mut *name, ptr::null_mut(), 0).as_ref() }
3636
}
3737

38+
/// Attempts to find a reference to a class in the global class table without
39+
/// triggering autoloading.
40+
///
41+
/// This is useful during module initialization when autoloading might not be
42+
/// available or could cause issues.
43+
///
44+
/// Returns a reference to the class if found, or [`None`] if the class
45+
/// could not be found or the class table has not been initialized.
46+
#[must_use]
47+
pub fn try_find_no_autoload(name: &str) -> Option<&'static Self> {
48+
// ZEND_FETCH_CLASS_NO_AUTOLOAD = 0x80
49+
const ZEND_FETCH_CLASS_NO_AUTOLOAD: u32 = 0x80;
50+
51+
ExecutorGlobals::get().class_table()?;
52+
let mut name = ZendStr::new(name, false);
53+
54+
unsafe {
55+
crate::ffi::zend_lookup_class_ex(
56+
&raw mut *name,
57+
ptr::null_mut(),
58+
ZEND_FETCH_CLASS_NO_AUTOLOAD,
59+
)
60+
.as_ref()
61+
}
62+
}
63+
3864
/// Creates a new [`ZendObject`], returned inside an [`ZBox<ZendObject>`]
3965
/// wrapper.
4066
///

0 commit comments

Comments
 (0)