Skip to content

Commit 4a4370c

Browse files
Add ability to send Rust closures to PHP (#58)
* Start work on returning new objects Change `IntoZval` to consume `self` * Add conversion from vector reference to PHP hashtable through clones * `set_binary` now only takes `Vec` Used to take `AsRef<[T]>` to allow users to pass arrays, however, this causes the data to be cloned even if the user passes a `Vec`. * Rename `as_zval` to `into_zval` to match Rust conventions * Started work on closures * Finish implementation of closures * Document closures, add to prelude, feature gate * Fixed closure example
1 parent 73526d6 commit 4a4370c

32 files changed

+1067
-294
lines changed

.github/workflows/build.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ jobs:
4747
LIBCLANG_PATH: ${{ runner.temp }}/llvm-${{ matrix.llvm }}/lib
4848
with:
4949
command: build
50-
args: --release
50+
args: --release --features alloc,closure
5151
- name: Test guide examples
5252
run: |
5353
mdbook test guide -L target/release/deps

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,9 @@ cc = "1.0.67"
2222
[features]
2323
zts = []
2424
debug = []
25+
2526
alloc = []
27+
closure = []
2628

2729
[workspace]
2830
members = [

example/skel/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ edition = "2018"
77
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
88

99
[dependencies]
10-
ext-php-rs = { path = "../../", features = ["alloc"] }
10+
ext-php-rs = { path = "../../", features = ["alloc", "closure"] }
1111

1212
[lib]
1313
name = "skel"

example/skel/php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,4 @@ case "$(uname)" in
66
*) EXT="so" ;;
77
esac
88

9-
php -dextension=$PWD/../../target/debug/libskel.$EXT "${@}"
9+
cargo build && php -dextension=$PWD/../../target/debug/libskel.$EXT "${@}"

example/skel/src/lib.rs

Lines changed: 80 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,18 @@
11
mod allocator;
22

33
use allocator::PhpAllocator;
4-
use ext_php_rs::{php_class, prelude::*};
4+
use ext_php_rs::{
5+
php::{
6+
exceptions::PhpException,
7+
types::{
8+
callable::Callable,
9+
closure::Closure,
10+
object::{ClassObject, ClassRef},
11+
},
12+
},
13+
php_class,
14+
prelude::*,
15+
};
516

617
// #[php_function]
718
// pub fn hello_world() -> String {
@@ -112,21 +123,47 @@ static GLOBAL: PhpAllocator = PhpAllocator::new();
112123
#[php_class]
113124
#[property(test = 0)]
114125
#[property(another = "Hello world")]
115-
#[derive(Default)]
126+
#[derive(Default, Debug, Clone)]
116127
pub struct Test {
117-
test: String,
128+
pub test: String,
129+
}
130+
131+
#[php_class]
132+
#[derive(Default)]
133+
struct PhpFuture {
134+
then: Option<Callable<'static>>,
118135
}
119136

120137
#[php_impl]
121-
impl Test {
122-
pub fn get(&mut self) -> &Test {
123-
use ext_php_rs::php::types::object::ZendObjectOverride;
138+
impl PhpFuture {
139+
pub fn then(&mut self, then: Callable<'static>) {
140+
self.then = Some(then);
141+
}
124142

125-
dbg!(unsafe { self.get_property::<String>("test") });
126-
unsafe { self.set_property("test", "another string") };
127-
self
143+
pub fn now(&self) -> Result<(), PhpException> {
144+
if let Some(then) = &self.then {
145+
then.try_call(vec![&"Hello"]).unwrap();
146+
Ok(())
147+
} else {
148+
Err(PhpException::default("No `then`".into()))
149+
}
128150
}
129151

152+
pub fn obj(&self) -> ClassObject<Test> {
153+
let obj = ClassObject::new(Test {
154+
test: "Hello world from class entry :)".into(),
155+
});
156+
157+
obj
158+
}
159+
160+
pub fn return_self(&self) -> ClassRef<PhpFuture> {
161+
ClassRef::from_ref(self).unwrap()
162+
}
163+
}
164+
165+
#[php_impl]
166+
impl Test {
130167
pub fn set_str(&mut self, str: String) {
131168
self.test = str;
132169
}
@@ -136,8 +173,41 @@ impl Test {
136173
}
137174
}
138175

176+
#[php_function]
177+
pub fn get_closure() -> Closure {
178+
let mut x = 100;
179+
Closure::wrap(Box::new(move || {
180+
x += 5;
181+
format!("x: {}", x)
182+
}) as Box<dyn FnMut() -> String>)
183+
}
184+
185+
#[php_function]
186+
pub fn fn_once() -> Closure {
187+
let x = "Hello".to_string();
188+
Closure::wrap_once(Box::new(move || {
189+
println!("val here: {}", &x);
190+
x
191+
}) as Box<dyn FnOnce() -> String>)
192+
}
193+
194+
#[php_function]
195+
pub fn closure_get_string() -> Closure {
196+
// Return a closure which takes two integers and returns a string
197+
Closure::wrap(Box::new(|a, b| format!("A: {} B: {}", a, b)) as Box<dyn Fn(i32, i32) -> String>)
198+
}
199+
200+
#[php_function]
201+
pub fn closure_count() -> Closure {
202+
let mut count = 0i32;
203+
204+
Closure::wrap(Box::new(move |a: i32| {
205+
count += a;
206+
count
207+
}) as Box<dyn FnMut(i32) -> i32>)
208+
}
209+
139210
#[php_module]
140211
pub fn module(module: ModuleBuilder) -> ModuleBuilder {
141-
// module.info_function(php_module_info)
142212
module
143213
}

example/skel/test.php

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,9 @@
11
<?php
22

3-
include __DIR__.'/vendor/autoload.php';
3+
$x = fn_once();
4+
$x();
5+
$x();
46

5-
var_dump('program starting');
6-
$x = new Test();
7-
$x->set_str('Hello World');
8-
var_dump($x->get_str());
9-
var_dump($x->get());
10-
# $x->test = 'hello world';
11-
var_dump($x->get());
12-
var_dump($x->get_str());
13-
// var_dump($x);
14-
var_dump('program done');
15-
// var_dump($x->get_str());
7+
// $x = get_closure();
8+
9+
// var_dump($x(5));

ext-php-rs-derive/src/class.rs

Lines changed: 5 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -67,38 +67,16 @@ pub fn parser(args: AttributeArgs, mut input: ItemStruct) -> Result<TokenStream>
6767

6868
let ItemStruct { ident, .. } = &input;
6969
let class_name = args.name.unwrap_or_else(|| ident.to_string());
70-
let handlers = Ident::new(
71-
&format!("_{}_OBJECT_HANDLERS", ident.to_string()),
72-
Span::call_site(),
73-
);
74-
let class_entry = Ident::new(
75-
&format!("_{}_CLASS_ENTRY", ident.to_string()),
76-
Span::call_site(),
77-
);
70+
let meta = Ident::new(&format!("_{}_META", ident.to_string()), Span::call_site());
7871

7972
let output = quote! {
8073
#input
8174

82-
static #handlers: ::ext_php_rs::php::types::object::Handlers<#ident> = ::ext_php_rs::php::types::object::Handlers::new();
83-
static mut #class_entry: ::std::option::Option<&'static ::ext_php_rs::php::class::ClassEntry> = None;
75+
static #meta: ::ext_php_rs::php::types::object::ClassMetadata<#ident> = ::ext_php_rs::php::types::object::ClassMetadata::new();
8476

85-
impl ::ext_php_rs::php::types::object::ZendObjectOverride for #ident {
86-
unsafe extern "C" fn create_object(
87-
ce: *mut ::ext_php_rs::php::class::ClassEntry,
88-
) -> *mut ::ext_php_rs::php::types::object::ZendObject {
89-
::ext_php_rs::php::types::object::ZendClassObject::<#ident>::new_ptr(ce, #handlers.get())
90-
.expect("Failed to allocate memory for new Zend object.")
91-
}
92-
93-
fn get_class() -> &'static ::ext_php_rs::php::class::ClassEntry {
94-
unsafe {
95-
#class_entry
96-
.expect(concat!("Class `", #class_name, "` has not been initialized yet."))
97-
}
98-
}
99-
100-
fn set_class(ce: &'static ::ext_php_rs::php::class::ClassEntry) {
101-
unsafe { #class_entry.replace(ce) };
77+
impl ::ext_php_rs::php::types::object::RegisteredClass for #ident {
78+
fn get_metadata() -> &'static ::ext_php_rs::php::types::object::ClassMetadata<Self> {
79+
&#meta
10280
}
10381
}
10482
};

ext-php-rs-derive/src/method.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,8 @@ fn build_arg_definitions(args: &[Arg]) -> (Vec<TokenStream>, bool) {
142142
_static = false;
143143

144144
quote! {
145-
let #mutability this = match ::ext_php_rs::php::types::object::ZendClassObject::<Self>::get(ex) {
145+
// SAFETY: We are calling this on an execution data from a class method.
146+
let #mutability this = match unsafe { ex.get_object::<Self>() } {
146147
Some(this) => this,
147148
None => return ::ext_php_rs::php::exceptions::throw(
148149
::ext_php_rs::php::class::ClassEntry::exception(),

ext-php-rs-derive/src/startup_function.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ pub fn parser(input: ItemFn) -> Result<TokenStream> {
2828
#(#stmts)*
2929
}
3030

31+
::ext_php_rs::php::module::ext_php_rs_startup();
32+
3133
#(#classes)*
3234
#(#constants)*
3335

@@ -48,7 +50,7 @@ fn build_classes(classes: &HashMap<String, Class>) -> Result<Vec<TokenStream>> {
4850
.map(|(name, class)| {
4951
let Class { class_name, .. } = &class;
5052
let ident = Ident::new(name, Span::call_site());
51-
let class_entry = Ident::new(&format!("_{}_CLASS_ENTRY", name), Span::call_site());
53+
let meta = Ident::new(&format!("_{}_META", name), Span::call_site());
5254
let methods = class.methods.iter().map(|method| {
5355
let builder = method.get_builder(&ident);
5456
let flags = method.get_flags();
@@ -122,7 +124,8 @@ fn build_classes(classes: &HashMap<String, Class>) -> Result<Vec<TokenStream>> {
122124
.object_override::<#ident>()
123125
.build()
124126
.expect(concat!("Unable to build class `", #class_name, "`"));
125-
unsafe { #class_entry.replace(class) };
127+
128+
#meta.set_ce(class);
126129
}})
127130
})
128131
.collect::<Result<Vec<_>>>()

guide/src/SUMMARY.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
- [`HashMap`](./types/hashmap.md)
1313
- [`Binary`](./types/binary.md)
1414
- [`Option`](./types/option.md)
15+
- [`Object`](./types/object.md)
16+
- [`Closure`](./types/closure.md)
1517
- [Macros](./macros/index.md)
1618
- [Module](./macros/module.md)
1719
- [Module Startup Function](./macros/module_startup.md)

0 commit comments

Comments
 (0)