davidcole1340 / ext-php-rs Goto Github PK
View Code? Open in Web Editor NEWBindings for the Zend API to build PHP extensions natively in Rust.
License: Apache License 2.0
Bindings for the Zend API to build PHP extensions natively in Rust.
License: Apache License 2.0
Hi!
First of all, thanks for developing this crate, it works wonderfully :)
However, there is a scenario that I'd like to use this crate that I don't fully understand how to do it. I'd like to pass a binary data string to Rust code, and despite having a look at #31 I don't fully understand how to use this.
I have the following code
fn do_something(input: &[u8]) -> u64 {
// Do stuff
}
/// Computes some things
///
/// @param $input string Your input.
///
/// @return int Your result
#[php_function]
pub fn computation_rs(input: &str) -> u64 {
do_something(input.as_bytes())
}
// Required to register the extension with PHP.
#[php_module]
pub fn module(module: ModuleBuilder) -> ModuleBuilder {
module
}
And my PHP code looks like
$data = random_bytes(32);
$result = computation_rs($data);
echo $result;
If I replace $data
with a valid string, this works, but I haven't managed to pass an array of bytes. I've tried setting the input
type in rust to Vec<u8>
but it didn't work either 🤔
Do you have any pointers?
Thanks!
With this change in rust-analyzer, expansion of ext-php-rs macros errors out as they are no longer expanded in order. This is because Rust provides no guarantees of macro expansion, and the library abuses the fact that rustc expands them (at the moment) in order by maintaining a global proc-macro state.
Currently the solution to this is to disable macro expansion in rust-analyzer:
"rust-analyzer.experimental.procAttrMacros": false
It could be best to move to remove this global state. The global state is used to maintain a register of all the entities to be registered in the PHP module startup and get module functions.
retval.set_array(vec![1, 2, 3, 4]);
var_dump(function());
array(4) {
[0]=>
int(1)
[1]=>
int(2)
[2]=>
int(3)
[3]=>
int(4)
}
[Mon Apr 19 11:37:15 2021] Script: '/home/david/Projects/opusphp/opus/test.php'
/home/david/.phpbrew/build/8.0.3-debug/Zend/zend_hash.c(278) : Freeing 0x00007f15d60588a0 (56 bytes), script=/home/david/Projects/opusphp/opus/test.php
[Mon Apr 19 11:37:15 2021] Script: '/home/david/Projects/opusphp/opus/test.php'
/home/david/.phpbrew/build/8.0.3-debug/Zend/zend_hash.c(153) : Freeing 0x00007f15d605c3c0 (264 bytes), script=/home/david/Projects/opusphp/opus/test.php
=== Total 2 memory leaks detected ===
ZvalTypeFlags
are not actually bitflags as the data types are exclusive and cannot be logically OR'd together. Could introduce a structure like so:
pub struct Type {
data_type: DataType,
is_refcounted: bool,
is_collectable: bool,
is_immutable: bool,
is_persistent: bool,
}
impl From<u32> for Type { ... }
Check the zend API version to ensure the library will compile with the given version.
Ability to derive FromZval
(and potentially IntoZval
) using a derive macro. Could be used in two situations:
#[derive(FromZval)]
pub enum IntOrBool {
Integer(u32),
Boolean(bool),
}
// translates to
impl FromZval<'_> for IntOrBool {
const TYPE: DataType = DataType::Mixed;
fn from_zval(zval: &'_ Zval) -> Option<Self> {
if let Some(integer) = u32::from_zval(zval) {
Some(integer)
} else if let Some(boolean) = bool::from_zval(zval) {
Some(boolean)
} else {
None
}
}
}
stdClass
into Rust. Example:#[derive(FromZval)]
pub struct ExampleStdClass {
a: i32,
b: String,
c: bool,
}
// translates to
impl FromZval<'_> for ExampleStdClass {
const TYPE: DataType = DataType::Object(None);
fn from_zval(zval: &'_ Zval) -> Option<Self> {
let obj = zval.object()?;
Some(Self {
a: obj.get_property("a").ok()?,
b: obj.get_property("b").ok()?,
c: obj.get_property("c").ok()?,
})
}
}
Types such as ZendStr
have an owned variant ZendString
. This can be generically replaced with a type ZBox<T>
, where T
implements a new trait ZBoxable
, which is implemented on Zend heap-allocated types, such as ZendStr
, ZendObject
and HashTable
:
pub struct ZBox<T: ZBoxable>(NonNull<T>);
pub unsafe trait ZBoxable {
fn free(&mut self) {
// by default, just free the memory using the ZMM
// downstream types like strings and objects can override with their respective release functions
unsafe { efree(self) };
}
}
ZBox
will dereference to T
, and downstream modules such as string
can implement on ZBox<ZendStr>
, such as impl TryFrom<String> for ZBox<ZendStr>
.
Allows types to mutate method properties as well as provides a better way to return self-references #86
Hello,
any idea what dependency am I missing?
I am unable to compile hello world example because of following errors:
Compiling ext-php-rs v0.7.3
The following warnings were emitted during compilation:
warning: In file included from src/wrapper.c:1:0:
warning: src/wrapper.h:7:71: error: unknown type name ‘bool’; did you mean ‘_Bool’?
warning: zend_string *ext_php_rs_zend_string_init(const char *str, size_t len, bool persistent);
warning: ^~~~
warning: _Bool
warning: src/wrapper.c:3:71: error: unknown type name ‘bool’; did you mean ‘_Bool’?
warning: zend_string *ext_php_rs_zend_string_init(const char *str, size_t len, bool persistent)
warning: ^~~~
warning: _Bool
error: failed to run custom build command for `ext-php-rs v0.7.3`
Caused by:
process didn't exit successfully: `/home/mrceperka/projects/mrceperka/ext-php-rs-example/target/debug/build/ext-php-rs-7bf2e69a5b04eccc/build-script-build` (exit status: 1)
--- stdout
cargo:rerun-if-changed=src/wrapper.h
cargo:rerun-if-changed=src/wrapper.c
cargo:rerun-if-changed=allowed_bindings.rs
TARGET = Some("x86_64-unknown-linux-gnu")
OPT_LEVEL = Some("0")
HOST = Some("x86_64-unknown-linux-gnu")
CC_x86_64-unknown-linux-gnu = None
CC_x86_64_unknown_linux_gnu = None
HOST_CC = None
CC = None
CFLAGS_x86_64-unknown-linux-gnu = None
CFLAGS_x86_64_unknown_linux_gnu = None
HOST_CFLAGS = None
CFLAGS = None
CRATE_CC_NO_DEFAULTS = None
DEBUG = Some("true")
CARGO_CFG_TARGET_FEATURE = Some("fxsr,sse,sse2")
running: "cc" "-O0" "-ffunction-sections" "-fdata-sections" "-fPIC" "-g" "-fno-omit-frame-pointer" "-m64" "-I" "/usr/include/php/20180731" "-I" "/usr/include/php/20180731/main" "-I" "/usr/include/php/20180731/TSRM" "-I" "/usr/include/php/20180731/Zend" "-I" "/usr/include/php/20180731/ext" "-I" "/usr/include/php/20180731/ext/date/lib\n" "-Wall" "-Wextra" "-o" "/home/mrceperka/projects/mrceperka/ext-php-rs-example/target/debug/build/ext-php-rs-adeee84e1b8aaa1e/out/src/wrapper.o" "-c" "src/wrapper.c"
cargo:warning=In file included from src/wrapper.c:1:0:
cargo:warning=src/wrapper.h:7:71: error: unknown type name ‘bool’; did you mean ‘_Bool’?
cargo:warning= zend_string *ext_php_rs_zend_string_init(const char *str, size_t len, bool persistent);
cargo:warning= ^~~~
cargo:warning= _Bool
cargo:warning=src/wrapper.c:3:71: error: unknown type name ‘bool’; did you mean ‘_Bool’?
cargo:warning= zend_string *ext_php_rs_zend_string_init(const char *str, size_t len, bool persistent)
cargo:warning= ^~~~
cargo:warning= _Bool
exit status: 1
--- stderr
error occurred: Command "cc" "-O0" "-ffunction-sections" "-fdata-sections" "-fPIC" "-g" "-fno-omit-frame-pointer" "-m64" "-I" "/usr/include/php/20180731" "-I" "/usr/include/php/20180731/main" "-I" "/usr/include/php/20180731/TSRM" "-I" "/usr/include/php/20180731/Zend" "-I" "/usr/include/php/20180731/ext" "-I" "/usr/include/php/20180731/ext/date/lib\n" "-Wall" "-Wextra" "-o" "/home/mrceperka/projects/mrceperka/ext-php-rs-example/target/debug/build/ext-php-rs-adeee84e1b8aaa1e/out/src/wrapper.o" "-c" "src/wrapper.c" with args "cc" did not execute successfully (status code exit status: 1).
Platform:
Thanks 🙏
Previously MaybeUninit
was used to store a struct inside a Zend object. This can be replaced with an Option<T>
, using std::ptr::write()
to write the option into memory without dropping the uninitalized object.
If a Zval
cannot be parsed as a Zval
, we can go up the chain until we find one that works.
e.g.
string -> double -> long
When using the #[php_class]
macro on a struct
, I'm trying to also set a PHP class constant, but I don't see any support. Am I missing something here on how to register class constants (either via macros or procedurally)
When running the example from https://github.com/davidcole1340/ext-php-rs/blob/466c1658e3c91db3398c585a3b859b18e5b8e070/guide/src/macros/classes.md#example in returning an exception using PhpException::from_class::<RedisException>
, I'm seeing a panic:
thread '<unnamed>' panicked at 'Attempted to access uninitalized class object', /Users/joe/.cargo/git/checkouts/ext-php-rs-0adfd3c26f55092c/f9528f0/src/types/class_object.rs:245:14
stack backtrace:
0: rust_begin_unwind
at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35/library/std/src/panicking.rs:517:5
1: core::panicking::panic_fmt
at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35/library/core/src/panicking.rs:101:14
2: core::option::expect_failed
at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35/library/core/src/option.rs:1615:5
3: core::option::Option<T>::expect
at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35/library/core/src/option.rs:698:21
4: <ext_php_rs::types::class_object::ZendClassObject<T> as core::ops::deref::DerefMut>::deref_mut
at /Users/joe/.cargo/git/checkouts/ext-php-rs-0adfd3c26f55092c/f9528f0/src/types/class_object.rs:243:9
5: ext_php_rs::zend::handlers::<impl ext_php_rs::ffi::_zend_object_handlers>::write_property::internal
at /Users/joe/.cargo/git/checkouts/ext-php-rs-0adfd3c26f55092c/f9528f0/src/zend/handlers.rs:139:30
6: ext_php_rs::zend::handlers::<impl ext_php_rs::ffi::_zend_object_handlers>::write_property
at /Users/joe/.cargo/git/checkouts/ext-php-rs-0adfd3c26f55092c/f9528f0/src/zend/handlers.rs:153:15
7: _zend_update_property_ex
8: _zend_throw_exception_zstr
9: _zend_throw_exception
10: _zend_throw_exception_ex
11: ext_php_rs::exception::throw_with_code
at /Users/joe/.cargo/git/checkouts/ext-php-rs-0adfd3c26f55092c/f9528f0/src/exception.rs:138:9
12: ext_php_rs::exception::PhpException::throw
at /Users/joe/.cargo/git/checkouts/ext-php-rs-0adfd3c26f55092c/f9528f0/src/exception.rs:65:9
13: <core::result::Result<T,E> as ext_php_rs::convert::IntoZval>::set_zval
at /Users/joe/.cargo/git/checkouts/ext-php-rs-0adfd3c26f55092c/f9528f0/src/convert.rs:186:17
14: php_v8::V8Js::_internal_php_execute_string
at ./src/lib.rs:141:1
15: _ZEND_DO_FCALL_SPEC_RETVAL_USED_HANDLER
16: _execute_ex
17: _zend_execute
18: _zend_eval_stringl
19: _zend_eval_stringl_ex
20: _do_cli
21: _main
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.
fatal runtime error: failed to initiate panic, error 5
zsh: abort RUST_BACKTRACE=1 php -r
Zvals currently implement both Send
and Sync
, however, if the zval contains a reference counted type (strings, arrays, objects), when dropped it will attempt to decrement the reference counter, which is not atomic and therefore not defined behaviour.
Class entries will always be 'static
$callbackSharedData = [];
$callbacks = [];
$callbacks[] = function() {
echo "sleeping callback";
sleep(1);
};
$callbacks[] = function() use ($callbackSharedData) {
$callbackSharedData[] = 'item';
};
example_ext_pass_array_of_callbacks($callbacks);
#[php_function]
pub fn example_ext_pass_array_of_callables(data: HashMap<String, ZendCallable>) {
for (key, call) in data.iter() {
call.try_call(vec![]).expect("Failed to call function");
}
}
Fails with:
Compiling ext-php-rs-example v0.1.0 (/app)
error: implementation of `FromZval` is not general enough
--> src/lib.rs:24:1
|
24 | #[php_function]
| ^^^^^^^^^^^^^^^ implementation of `FromZval` is not general enough
|
= note: `FromZval<'0>` would have to be implemented for the type `ext_php_rs::types::ZendCallable<'_>`, for any lifetime `'0`...
= note: ...but `FromZval<'1>` is actually implemented for the type `ext_php_rs::types::ZendCallable<'1>`, for some specific lifetime `'1`
= note: this error originates in the attribute macro `php_function` (in Nightly builds, run with -Z macro-backtrace for more info)
error: implementation of `FromZval` is not general enough
--> src/lib.rs:58:1
|
58 | #[php_module]
| ^^^^^^^^^^^^^ implementation of `FromZval` is not general enough
|
= note: `FromZval<'0>` would have to be implemented for the type `ext_php_rs::types::ZendCallable<'_>`, for any lifetime `'0`...
= note: ...but `FromZval<'1>` is actually implemented for the type `ext_php_rs::types::ZendCallable<'1>`, for some specific lifetime `'1`
= note: this error originates in the attribute macro `php_module` (in Nightly builds, run with -Z macro-backtrace for more info)
error: could not compile `ext-php-rs-example` due to 2 previous errors
So I have tried lifetimes
like this:
#[php_function]
pub fn example_ext_pass_array_of_callables<'a>(data: HashMap<String, ZendCallable<'a>>) {
for (key, call) in data.iter() {
call.try_call(vec![]).expect("Failed to call function");
}
}
Fails with:
Compiling ext-php-rs-example v0.1.0 (/app)
error[E0261]: use of undeclared lifetime name `'a`
--> src/lib.rs:24:1
|
24 | #[php_function]
| ^^^^^^^^^^^^^^^- lifetime `'a` is missing in item created through this procedural macro
| |
| undeclared lifetime
|
= note: this error originates in the attribute macro `php_function` (in Nightly builds, run with -Z macro-backtrace for more info)
error[E0261]: use of undeclared lifetime name `'a`
--> src/lib.rs:58:1
|
58 | #[php_module]
| ^^^^^^^^^^^^^- lifetime `'a` is missing in item created through this procedural macro
| |
| undeclared lifetime
|
= note: this error originates in the attribute macro `php_module` (in Nightly builds, run with -Z macro-backtrace for more info)
For more information about this error, try `rustc --explain E0261`.
error: could not compile `ext-php-rs-example` due to 2 previous errors
Any ideas?
Is this even possible?
Thanks 🙏
Similar to #86, these functions rely on the user only calling them on types that have been allocated on the heap and immediately proceed a ZendObject
. This should be removed in favour of users using the ZendClassObject<Self>
APIs.
Windows is not currently supported, at the moment the only blocking factor that I know of is that the php-config
executable is not available on Windows, so we would need to find a way to retrieve the include path for bindgen.
The build ID is incorrect causing PHP to reject the extension.
Module paths are currently long and convoluted. Can easily be shortened (drop ::php
, re-export types from ::types
instead of exporting modules).
This is a great lib! Tripped through some details that was probably mostly related to my level of Rust ability honestly.
Anyways, I figured I'd share in case the example can be useful for folks: https://github.com/jphenow/tomlrs-php
It's incomplete because I didn't finish packaging it up for composer or anything but still might give folks some ideas as they get started.
Feel free to just close if this isn't useful at all - mostly wanted to say thanks for this library!
Should be able to accept a reference inside a zval: https://www.phpinternalsbook.com/php7/zvals/references.html
Questions to be answered:
Hi!
I'd like to pass binary data between PHP and the Rust crate. As far as I understand both on PHP side this is just a string while on rust side they'd be slices of u8 (or vectors?).
Is something like that possible or planned?
Thanks again for this great library!
Assertion failed: ((zend_gc_refcount(&(ht)->gc) == 1) || ((ht)->u.flags & (1<<6))), function _zend_hash_str_add_or_update_i, file /Users/davidcole/.php/build/php-8.0.9/Zend/zend_hash.c, line 819.
fish: Job 1, 'php -dextension=(pwd)/../../t...' terminated by signal SIGABRT (Abort)
I did all steps from README example section, but when I run php -dextension=./target/release/libphp_lib.dylib test.php
I get the error:
% php -dextension=./target/release/libphp_lib.dylib test.php
PHP Warning: PHP Startup: Invalid library (maybe not a PHP library) './target/release/libphp_lib.dylib' in Unknown on line 0
Warning: PHP Startup: Invalid library (maybe not a PHP library) './target/release/libphp_lib.dylib' in Unknown on line 0
PHP Fatal error: Uncaught Error: Call to undefined function hello_world() in /Users/.../Documents/Code/Rust/php_lib/test.php:2
Stack trace:
#0 {main}
thrown in /Users/.../Documents/Code/Rust/php_lib/test.php on line 2
Fatal error: Uncaught Error: Call to undefined function hello_world() in /Users/.../Documents/Code/Rust/php_lib/test.php:2
Stack trace:
#0 {main}
thrown in /Users/.../Documents/Code/Rust/php_lib/test.php on line 2
Cargo.toml
[package]
name = "php_lib"
version = "0.1.0"
edition = "2021"
[dependencies]
ext-php-rs="0.7.3"
[lib]
crate-type = ["cdylib"]
MacOS 12.1
PHP 8.0
Rust 1.56.0
cargo build and cargo build --release works fine.
Any ideas?
Thanks in advance.
Classes that have an associated object registered can currently be serialized and unserialized. Since PHP can't serialize the associated struct this should be disabled. Currently, PHP creates a new object without calling the constructor, attempts to access the properties which fails due to an uninitialized object.
Currently, the ArgParser
and Arg
types are defined as so:
pub struct ArgParser<'a, 'arg, 'zval> {
args: Vec<&'arg mut Arg<'zval>>,
min_num_args: Option<u32>,
execute_data: &'a ExecutionData,
}
pub struct Arg<'a> {
name: String,
_type: DataType,
as_ref: bool,
allow_null: bool,
variadic: bool,
default_value: Option<String>,
zval: Option<&'a Zval>,
}
'zval
should be replaced with 'a
, as the zvals inside Arg
are supplied from &'a ExecutionData
. The compiler doesn't know this because pointers are converted into references.
Some methods take an immutable reference to self
and then return a mutable reference to a self
derivative. This should be changed to take a mutable reference to self
, while adding the corresponding immutable reference function.
This is currently blocked on the argument parser. Consider this example:
extern "C" fn example(ex: &mut ExecutionData, ret: &mut Zval) {
let mut a = Arg::new("a", DataType::Long);
let mut b = Arg::new("b", DataType::String);
let parser = ArgParser::new(ex).arg(&mut a).arg(&mut b).parse();
^^ immutable borrow happens here
if parser.is_err() {
return;
}
let this: &mut ZendClassObject<Example> = ex.get_object();
^^ mutable borrow happens here
}
ArgParser
takes an &ExecutionData
, and when parsed stores a reference to each arguments zval inside each argument, which has the same lifetime as ex
. When we call ex.get_object()
, we need to borrow mutably to mutate the underlying object, but &ExecutionData
is still borrowed through the two arguments a
and b
.
Functions which exhibit this behaviour:
ExecutionData::get_object()
ExecutionData::get_self()
At the moment you can extract &T
from Zval
and ZendObject
when T: RegisteredClass
, however you can't do the same for &mut T
.
This will require two new traits: FromZvalMut
and FromZendObjectMut
. FromZvalMut
can be generically implemented for any type that already implements FromZval
, and the same goes for FromZendObjectMut
.
This will also require the Zval
stored in Arg
to be mutable.
It be super cool if we could extend the derive macros to be able to also store metadata information about the compiled module and automatically generate stub files. For example:
#[php_class(name = "Vodik\\Example")]
#[derive(Debug, Clone)]
struct Example(...);
#[php_impl]
impl Example {
pub fn __construct(arg: u32) -> Self {
// ...
}
/// Example documentation
pub fn get_value(&self) -> u32 {
// ...
}
could result in automatically emitting a PHP file like this:
namespace Vodik\Example
{
class Example
{
public function __construct(int $arg) {}
/**
* Example documentation
*/
public function getValue(): int {}
}
}
Don't know how hard it would be to implement, but it would be nice if this project could auto-generate stubs for the extension on build to be used for auto-completion in an IDE.
The ClassRef
type is used to return a reference to self
in class methods. It is inherently unsafe because it relies on the user returning self
where self
has been allocated on the heap, and it must be followed by a ZendObject
.
This should be removed in favour of functions taking a ZendClassObject<Self>
when they want to return self
.
Use latest Rust version - too many combinations otherwise. Target PHP versions >= 7.4
.
Add rustfmt.toml
to format project.
wrap_comments
where T: AsRef<str>
simply replace T
with impl AsRef<str>
for strings, keep as before for normal complex cases.Rather than using unwrap
, return Result
or Option
. More of a hassle to use but safer overall
At the moment, calling zval.string()
will attempt to convert a f64
into a string, so zval.string() != zval.str()
all the time. This should be removed, and a new function added to FromZval
, coerce_from_zval()
:
pub trait FromZval<'a> {
fn from_zval(zval: &'a Zval) -> Option<Self>;
// by default, most types won't need coercing
fn coerce_from_zval(zval: &'a Zval) -> Option<Self> {
Self::from_zval(zval)
}
}
Attempting to access properties which have been added to classes causes a segfault:
php: /home/david/.phpbrew/build/8.0.3-debug/Zend/zend_operators.c:923: __zval_get_string_func: Assertion `0' failed.
fish: “php -dextension=(pwd)/target/de…” terminated by signal SIGABRT (Abort)
Seems like our documentation isn't being published on docs.rs because PHP is required to compile the crate :'( - could potentially look at moving the bindings into a seperate ext-php-rs-sys
crate so the main crate's docs can compile.
Reduce the need to allocate the hashmap and closures on each call.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.