Skip to content

Commit f805534

Browse files
authored
refactor ClassEntity.implements (#189)
Instead of taking a function to delay fetching ClassEntry, accept an Interface, and hide the late-fetching
1 parent f3ef58c commit f805534

File tree

4 files changed

+34
-20
lines changed

4 files changed

+34
-20
lines changed

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,14 +41,14 @@ class Foo {}
4141
If you want the class `Foo` extends `Bar`, and implements interface `Stringable`:
4242

4343
```rust,no_run
44-
use phper::classes::{ClassEntity, ClassEntry};
44+
use phper::classes::{ClassEntity, ClassEntry, Interface};
4545
4646
let mut foo = ClassEntity::new("Foo");
4747
foo.extends(|| ClassEntry::from_globals("Bar").unwrap());
48-
foo.implements(|| ClassEntry::from_globals("Stringable").unwrap());
48+
foo.implements(Interface::from_name("Stringable"));
4949
```
5050

51-
You should implements the method `Stringable::__toString` after implemented
51+
You should implement the method `Stringable::__toString` after implementing
5252
`Stringable`, otherwise the class `Foo` will become abstract class.
5353

5454
## Add properties

phper/src/classes.rs

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -376,22 +376,39 @@ impl<T> Clone for StateClass<T> {
376376
#[derive(Clone)]
377377
pub struct Interface {
378378
inner: Rc<RefCell<*mut zend_class_entry>>,
379+
name: Option<String>,
379380
}
380381

381382
impl Interface {
382383
fn null() -> Self {
383384
Self {
384385
inner: Rc::new(RefCell::new(null_mut())),
386+
name: None,
387+
}
388+
}
389+
390+
/// Create a new interface from global name (eg "Stringable", "ArrayAccess")
391+
pub fn from_name(name: impl Into<String>) -> Self {
392+
Self {
393+
inner: Rc::new(RefCell::new(null_mut())),
394+
name: Some(name.into()),
385395
}
386396
}
387397

388398
fn bind(&self, ptr: *mut zend_class_entry) {
399+
if self.name.is_some() {
400+
panic!("Cannot bind() an Interface created with from_name()");
401+
}
389402
*self.inner.borrow_mut() = ptr;
390403
}
391404

392405
/// Converts to class entry.
393406
pub fn as_class_entry(&self) -> &ClassEntry {
394-
unsafe { ClassEntry::from_mut_ptr(*self.inner.borrow()) }
407+
if let Some(name) = &self.name {
408+
ClassEntry::from_globals(name).unwrap()
409+
} else {
410+
unsafe { ClassEntry::from_mut_ptr(*self.inner.borrow()) }
411+
}
395412
}
396413
}
397414

@@ -411,7 +428,7 @@ pub struct ClassEntity<T: 'static> {
411428
method_entities: Vec<MethodEntity>,
412429
property_entities: Vec<PropertyEntity>,
413430
parent: Option<Box<dyn Fn() -> &'static ClassEntry>>,
414-
interfaces: Vec<Box<dyn Fn() -> &'static ClassEntry>>,
431+
interfaces: Vec<Interface>,
415432
constants: Vec<ConstantEntity>,
416433
bind_class: StateClass<T>,
417434
state_cloner: Option<Rc<StateCloner>>,
@@ -552,23 +569,22 @@ impl<T: 'static> ClassEntity<T> {
552569
/// implement multi interface, so this method can be called multi time.
553570
///
554571
/// *Because in the `MINIT` phase, the class starts to register, so the*
555-
/// *closure is used to return the `ClassEntry` to delay the acquisition of*
556-
/// *the class.*
572+
/// *`ClassEntry` is not fetched until later*
557573
///
558574
/// # Examples
559575
///
560576
/// ```no_run
561-
/// use phper::classes::{ClassEntity, ClassEntry};
577+
/// use phper::classes::{ClassEntity, ClassEntry, Interface};
562578
///
563579
/// let mut class = ClassEntity::new("MyClass");
564-
/// class.implements(|| ClassEntry::from_globals("Stringable").unwrap());
580+
/// class.implements(Interface::from_name("Stringable"));
565581
///
566582
/// // Here you have to the implement the method `__toString()` in `Stringable`
567583
/// // for `MyClass`, otherwise `MyClass` will become abstract class.
568584
/// // ...
569585
/// ```
570-
pub fn implements(&mut self, interface: impl Fn() -> &'static ClassEntry + 'static) {
571-
self.interfaces.push(Box::new(interface));
586+
pub fn implements(&mut self, interface: Interface) {
587+
self.interfaces.push(interface);
572588
}
573589

574590
/// Add the state clone function, called when cloning PHP object.
@@ -633,7 +649,7 @@ impl<T: 'static> ClassEntity<T> {
633649
self.bind_class.bind(class_ce);
634650

635651
for interface in &self.interfaces {
636-
let interface_ce = interface().as_ptr();
652+
let interface_ce = interface.as_class_entry().as_ptr();
637653
zend_class_implements(class_ce, 1, interface_ce);
638654
}
639655

tests/integration/src/classes.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@
1111
use phper::{
1212
alloc::RefClone,
1313
classes::{
14-
ClassEntity, ClassEntry, InterfaceEntity, Visibility, array_access_class, iterator_class,
14+
ClassEntity, ClassEntry, Interface, InterfaceEntity, Visibility, array_access_class,
15+
iterator_class,
1516
},
1617
functions::{Argument, ReturnType},
1718
modules::Module,
@@ -83,8 +84,8 @@ fn integrate_foo(module: &mut Module) {
8384
array: Default::default(),
8485
});
8586

86-
class.implements(iterator_class);
87-
class.implements(array_access_class);
87+
class.implements(Interface::from_name("Iterator"));
88+
class.implements(Interface::from_name("ArrayAccess"));
8889

8990
// Implement Iterator interface.
9091
class
@@ -228,7 +229,7 @@ fn integrate_stringable(module: &mut Module) {
228229
use phper::{functions::ReturnType, types::ReturnTypeHint};
229230

230231
let mut cls = ClassEntity::new(r"IntegrationTest\FooString");
231-
cls.implements(|| ClassEntry::from_globals("Stringable").unwrap());
232+
cls.implements(Interface::from_name("Stringable"));
232233
cls.add_method("__toString", Visibility::Public, |_this, _: &mut [ZVal]| {
233234
phper::ok("string")
234235
})

tests/integration/src/typehints.rs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -92,11 +92,8 @@ fn make_foo_handler() -> ClassEntity<()> {
9292

9393
fn make_foo_class(i_foo: Interface) -> ClassEntity<()> {
9494
let mut class = ClassEntity::new(r"IntegrationTest\TypeHints\Foo");
95+
class.implements(i_foo);
9596

96-
// leak Interface so that ClassEntry can be retrieved later, during module
97-
// startup
98-
let i_foo_copy: &'static Interface = Box::leak(Box::new(i_foo));
99-
class.implements(move || i_foo_copy.as_class_entry());
10097
class
10198
.add_method("getValue", Visibility::Public, |this, _| {
10299
let value = this

0 commit comments

Comments
 (0)