diff --git a/phper-doc/doc/_06_module/_06_register_class/index.md b/phper-doc/doc/_06_module/_06_register_class/index.md index b07e4228..20233d77 100644 --- a/phper-doc/doc/_06_module/_06_register_class/index.md +++ b/phper-doc/doc/_06_module/_06_register_class/index.md @@ -41,14 +41,14 @@ class Foo {} If you want the class `Foo` extends `Bar`, and implements interface `Stringable`: ```rust,no_run -use phper::classes::{ClassEntity, ClassEntry}; +use phper::classes::{ClassEntity, ClassEntry, Interface}; let mut foo = ClassEntity::new("Foo"); foo.extends(|| ClassEntry::from_globals("Bar").unwrap()); -foo.implements(|| ClassEntry::from_globals("Stringable").unwrap()); +foo.implements(Interface::from_name("Stringable")); ``` -You should implements the method `Stringable::__toString` after implemented +You should implement the method `Stringable::__toString` after implementing `Stringable`, otherwise the class `Foo` will become abstract class. ## Add properties diff --git a/phper/src/classes.rs b/phper/src/classes.rs index 417c7183..f011abba 100644 --- a/phper/src/classes.rs +++ b/phper/src/classes.rs @@ -376,22 +376,39 @@ impl Clone for StateClass { #[derive(Clone)] pub struct Interface { inner: Rc>, + name: Option, } impl Interface { fn null() -> Self { Self { inner: Rc::new(RefCell::new(null_mut())), + name: None, + } + } + + /// Create a new interface from global name (eg "Stringable", "ArrayAccess") + pub fn from_name(name: impl Into) -> Self { + Self { + inner: Rc::new(RefCell::new(null_mut())), + name: Some(name.into()), } } fn bind(&self, ptr: *mut zend_class_entry) { + if self.name.is_some() { + panic!("Cannot bind() an Interface created with from_name()"); + } *self.inner.borrow_mut() = ptr; } /// Converts to class entry. pub fn as_class_entry(&self) -> &ClassEntry { - unsafe { ClassEntry::from_mut_ptr(*self.inner.borrow()) } + if let Some(name) = &self.name { + ClassEntry::from_globals(name).unwrap() + } else { + unsafe { ClassEntry::from_mut_ptr(*self.inner.borrow()) } + } } } @@ -411,7 +428,7 @@ pub struct ClassEntity { method_entities: Vec, property_entities: Vec, parent: Option &'static ClassEntry>>, - interfaces: Vec &'static ClassEntry>>, + interfaces: Vec, constants: Vec, bind_class: StateClass, state_cloner: Option>, @@ -552,23 +569,22 @@ impl ClassEntity { /// implement multi interface, so this method can be called multi time. /// /// *Because in the `MINIT` phase, the class starts to register, so the* - /// *closure is used to return the `ClassEntry` to delay the acquisition of* - /// *the class.* + /// *`ClassEntry` is not fetched until later* /// /// # Examples /// /// ```no_run - /// use phper::classes::{ClassEntity, ClassEntry}; + /// use phper::classes::{ClassEntity, ClassEntry, Interface}; /// /// let mut class = ClassEntity::new("MyClass"); - /// class.implements(|| ClassEntry::from_globals("Stringable").unwrap()); + /// class.implements(Interface::from_name("Stringable")); /// /// // Here you have to the implement the method `__toString()` in `Stringable` /// // for `MyClass`, otherwise `MyClass` will become abstract class. /// // ... /// ``` - pub fn implements(&mut self, interface: impl Fn() -> &'static ClassEntry + 'static) { - self.interfaces.push(Box::new(interface)); + pub fn implements(&mut self, interface: Interface) { + self.interfaces.push(interface); } /// Add the state clone function, called when cloning PHP object. @@ -633,7 +649,7 @@ impl ClassEntity { self.bind_class.bind(class_ce); for interface in &self.interfaces { - let interface_ce = interface().as_ptr(); + let interface_ce = interface.as_class_entry().as_ptr(); zend_class_implements(class_ce, 1, interface_ce); } diff --git a/tests/integration/src/classes.rs b/tests/integration/src/classes.rs index e8aaa09a..25ceac3e 100644 --- a/tests/integration/src/classes.rs +++ b/tests/integration/src/classes.rs @@ -11,7 +11,8 @@ use phper::{ alloc::RefClone, classes::{ - ClassEntity, ClassEntry, InterfaceEntity, Visibility, array_access_class, iterator_class, + ClassEntity, ClassEntry, Interface, InterfaceEntity, Visibility, array_access_class, + iterator_class, }, functions::{Argument, ReturnType}, modules::Module, @@ -83,8 +84,8 @@ fn integrate_foo(module: &mut Module) { array: Default::default(), }); - class.implements(iterator_class); - class.implements(array_access_class); + class.implements(Interface::from_name("Iterator")); + class.implements(Interface::from_name("ArrayAccess")); // Implement Iterator interface. class @@ -228,7 +229,7 @@ fn integrate_stringable(module: &mut Module) { use phper::{functions::ReturnType, types::ReturnTypeHint}; let mut cls = ClassEntity::new(r"IntegrationTest\FooString"); - cls.implements(|| ClassEntry::from_globals("Stringable").unwrap()); + cls.implements(Interface::from_name("Stringable")); cls.add_method("__toString", Visibility::Public, |_this, _: &mut [ZVal]| { phper::ok("string") }) diff --git a/tests/integration/src/typehints.rs b/tests/integration/src/typehints.rs index 955bd759..d8334a00 100644 --- a/tests/integration/src/typehints.rs +++ b/tests/integration/src/typehints.rs @@ -92,11 +92,8 @@ fn make_foo_handler() -> ClassEntity<()> { fn make_foo_class(i_foo: Interface) -> ClassEntity<()> { let mut class = ClassEntity::new(r"IntegrationTest\TypeHints\Foo"); + class.implements(i_foo); - // leak Interface so that ClassEntry can be retrieved later, during module - // startup - let i_foo_copy: &'static Interface = Box::leak(Box::new(i_foo)); - class.implements(move || i_foo_copy.as_class_entry()); class .add_method("getValue", Visibility::Public, |this, _| { let value = this