|
1 | 1 | use ext_php_rs::prelude::*; |
2 | | -use ext_php_rs::types::ZendClassObject; |
| 2 | +use ext_php_rs::types::{ZendClassObject, Zval}; |
3 | 3 | use ext_php_rs::zend::ce; |
4 | 4 |
|
5 | 5 | #[php_interface] |
@@ -211,6 +211,144 @@ impl VecIterator { |
211 | 211 | // You can use `#[php_impl_interface]` to implement interfaces defined in other crates. |
212 | 212 | // See the `php_interface` and `php_impl_interface` macros for more details. |
213 | 213 |
|
| 214 | +// ============================================================================ |
| 215 | +// Test Feature 5: Short form implements syntax |
| 216 | +// Using #[php(implements("\\InterfaceName"))] instead of the verbose |
| 217 | +// #[php(implements(ce = ce::interface, stub = "\\InterfaceName"))] |
| 218 | +// ============================================================================ |
| 219 | + |
| 220 | +/// Test class implementing `ArrayAccess` using short form syntax. |
| 221 | +/// This tests runtime class entry lookup via `ClassEntry::try_find_no_autoload()`. |
| 222 | +#[php_class] |
| 223 | +#[php(name = "ExtPhpRs\\Interface\\ShortFormArrayAccess")] |
| 224 | +#[php(implements("\\ArrayAccess"))] |
| 225 | +pub struct ShortFormArrayAccess { |
| 226 | + data: Vec<i64>, |
| 227 | +} |
| 228 | + |
| 229 | +#[php_impl] |
| 230 | +#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)] |
| 231 | +impl ShortFormArrayAccess { |
| 232 | + pub fn __construct() -> Self { |
| 233 | + Self { |
| 234 | + data: vec![10, 20, 30, 40, 50], |
| 235 | + } |
| 236 | + } |
| 237 | + |
| 238 | + /// `ArrayAccess::offsetExists` - offset must be `mixed` to match PHP interface. |
| 239 | + pub fn offset_exists(&self, offset: &Zval) -> bool { |
| 240 | + if let Some(idx) = offset.long() { |
| 241 | + idx >= 0 && (idx as usize) < self.data.len() |
| 242 | + } else { |
| 243 | + false |
| 244 | + } |
| 245 | + } |
| 246 | + |
| 247 | + /// `ArrayAccess::offsetGet` - offset must be `mixed` to match PHP interface. |
| 248 | + pub fn offset_get(&self, offset: &Zval) -> Option<i64> { |
| 249 | + let idx = offset.long()?; |
| 250 | + if idx >= 0 { |
| 251 | + self.data.get(idx as usize).copied() |
| 252 | + } else { |
| 253 | + None |
| 254 | + } |
| 255 | + } |
| 256 | + |
| 257 | + /// `ArrayAccess::offsetSet` - offset and value must be `mixed` to match PHP interface. |
| 258 | + pub fn offset_set(&mut self, offset: &Zval, value: &Zval) { |
| 259 | + if let (Some(idx), Some(val)) = (offset.long(), value.long()) |
| 260 | + && idx >= 0 |
| 261 | + && (idx as usize) < self.data.len() |
| 262 | + { |
| 263 | + self.data[idx as usize] = val; |
| 264 | + } |
| 265 | + } |
| 266 | + |
| 267 | + /// `ArrayAccess::offsetUnset` - offset must be `mixed` to match PHP interface. |
| 268 | + pub fn offset_unset(&mut self, offset: &Zval) { |
| 269 | + if let Some(idx) = offset.long() |
| 270 | + && idx >= 0 |
| 271 | + && (idx as usize) < self.data.len() |
| 272 | + { |
| 273 | + self.data.remove(idx as usize); |
| 274 | + } |
| 275 | + } |
| 276 | +} |
| 277 | + |
| 278 | +/// Test class implementing `Countable` using short form syntax. |
| 279 | +/// `Countable` is defined in SPL which is always available. |
| 280 | +#[php_class] |
| 281 | +#[php(name = "ExtPhpRs\\Interface\\CountableTest")] |
| 282 | +#[php(implements("\\Countable"))] |
| 283 | +pub struct CountableTest { |
| 284 | + items: Vec<String>, |
| 285 | +} |
| 286 | + |
| 287 | +#[php_impl] |
| 288 | +#[allow(clippy::cast_possible_wrap)] |
| 289 | +impl CountableTest { |
| 290 | + pub fn __construct() -> Self { |
| 291 | + Self { items: Vec::new() } |
| 292 | + } |
| 293 | + |
| 294 | + pub fn add(&mut self, item: String) { |
| 295 | + self.items.push(item); |
| 296 | + } |
| 297 | + |
| 298 | + /// Returns the count for `count()`. |
| 299 | + pub fn count(&self) -> i64 { |
| 300 | + self.items.len() as i64 |
| 301 | + } |
| 302 | +} |
| 303 | + |
| 304 | +/// Test class implementing multiple interfaces using mixed syntax |
| 305 | +/// (both short form and explicit form). |
| 306 | +#[php_class] |
| 307 | +#[php(name = "ExtPhpRs\\Interface\\MixedImplementsTest")] |
| 308 | +#[php(implements(ce = ce::iterator, stub = "\\Iterator"))] |
| 309 | +#[php(implements("\\Countable"))] |
| 310 | +pub struct MixedImplementsTest { |
| 311 | + items: Vec<i64>, |
| 312 | + index: usize, |
| 313 | +} |
| 314 | + |
| 315 | +#[php_impl] |
| 316 | +impl MixedImplementsTest { |
| 317 | + pub fn __construct() -> Self { |
| 318 | + Self { |
| 319 | + items: vec![10, 20, 30], |
| 320 | + index: 0, |
| 321 | + } |
| 322 | + } |
| 323 | + |
| 324 | + // Iterator methods |
| 325 | + pub fn current(&self) -> Option<i64> { |
| 326 | + self.items.get(self.index).copied() |
| 327 | + } |
| 328 | + |
| 329 | + pub fn key(&self) -> usize { |
| 330 | + self.index |
| 331 | + } |
| 332 | + |
| 333 | + pub fn next(&mut self) { |
| 334 | + self.index += 1; |
| 335 | + } |
| 336 | + |
| 337 | + pub fn rewind(&mut self) { |
| 338 | + self.index = 0; |
| 339 | + } |
| 340 | + |
| 341 | + pub fn valid(&self) -> bool { |
| 342 | + self.index < self.items.len() |
| 343 | + } |
| 344 | + |
| 345 | + #[allow(clippy::cast_possible_wrap)] |
| 346 | + // Countable method |
| 347 | + pub fn count(&self) -> i64 { |
| 348 | + self.items.len() as i64 |
| 349 | + } |
| 350 | +} |
| 351 | + |
214 | 352 | // Test Feature 2: Interface inheritance via trait bounds |
215 | 353 | // Define a parent interface |
216 | 354 | #[php_interface] |
@@ -270,6 +408,10 @@ pub fn build_module(builder: ModuleBuilder) -> ModuleBuilder { |
270 | 408 | .class::<VecIterator>() |
271 | 409 | // Greeter with #[php_impl_interface] |
272 | 410 | .class::<Greeter>() |
| 411 | + // Short form implements syntax tests |
| 412 | + .class::<ShortFormArrayAccess>() |
| 413 | + .class::<CountableTest>() |
| 414 | + .class::<MixedImplementsTest>() |
273 | 415 | } |
274 | 416 |
|
275 | 417 | #[cfg(test)] |
|
0 commit comments