diff --git a/examples/http-client/src/errors.rs b/examples/http-client/src/errors.rs index 69859abb..27bdb39b 100644 --- a/examples/http-client/src/errors.rs +++ b/examples/http-client/src/errors.rs @@ -9,7 +9,7 @@ // See the Mulan PSL v2 for more details. use phper::{ - classes::{ClassEntity, ClassEntry}, + classes::{ClassEntity, ClassEntry, StateClass}, errors::{Throwable, exception_class}, }; @@ -19,7 +19,7 @@ const EXCEPTION_CLASS_NAME: &str = "HttpClient\\HttpClientException"; pub fn make_exception_class() -> ClassEntity<()> { let mut class = ClassEntity::new(EXCEPTION_CLASS_NAME); // The `extends` is same as the PHP class `extends`. - class.extends(exception_class); + class.extends(StateClass::from_name("Exception")); class } diff --git a/examples/http-server/src/errors.rs b/examples/http-server/src/errors.rs index eeb217f0..00c7c6a7 100644 --- a/examples/http-server/src/errors.rs +++ b/examples/http-server/src/errors.rs @@ -9,7 +9,7 @@ // See the Mulan PSL v2 for more details. use phper::{ - classes::{ClassEntity, ClassEntry}, + classes::{ClassEntity, ClassEntry, StateClass}, errors::{Throwable, exception_class}, }; use std::error::Error; @@ -43,6 +43,6 @@ impl From for phper::Error { pub fn make_exception_class() -> ClassEntity<()> { let mut class = ClassEntity::new(EXCEPTION_CLASS_NAME); // As an Exception class, inheriting from the base Exception class is important. - class.extends(exception_class); + class.extends(StateClass::from_name("Exception")); class } diff --git a/phper-doc/doc/_02_quick_start/_02_write_a_simple_http_client/index.md b/phper-doc/doc/_02_quick_start/_02_write_a_simple_http_client/index.md index 3bcd7c8e..8538def3 100644 --- a/phper-doc/doc/_02_quick_start/_02_write_a_simple_http_client/index.md +++ b/phper-doc/doc/_02_quick_start/_02_write_a_simple_http_client/index.md @@ -91,7 +91,7 @@ Now let's begin to finish the logic. ```rust use phper::{ - classes::{ClassEntry, ClassEntity}, + classes::{ClassEntry, ClassEntity, StateClass}, errors::{exception_class, Throwable}, }; @@ -101,7 +101,7 @@ Now let's begin to finish the logic. pub fn make_exception_class() -> ClassEntity<()> { let mut class = ClassEntity::new(EXCEPTION_CLASS_NAME); // The `extends` is same as the PHP class `extends`. - class.extends(exception_class); + class.extends(StateClass::from_name("Exception")); class } @@ -155,7 +155,7 @@ Now let's begin to finish the logic. # pub fn make_exception_class() -> ClassEntity<()> { # let mut class = ClassEntity::new(EXCEPTION_CLASS_NAME); # // The `extends` is same as the PHP class `extends`. - # class.extends(exception_class); + # class.extends(StateClass::from_name("Exception")); # class # } # 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 20233d77..8eb70c62 100644 --- a/phper-doc/doc/_06_module/_06_register_class/index.md +++ b/phper-doc/doc/_06_module/_06_register_class/index.md @@ -38,13 +38,21 @@ class Foo {} ## Extends & implements -If you want the class `Foo` extends `Bar`, and implements interface `Stringable`: +To allow a class to extend another, you can use `ClassEntity.extends(StateClass)` for classes implemented +in your module. A StateClass can either be obtained by registering your own class against the module, or +by looking up the class by name (for example, for PHP built-in classes like `Exception`). + +If you want class `Foo` extends `Bar`, and implements interface `Stringable`: ```rust,no_run use phper::classes::{ClassEntity, ClassEntry, Interface}; +use phper::{modules::Module, php_get_module}; + +let mut module = Module::new("test", "0.1", ""); +let bar = module.add_class(ClassEntity::new("Bar")); let mut foo = ClassEntity::new("Foo"); -foo.extends(|| ClassEntry::from_globals("Bar").unwrap()); +foo.extends(bar); foo.implements(Interface::from_name("Stringable")); ``` diff --git a/phper-doc/doc/_06_module/_07_register_interface/index.md b/phper-doc/doc/_06_module/_07_register_interface/index.md index d07834b6..53d8d1d6 100644 --- a/phper-doc/doc/_06_module/_07_register_interface/index.md +++ b/phper-doc/doc/_06_module/_07_register_interface/index.md @@ -41,12 +41,11 @@ interface Foo {} If you want the interface `Foo` extends `ArrayAccess` and `Iterator` interfaces. ```rust,no_run -use phper::classes::{InterfaceEntity, ClassEntry}; -use phper::classes::{array_access_class, iterator_class}; +use phper::classes::{Interface, InterfaceEntity, ClassEntry}; let mut foo = InterfaceEntity::new("Foo"); -foo.extends(|| array_access_class()); -foo.extends(|| iterator_class()); +foo.extends(Interface::from_name("ArrayAccess")); +foo.extends(Interface::from_name("Iterator")); ``` Same as: diff --git a/phper/src/classes.rs b/phper/src/classes.rs index f011abba..0d4169aa 100644 --- a/phper/src/classes.rs +++ b/phper/src/classes.rs @@ -28,7 +28,7 @@ use std::{ ffi::{CString, c_char, c_void}, fmt::Debug, marker::PhantomData, - mem::{ManuallyDrop, replace, size_of, zeroed}, + mem::{ManuallyDrop, replace, size_of, transmute, zeroed}, os::raw::c_int, ptr, ptr::null_mut, @@ -36,18 +36,6 @@ use std::{ slice, }; -/// Predefined interface `Iterator`. -#[inline] -pub fn iterator_class<'a>() -> &'a ClassEntry { - unsafe { ClassEntry::from_ptr(zend_ce_iterator) } -} - -/// Predefined interface `ArrayAccess`. -#[inline] -pub fn array_access_class<'a>() -> &'a ClassEntry { - unsafe { ClassEntry::from_ptr(zend_ce_arrayaccess) } -} - /// Wrapper of [zend_class_entry]. #[derive(Clone)] #[repr(transparent)] @@ -287,13 +275,26 @@ fn find_global_class_entry_ptr(name: impl AsRef) -> *mut zend_class_entry { /// ``` pub struct StateClass { inner: Rc>, + name: Option, _p: PhantomData, } +impl StateClass<()> { + /// Create from name, which will be looked up from globals. + pub fn from_name(name: impl Into) -> Self { + Self { + inner: Rc::new(RefCell::new(null_mut())), + name: Some(name.into()), + _p: PhantomData, + } + } +} + impl StateClass { fn null() -> Self { Self { inner: Rc::new(RefCell::new(null_mut())), + name: None, _p: PhantomData, } } @@ -304,7 +305,11 @@ impl StateClass { /// 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()) } + } } /// Create the object from class and call `__construct` with arguments. @@ -333,6 +338,7 @@ impl Clone for StateClass { fn clone(&self) -> Self { Self { inner: self.inner.clone(), + name: self.name.clone(), _p: self._p, } } @@ -427,7 +433,7 @@ pub struct ClassEntity { state_constructor: Rc, method_entities: Vec, property_entities: Vec, - parent: Option &'static ClassEntry>>, + parent: Option>, interfaces: Vec, constants: Vec, bind_class: StateClass, @@ -550,19 +556,40 @@ impl ClassEntity { /// Register class to `extends` the parent class. /// /// *Because in the `MINIT` phase, the class starts to register, so the* - /// *closure is used to return the `ClassEntry` to delay the acquisition of* + /// *`ClassEntry` is looked up by name to delay the acquisition of* /// *the class.* /// /// # Examples /// /// ```no_run - /// use phper::classes::{ClassEntity, ClassEntry}; + /// use phper::{ + /// classes::{ClassEntity, ClassEntry, StateClass}, + /// modules::Module, + /// php_get_module, + /// }; /// - /// let mut class = ClassEntity::new("MyException"); - /// class.extends(|| ClassEntry::from_globals("Exception").unwrap()); + /// #[php_get_module] + /// pub fn get_module() -> Module { + /// let mut module = Module::new( + /// env!("CARGO_CRATE_NAME"), + /// env!("CARGO_PKG_VERSION"), + /// env!("CARGO_PKG_AUTHORS"), + /// ); + /// + /// let foo = module.add_class(ClassEntity::new("Foo")); + /// let mut bar = ClassEntity::new("Bar"); + /// bar.extends(foo); + /// module.add_class(bar); + /// + /// let mut ex = ClassEntity::new("MyException"); + /// ex.extends(StateClass::from_name("Exception")); + /// module.add_class(ex); + /// + /// module + /// } /// ``` - pub fn extends(&mut self, parent: impl Fn() -> &'static ClassEntry + 'static) { - self.parent = Some(Box::new(parent)); + pub fn extends(&mut self, parent: StateClass) { + self.parent = Some(unsafe { transmute::, StateClass<()>>(parent) }); } /// Register class to `implements` the interface, due to the class can @@ -634,7 +661,7 @@ impl ClassEntity { let parent: *mut zend_class_entry = self .parent .as_ref() - .map(|parent| parent()) + .map(|parent| parent.as_class_entry()) .map(|entry| entry.as_ptr() as *mut _) .unwrap_or(null_mut()); @@ -800,20 +827,24 @@ impl InterfaceEntity { /// Register interface to `extends` the interfaces, due to the interface can /// extends multi interface, so this method can be called multi time. /// - /// *Because in the `MINIT` phase, the class starts to register, so the* + /// *Because in the `MINIT` phase, the class starts to register, a* /// *closure is used to return the `ClassEntry` to delay the acquisition of* /// *the class.* /// /// # Examples /// /// ```no_run - /// use phper::classes::{ClassEntry, InterfaceEntity}; + /// use phper::classes::{Interface, InterfaceEntity}; /// /// let mut interface = InterfaceEntity::new("MyInterface"); - /// interface.extends(|| ClassEntry::from_globals("Stringable").unwrap()); + /// interface.extends(Interface::from_name("Stringable")); /// ``` - pub fn extends(&mut self, interface: impl Fn() -> &'static ClassEntry + 'static) { - self.extends.push(Box::new(interface)); + pub fn extends(&mut self, interface: Interface) { + self.extends.push(Box::new(move || { + let entry: &'static ClassEntry = + unsafe { std::mem::transmute(interface.as_class_entry()) }; + entry + })); } #[allow(clippy::useless_conversion)] diff --git a/tests/integration/src/classes.rs b/tests/integration/src/classes.rs index 25ceac3e..b6fbee51 100644 --- a/tests/integration/src/classes.rs +++ b/tests/integration/src/classes.rs @@ -10,10 +10,7 @@ use phper::{ alloc::RefClone, - classes::{ - ClassEntity, ClassEntry, Interface, InterfaceEntity, Visibility, array_access_class, - iterator_class, - }, + classes::{ClassEntity, ClassEntry, Interface, InterfaceEntity, StateClass, Visibility}, functions::{Argument, ReturnType}, modules::Module, types::{ArgumentTypeHint, ReturnTypeHint}, @@ -23,10 +20,11 @@ use std::{collections::HashMap, convert::Infallible}; pub fn integrate(module: &mut Module) { integrate_a(module); - integrate_foo(module); + let foo_class = integrate_foo(module); integrate_i_bar(module); integrate_static_props(module); integrate_i_constants(module); + integrate_bar_extends_foo(module, foo_class); #[cfg(phper_major_version = "8")] integrate_stringable(module); } @@ -78,7 +76,7 @@ struct Foo { array: HashMap, } -fn integrate_foo(module: &mut Module) { +fn integrate_foo(module: &mut Module) -> StateClass { let mut class = ClassEntity::new_with_state_constructor("IntegrationTest\\Foo", || Foo { position: 0, array: Default::default(), @@ -169,14 +167,14 @@ fn integrate_foo(module: &mut Module) { .argument(Argument::new("offset").with_type_hint(ArgumentTypeHint::Mixed)) .return_type(ReturnType::new(ReturnTypeHint::Void)); - module.add_class(class); + module.add_class(class) } fn integrate_i_bar(module: &mut Module) { let mut interface = InterfaceEntity::new(r"IntegrationTest\IBar"); - interface.extends(|| array_access_class()); - interface.extends(|| iterator_class()); + interface.extends(Interface::from_name("ArrayAccess")); + interface.extends(Interface::from_name("Iterator")); interface .add_method("doSomethings") @@ -224,6 +222,13 @@ fn integrate_static_props(module: &mut Module) { module.add_class(class); } +fn integrate_bar_extends_foo(module: &mut Module, foo_class: StateClass) { + let mut cls = ClassEntity::new(r"IntegrationTest\BarExtendsFoo"); + cls.extends(foo_class); + cls.add_method("test", Visibility::Public, |_, _| phper::ok(())); + module.add_class(cls); +} + #[cfg(phper_major_version = "8")] fn integrate_stringable(module: &mut Module) { use phper::{functions::ReturnType, types::ReturnTypeHint}; diff --git a/tests/integration/tests/php/classes.php b/tests/integration/tests/php/classes.php index e7ecf4df..cdd8dbb1 100644 --- a/tests/integration/tests/php/classes.php +++ b/tests/integration/tests/php/classes.php @@ -100,3 +100,8 @@ class Foo2 extends IntegrationTest\Foo {} assert_false(IntegrationTest\IConstants::CST_FALSE); assert_eq(100, IntegrationTest\IConstants::CST_INT); assert_eq(10.0, IntegrationTest\IConstants::CST_FLOAT); + +// Test module class extends module class +$bar = new \IntegrationTest\BarExtendsFoo; //Bar should extend Foo +$reflection = new ReflectionClass($bar); +assert_true($reflection->isSubclassOf(IntegrationTest\Foo::class)); \ No newline at end of file