@@ -398,6 +398,98 @@ extern crate proc_macro;
398398/// echo Counter::getCount(); // 2
399399/// ```
400400///
401+ /// ## Using Classes as Properties
402+ ///
403+ /// By default, `#[php_class]` types cannot be used directly as properties of
404+ /// other `#[php_class]` types because they don't implement `FromZval`. To
405+ /// enable this, use the `class_derives_clone!` macro on any class that needs to
406+ /// be used as a property.
407+ ///
408+ /// The class must implement `Clone`, and calling `class_derives_clone!` will
409+ /// implement `FromZval` and `FromZendObject` for the type, allowing PHP objects
410+ /// to be cloned into Rust values.
411+ ///
412+ /// ```rust,ignore
413+ /// use ext_php_rs::prelude::*;
414+ /// use ext_php_rs::class_derives_clone;
415+ ///
416+ /// // Inner class that will be used as a property
417+ /// #[php_class]
418+ /// #[derive(Clone)]
419+ /// pub struct Address {
420+ /// #[php(prop)]
421+ /// pub street: String,
422+ /// #[php(prop)]
423+ /// pub city: String,
424+ /// }
425+ ///
426+ /// // Enable this class to be used as a property
427+ /// class_derives_clone!(Address);
428+ ///
429+ /// #[php_impl]
430+ /// impl Address {
431+ /// pub fn __construct(street: String, city: String) -> Self {
432+ /// Self { street, city }
433+ /// }
434+ /// }
435+ ///
436+ /// // Outer class containing the inner class as a property
437+ /// #[php_class]
438+ /// pub struct Person {
439+ /// #[php(prop)]
440+ /// pub name: String,
441+ /// #[php(prop)]
442+ /// pub address: Address, // Works because we called class_derives_clone!
443+ /// }
444+ ///
445+ /// #[php_impl]
446+ /// impl Person {
447+ /// pub fn __construct(name: String, address: Address) -> Self {
448+ /// Self { name, address }
449+ /// }
450+ ///
451+ /// pub fn get_city(&self) -> String {
452+ /// self.address.city.clone()
453+ /// }
454+ /// }
455+ ///
456+ /// #[php_module]
457+ /// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
458+ /// module
459+ /// .class::<Address>()
460+ /// .class::<Person>()
461+ /// }
462+ /// ```
463+ ///
464+ /// From PHP:
465+ ///
466+ /// ```php
467+ /// <?php
468+ ///
469+ /// $address = new Address("123 Main St", "Springfield");
470+ /// $person = new Person("John Doe", $address);
471+ ///
472+ /// echo $person->name; // "John Doe"
473+ /// echo $person->address->city; // "Springfield"
474+ /// echo $person->getCity(); // "Springfield"
475+ ///
476+ /// // You can also set the nested property
477+ /// $newAddress = new Address("456 Oak Ave", "Shelbyville");
478+ /// $person->address = $newAddress;
479+ /// echo $person->address->city; // "Shelbyville"
480+ /// ```
481+ ///
482+ /// **Important notes:**
483+ ///
484+ /// - The inner class must implement `Clone`
485+ /// - Call `class_derives_clone!` after the `#[php_class]` definition
486+ /// - When accessed from PHP, the property returns a clone of the Rust value
487+ /// - Modifications to the returned object don't affect the original unless
488+ /// reassigned
489+ ///
490+ /// See [GitHub issue #182](https://github.com/extphprs/ext-php-rs/issues/182)
491+ /// for more context.
492+ ///
401493/// ## Abstract Classes
402494///
403495/// Abstract classes cannot be instantiated directly and may contain abstract
0 commit comments