Skip to content

Commit 477bb83

Browse files
authored
improve Interface::extends and ClassEntry::extends (#190)
1 parent a118552 commit 477bb83

File tree

8 files changed

+97
-49
lines changed

8 files changed

+97
-49
lines changed

examples/http-client/src/errors.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
// See the Mulan PSL v2 for more details.
1010

1111
use phper::{
12-
classes::{ClassEntity, ClassEntry},
12+
classes::{ClassEntity, ClassEntry, StateClass},
1313
errors::{Throwable, exception_class},
1414
};
1515

@@ -19,7 +19,7 @@ const EXCEPTION_CLASS_NAME: &str = "HttpClient\\HttpClientException";
1919
pub fn make_exception_class() -> ClassEntity<()> {
2020
let mut class = ClassEntity::new(EXCEPTION_CLASS_NAME);
2121
// The `extends` is same as the PHP class `extends`.
22-
class.extends(exception_class);
22+
class.extends(StateClass::from_name("Exception"));
2323
class
2424
}
2525

examples/http-server/src/errors.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
// See the Mulan PSL v2 for more details.
1010

1111
use phper::{
12-
classes::{ClassEntity, ClassEntry},
12+
classes::{ClassEntity, ClassEntry, StateClass},
1313
errors::{Throwable, exception_class},
1414
};
1515
use std::error::Error;
@@ -43,6 +43,6 @@ impl From<HttpServerError> for phper::Error {
4343
pub fn make_exception_class() -> ClassEntity<()> {
4444
let mut class = ClassEntity::new(EXCEPTION_CLASS_NAME);
4545
// As an Exception class, inheriting from the base Exception class is important.
46-
class.extends(exception_class);
46+
class.extends(StateClass::from_name("Exception"));
4747
class
4848
}

phper-doc/doc/_02_quick_start/_02_write_a_simple_http_client/index.md

+3-3
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ Now let's begin to finish the logic.
9191

9292
```rust
9393
use phper::{
94-
classes::{ClassEntry, ClassEntity},
94+
classes::{ClassEntry, ClassEntity, StateClass},
9595
errors::{exception_class, Throwable},
9696
};
9797

@@ -101,7 +101,7 @@ Now let's begin to finish the logic.
101101
pub fn make_exception_class() -> ClassEntity<()> {
102102
let mut class = ClassEntity::new(EXCEPTION_CLASS_NAME);
103103
// The `extends` is same as the PHP class `extends`.
104-
class.extends(exception_class);
104+
class.extends(StateClass::from_name("Exception"));
105105
class
106106
}
107107

@@ -155,7 +155,7 @@ Now let's begin to finish the logic.
155155
# pub fn make_exception_class() -> ClassEntity<()> {
156156
# let mut class = ClassEntity::new(EXCEPTION_CLASS_NAME);
157157
# // The `extends` is same as the PHP class `extends`.
158-
# class.extends(exception_class);
158+
# class.extends(StateClass::from_name("Exception"));
159159
# class
160160
# }
161161
#

phper-doc/doc/_06_module/_06_register_class/index.md

+10-2
Original file line numberDiff line numberDiff line change
@@ -38,13 +38,21 @@ class Foo {}
3838

3939
## Extends & implements
4040

41-
If you want the class `Foo` extends `Bar`, and implements interface `Stringable`:
41+
To allow a class to extend another, you can use `ClassEntity.extends(StateClass<T>)` for classes implemented
42+
in your module. A StateClass can either be obtained by registering your own class against the module, or
43+
by looking up the class by name (for example, for PHP built-in classes like `Exception`).
44+
45+
If you want class `Foo` extends `Bar`, and implements interface `Stringable`:
4246

4347
```rust,no_run
4448
use phper::classes::{ClassEntity, ClassEntry, Interface};
49+
use phper::{modules::Module, php_get_module};
50+
51+
let mut module = Module::new("test", "0.1", "");
4552
53+
let bar = module.add_class(ClassEntity::new("Bar"));
4654
let mut foo = ClassEntity::new("Foo");
47-
foo.extends(|| ClassEntry::from_globals("Bar").unwrap());
55+
foo.extends(bar);
4856
foo.implements(Interface::from_name("Stringable"));
4957
```
5058

phper-doc/doc/_06_module/_07_register_interface/index.md

+3-4
Original file line numberDiff line numberDiff line change
@@ -41,12 +41,11 @@ interface Foo {}
4141
If you want the interface `Foo` extends `ArrayAccess` and `Iterator` interfaces.
4242

4343
```rust,no_run
44-
use phper::classes::{InterfaceEntity, ClassEntry};
45-
use phper::classes::{array_access_class, iterator_class};
44+
use phper::classes::{Interface, InterfaceEntity, ClassEntry};
4645
4746
let mut foo = InterfaceEntity::new("Foo");
48-
foo.extends(|| array_access_class());
49-
foo.extends(|| iterator_class());
47+
foo.extends(Interface::from_name("ArrayAccess"));
48+
foo.extends(Interface::from_name("Iterator"));
5049
```
5150

5251
Same as:

phper/src/classes.rs

+58-27
Original file line numberDiff line numberDiff line change
@@ -28,26 +28,14 @@ use std::{
2828
ffi::{CString, c_char, c_void},
2929
fmt::Debug,
3030
marker::PhantomData,
31-
mem::{ManuallyDrop, replace, size_of, zeroed},
31+
mem::{ManuallyDrop, replace, size_of, transmute, zeroed},
3232
os::raw::c_int,
3333
ptr,
3434
ptr::null_mut,
3535
rc::Rc,
3636
slice,
3737
};
3838

39-
/// Predefined interface `Iterator`.
40-
#[inline]
41-
pub fn iterator_class<'a>() -> &'a ClassEntry {
42-
unsafe { ClassEntry::from_ptr(zend_ce_iterator) }
43-
}
44-
45-
/// Predefined interface `ArrayAccess`.
46-
#[inline]
47-
pub fn array_access_class<'a>() -> &'a ClassEntry {
48-
unsafe { ClassEntry::from_ptr(zend_ce_arrayaccess) }
49-
}
50-
5139
/// Wrapper of [zend_class_entry].
5240
#[derive(Clone)]
5341
#[repr(transparent)]
@@ -287,13 +275,26 @@ fn find_global_class_entry_ptr(name: impl AsRef<str>) -> *mut zend_class_entry {
287275
/// ```
288276
pub struct StateClass<T> {
289277
inner: Rc<RefCell<*mut zend_class_entry>>,
278+
name: Option<String>,
290279
_p: PhantomData<T>,
291280
}
292281

282+
impl StateClass<()> {
283+
/// Create from name, which will be looked up from globals.
284+
pub fn from_name(name: impl Into<String>) -> Self {
285+
Self {
286+
inner: Rc::new(RefCell::new(null_mut())),
287+
name: Some(name.into()),
288+
_p: PhantomData,
289+
}
290+
}
291+
}
292+
293293
impl<T> StateClass<T> {
294294
fn null() -> Self {
295295
Self {
296296
inner: Rc::new(RefCell::new(null_mut())),
297+
name: None,
297298
_p: PhantomData,
298299
}
299300
}
@@ -304,7 +305,11 @@ impl<T> StateClass<T> {
304305

305306
/// Converts to class entry.
306307
pub fn as_class_entry(&self) -> &ClassEntry {
307-
unsafe { ClassEntry::from_mut_ptr(*self.inner.borrow()) }
308+
if let Some(name) = &self.name {
309+
ClassEntry::from_globals(name).unwrap()
310+
} else {
311+
unsafe { ClassEntry::from_mut_ptr(*self.inner.borrow()) }
312+
}
308313
}
309314

310315
/// Create the object from class and call `__construct` with arguments.
@@ -333,6 +338,7 @@ impl<T> Clone for StateClass<T> {
333338
fn clone(&self) -> Self {
334339
Self {
335340
inner: self.inner.clone(),
341+
name: self.name.clone(),
336342
_p: self._p,
337343
}
338344
}
@@ -427,7 +433,7 @@ pub struct ClassEntity<T: 'static> {
427433
state_constructor: Rc<StateConstructor>,
428434
method_entities: Vec<MethodEntity>,
429435
property_entities: Vec<PropertyEntity>,
430-
parent: Option<Box<dyn Fn() -> &'static ClassEntry>>,
436+
parent: Option<StateClass<()>>,
431437
interfaces: Vec<Interface>,
432438
constants: Vec<ConstantEntity>,
433439
bind_class: StateClass<T>,
@@ -550,19 +556,40 @@ impl<T: 'static> ClassEntity<T> {
550556
/// Register class to `extends` the parent class.
551557
///
552558
/// *Because in the `MINIT` phase, the class starts to register, so the*
553-
/// *closure is used to return the `ClassEntry` to delay the acquisition of*
559+
/// *`ClassEntry` is looked up by name to delay the acquisition of*
554560
/// *the class.*
555561
///
556562
/// # Examples
557563
///
558564
/// ```no_run
559-
/// use phper::classes::{ClassEntity, ClassEntry};
565+
/// use phper::{
566+
/// classes::{ClassEntity, ClassEntry, StateClass},
567+
/// modules::Module,
568+
/// php_get_module,
569+
/// };
560570
///
561-
/// let mut class = ClassEntity::new("MyException");
562-
/// class.extends(|| ClassEntry::from_globals("Exception").unwrap());
571+
/// #[php_get_module]
572+
/// pub fn get_module() -> Module {
573+
/// let mut module = Module::new(
574+
/// env!("CARGO_CRATE_NAME"),
575+
/// env!("CARGO_PKG_VERSION"),
576+
/// env!("CARGO_PKG_AUTHORS"),
577+
/// );
578+
///
579+
/// let foo = module.add_class(ClassEntity::new("Foo"));
580+
/// let mut bar = ClassEntity::new("Bar");
581+
/// bar.extends(foo);
582+
/// module.add_class(bar);
583+
///
584+
/// let mut ex = ClassEntity::new("MyException");
585+
/// ex.extends(StateClass::from_name("Exception"));
586+
/// module.add_class(ex);
587+
///
588+
/// module
589+
/// }
563590
/// ```
564-
pub fn extends(&mut self, parent: impl Fn() -> &'static ClassEntry + 'static) {
565-
self.parent = Some(Box::new(parent));
591+
pub fn extends<S: 'static>(&mut self, parent: StateClass<S>) {
592+
self.parent = Some(unsafe { transmute::<StateClass<S>, StateClass<()>>(parent) });
566593
}
567594

568595
/// Register class to `implements` the interface, due to the class can
@@ -634,7 +661,7 @@ impl<T: 'static> ClassEntity<T> {
634661
let parent: *mut zend_class_entry = self
635662
.parent
636663
.as_ref()
637-
.map(|parent| parent())
664+
.map(|parent| parent.as_class_entry())
638665
.map(|entry| entry.as_ptr() as *mut _)
639666
.unwrap_or(null_mut());
640667

@@ -800,20 +827,24 @@ impl InterfaceEntity {
800827
/// Register interface to `extends` the interfaces, due to the interface can
801828
/// extends multi interface, so this method can be called multi time.
802829
///
803-
/// *Because in the `MINIT` phase, the class starts to register, so the*
830+
/// *Because in the `MINIT` phase, the class starts to register, a*
804831
/// *closure is used to return the `ClassEntry` to delay the acquisition of*
805832
/// *the class.*
806833
///
807834
/// # Examples
808835
///
809836
/// ```no_run
810-
/// use phper::classes::{ClassEntry, InterfaceEntity};
837+
/// use phper::classes::{Interface, InterfaceEntity};
811838
///
812839
/// let mut interface = InterfaceEntity::new("MyInterface");
813-
/// interface.extends(|| ClassEntry::from_globals("Stringable").unwrap());
840+
/// interface.extends(Interface::from_name("Stringable"));
814841
/// ```
815-
pub fn extends(&mut self, interface: impl Fn() -> &'static ClassEntry + 'static) {
816-
self.extends.push(Box::new(interface));
842+
pub fn extends(&mut self, interface: Interface) {
843+
self.extends.push(Box::new(move || {
844+
let entry: &'static ClassEntry =
845+
unsafe { std::mem::transmute(interface.as_class_entry()) };
846+
entry
847+
}));
817848
}
818849

819850
#[allow(clippy::useless_conversion)]

tests/integration/src/classes.rs

+14-9
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,7 @@
1010

1111
use phper::{
1212
alloc::RefClone,
13-
classes::{
14-
ClassEntity, ClassEntry, Interface, InterfaceEntity, Visibility, array_access_class,
15-
iterator_class,
16-
},
13+
classes::{ClassEntity, ClassEntry, Interface, InterfaceEntity, StateClass, Visibility},
1714
functions::{Argument, ReturnType},
1815
modules::Module,
1916
types::{ArgumentTypeHint, ReturnTypeHint},
@@ -23,10 +20,11 @@ use std::{collections::HashMap, convert::Infallible};
2320

2421
pub fn integrate(module: &mut Module) {
2522
integrate_a(module);
26-
integrate_foo(module);
23+
let foo_class = integrate_foo(module);
2724
integrate_i_bar(module);
2825
integrate_static_props(module);
2926
integrate_i_constants(module);
27+
integrate_bar_extends_foo(module, foo_class);
3028
#[cfg(phper_major_version = "8")]
3129
integrate_stringable(module);
3230
}
@@ -78,7 +76,7 @@ struct Foo {
7876
array: HashMap<i64, ZVal>,
7977
}
8078

81-
fn integrate_foo(module: &mut Module) {
79+
fn integrate_foo(module: &mut Module) -> StateClass<Foo> {
8280
let mut class = ClassEntity::new_with_state_constructor("IntegrationTest\\Foo", || Foo {
8381
position: 0,
8482
array: Default::default(),
@@ -169,14 +167,14 @@ fn integrate_foo(module: &mut Module) {
169167
.argument(Argument::new("offset").with_type_hint(ArgumentTypeHint::Mixed))
170168
.return_type(ReturnType::new(ReturnTypeHint::Void));
171169

172-
module.add_class(class);
170+
module.add_class(class)
173171
}
174172

175173
fn integrate_i_bar(module: &mut Module) {
176174
let mut interface = InterfaceEntity::new(r"IntegrationTest\IBar");
177175

178-
interface.extends(|| array_access_class());
179-
interface.extends(|| iterator_class());
176+
interface.extends(Interface::from_name("ArrayAccess"));
177+
interface.extends(Interface::from_name("Iterator"));
180178

181179
interface
182180
.add_method("doSomethings")
@@ -224,6 +222,13 @@ fn integrate_static_props(module: &mut Module) {
224222
module.add_class(class);
225223
}
226224

225+
fn integrate_bar_extends_foo(module: &mut Module, foo_class: StateClass<Foo>) {
226+
let mut cls = ClassEntity::new(r"IntegrationTest\BarExtendsFoo");
227+
cls.extends(foo_class);
228+
cls.add_method("test", Visibility::Public, |_, _| phper::ok(()));
229+
module.add_class(cls);
230+
}
231+
227232
#[cfg(phper_major_version = "8")]
228233
fn integrate_stringable(module: &mut Module) {
229234
use phper::{functions::ReturnType, types::ReturnTypeHint};

tests/integration/tests/php/classes.php

+5
Original file line numberDiff line numberDiff line change
@@ -100,3 +100,8 @@ class Foo2 extends IntegrationTest\Foo {}
100100
assert_false(IntegrationTest\IConstants::CST_FALSE);
101101
assert_eq(100, IntegrationTest\IConstants::CST_INT);
102102
assert_eq(10.0, IntegrationTest\IConstants::CST_FLOAT);
103+
104+
// Test module class extends module class
105+
$bar = new \IntegrationTest\BarExtendsFoo; //Bar should extend Foo
106+
$reflection = new ReflectionClass($bar);
107+
assert_true($reflection->isSubclassOf(IntegrationTest\Foo::class));

0 commit comments

Comments
 (0)