mirror of
https://github.com/danog/ext-php-rs.git
synced 2024-12-12 09:09:46 +01:00
Merge remote-tracking branch 'origin/master' into process-globals
This commit is contained in:
commit
21134fb618
15
.github/actions/embed/Dockerfile
vendored
Normal file
15
.github/actions/embed/Dockerfile
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
FROM php:8.2-bullseye
|
||||
|
||||
WORKDIR /tmp
|
||||
|
||||
RUN apt update -y && apt upgrade -y
|
||||
RUN apt install lsb-release wget gnupg software-properties-common -y
|
||||
RUN bash -c "$(wget -O - https://apt.llvm.org/llvm.sh)"
|
||||
|
||||
ENV RUSTUP_HOME=/rust
|
||||
ENV CARGO_HOME=/cargo
|
||||
ENV PATH=/cargo/bin:/rust/bin:$PATH
|
||||
|
||||
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --no-modify-path
|
||||
|
||||
ENTRYPOINT [ "/cargo/bin/cargo", "test", "--lib", "--release", "--all-features" ]
|
5
.github/actions/embed/action.yml
vendored
Normal file
5
.github/actions/embed/action.yml
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
name: 'PHP Embed and Rust'
|
||||
description: 'Builds the crate after installing the latest PHP with php embed and stable Rust.'
|
||||
runs:
|
||||
using: 'docker'
|
||||
image: 'Dockerfile'
|
22
.github/workflows/build.yml
vendored
22
.github/workflows/build.yml
vendored
@ -15,9 +15,9 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||
php: ["8.0", "8.1", "8.2"]
|
||||
php: ["8.0", "8.1", "8.2", "8.3"]
|
||||
rust: [stable, nightly]
|
||||
clang: ["14"]
|
||||
clang: ["15", "17"]
|
||||
phpts: [ts, nts]
|
||||
exclude:
|
||||
# ext-php-rs requires nightly Rust when on Windows.
|
||||
@ -28,6 +28,12 @@ jobs:
|
||||
phpts: ts
|
||||
- os: ubuntu-latest
|
||||
phpts: ts
|
||||
- os: macos-latest
|
||||
clang: "17"
|
||||
- os: ubuntu-latest
|
||||
clang: "15"
|
||||
- os: windows-latest
|
||||
clang: "15"
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
steps:
|
||||
@ -83,10 +89,10 @@ jobs:
|
||||
- name: Build
|
||||
env:
|
||||
EXT_PHP_RS_TEST: ""
|
||||
run: cargo build --release --all-features --all
|
||||
run: cargo build --release --features closure,anyhow --all
|
||||
# Test & lint
|
||||
- name: Test inline examples
|
||||
run: cargo test --release --all --all-features
|
||||
run: cargo test --release --all --features closure,anyhow
|
||||
- name: Run rustfmt
|
||||
if: matrix.rust == 'stable' && matrix.os == 'ubuntu-latest' && matrix.php == '8.2'
|
||||
run: cargo fmt --all -- --check
|
||||
@ -110,3 +116,11 @@ jobs:
|
||||
uses: actions/checkout@v3
|
||||
- name: Build
|
||||
uses: ./.github/actions/zts
|
||||
test-embed:
|
||||
name: Test with embed
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
- name: Test
|
||||
uses: ./.github/actions/embed
|
||||
|
2
.github/workflows/docs.yml
vendored
2
.github/workflows/docs.yml
vendored
@ -16,7 +16,7 @@ jobs:
|
||||
matrix:
|
||||
os: ["ubuntu-latest"]
|
||||
php: ["8.2"]
|
||||
clang: ["14"]
|
||||
clang: ["17"]
|
||||
mdbook: ["latest"]
|
||||
steps:
|
||||
- name: Checkout code
|
||||
|
17
Cargo.toml
17
Cargo.toml
@ -5,7 +5,7 @@ repository = "https://github.com/davidcole1340/ext-php-rs"
|
||||
homepage = "https://github.com/davidcole1340/ext-php-rs"
|
||||
license = "MIT OR Apache-2.0"
|
||||
keywords = ["php", "ffi", "zend"]
|
||||
version = "0.10.1"
|
||||
version = "0.10.5"
|
||||
authors = ["David Cole <david.cole1340@gmail.com>"]
|
||||
edition = "2018"
|
||||
categories = ["api-bindings"]
|
||||
@ -17,30 +17,31 @@ parking_lot = "0.12"
|
||||
cfg-if = "1.0"
|
||||
once_cell = "1.17"
|
||||
anyhow = { version = "1", optional = true }
|
||||
ext-php-rs-derive = { version = "=0.10.0", path = "./crates/macros" }
|
||||
ext-php-rs-derive = { version = "=0.10.1", path = "./crates/macros" }
|
||||
|
||||
[dev-dependencies]
|
||||
skeptic = "0.13"
|
||||
|
||||
[build-dependencies]
|
||||
anyhow = "1"
|
||||
bindgen = "0.65.1"
|
||||
bindgen = "0.68.1"
|
||||
cc = "1.0"
|
||||
skeptic = "0.13"
|
||||
|
||||
[target.'cfg(windows)'.build-dependencies]
|
||||
ureq = { version = "2.4", features = ["native-tls", "gzip"], default-features = false }
|
||||
ureq = { version = "2.4", features = [
|
||||
"native-tls",
|
||||
"gzip",
|
||||
], default-features = false }
|
||||
native-tls = "0.2"
|
||||
zip = "0.6"
|
||||
|
||||
[features]
|
||||
closure = []
|
||||
embed = []
|
||||
|
||||
[workspace]
|
||||
members = [
|
||||
"crates/macros",
|
||||
"crates/cli"
|
||||
]
|
||||
members = ["crates/macros", "crates/cli"]
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
rustdoc-args = ["--cfg", "docs"]
|
||||
|
@ -26,6 +26,8 @@ bind! {
|
||||
_efree,
|
||||
_emalloc,
|
||||
_zend_executor_globals,
|
||||
_sapi_globals_struct,
|
||||
_sapi_module_struct,
|
||||
_zend_expected_type,
|
||||
_zend_expected_type_Z_EXPECTED_ARRAY,
|
||||
_zend_expected_type_Z_EXPECTED_BOOL,
|
||||
@ -55,6 +57,8 @@ bind! {
|
||||
zend_array_destroy,
|
||||
zend_array_dup,
|
||||
zend_call_known_function,
|
||||
zend_fetch_function_str,
|
||||
zend_hash_str_find_ptr_lc,
|
||||
zend_ce_argument_count_error,
|
||||
zend_ce_arithmetic_error,
|
||||
zend_ce_compile_error,
|
||||
@ -99,6 +103,8 @@ bind! {
|
||||
zend_objects_clone_members,
|
||||
zend_register_bool_constant,
|
||||
zend_register_double_constant,
|
||||
zend_register_ini_entries,
|
||||
zend_ini_entry_def,
|
||||
zend_register_internal_class_ex,
|
||||
zend_register_long_constant,
|
||||
zend_register_string_constant,
|
||||
@ -106,6 +112,7 @@ bind! {
|
||||
zend_string,
|
||||
zend_string_init_interned,
|
||||
zend_throw_exception_ex,
|
||||
zend_throw_exception_object,
|
||||
zend_type,
|
||||
zend_value,
|
||||
zend_wrong_parameters_count_error,
|
||||
@ -137,6 +144,7 @@ bind! {
|
||||
IS_CONSTANT_AST_EX,
|
||||
IS_DOUBLE,
|
||||
IS_FALSE,
|
||||
IS_INDIRECT,
|
||||
IS_INTERNED_STRING_EX,
|
||||
IS_LONG,
|
||||
IS_MIXED,
|
||||
@ -157,6 +165,10 @@ bind! {
|
||||
IS_PTR,
|
||||
MAY_BE_ANY,
|
||||
MAY_BE_BOOL,
|
||||
PHP_INI_USER,
|
||||
PHP_INI_PERDIR,
|
||||
PHP_INI_SYSTEM,
|
||||
PHP_INI_ALL,
|
||||
USING_ZTS,
|
||||
ZEND_ACC_ABSTRACT,
|
||||
ZEND_ACC_ANON_CLASS,
|
||||
@ -233,6 +245,8 @@ bind! {
|
||||
zend_class_serialize_deny,
|
||||
zend_class_unserialize_deny,
|
||||
zend_executor_globals,
|
||||
sapi_globals_struct,
|
||||
sapi_module_struct,
|
||||
zend_objects_store_del,
|
||||
zend_hash_move_forward_ex,
|
||||
zend_hash_get_current_key_type_ex,
|
||||
@ -247,6 +261,8 @@ bind! {
|
||||
core_globals,
|
||||
sapi_globals_struct,
|
||||
sapi_globals,
|
||||
sapi_globals,
|
||||
sapi_module,
|
||||
php_printf,
|
||||
__zend_malloc,
|
||||
tsrm_get_ls_cache,
|
||||
@ -268,6 +284,13 @@ bind! {
|
||||
zend_is_auto_global,
|
||||
zend_llist_get_next_ex,
|
||||
zend_llist_get_prev_ex,
|
||||
sapi_globals_offset,
|
||||
zend_atomic_bool_store,
|
||||
zend_interrupt_function
|
||||
zend_interrupt_function,
|
||||
zend_eval_string,
|
||||
zend_file_handle,
|
||||
zend_stream_init_filename,
|
||||
php_execute_script,
|
||||
zend_register_module_ex,
|
||||
_zend_bailout
|
||||
}
|
||||
|
38
build.rs
38
build.rs
@ -16,7 +16,7 @@ use bindgen::RustTarget;
|
||||
use impl_::Provider;
|
||||
|
||||
const MIN_PHP_API_VER: u32 = 20200930;
|
||||
const MAX_PHP_API_VER: u32 = 20220829;
|
||||
const MAX_PHP_API_VER: u32 = 20230831;
|
||||
|
||||
pub trait PHPProvider<'a>: Sized {
|
||||
/// Create a new PHP provider.
|
||||
@ -151,9 +151,31 @@ fn build_wrapper(defines: &[(&str, &str)], includes: &[PathBuf]) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "embed")]
|
||||
/// Builds the embed library.
|
||||
fn build_embed(defines: &[(&str, &str)], includes: &[PathBuf]) -> Result<()> {
|
||||
let mut build = cc::Build::new();
|
||||
for (var, val) in defines {
|
||||
build.define(var, *val);
|
||||
}
|
||||
build
|
||||
.file("src/embed/embed.c")
|
||||
.includes(includes)
|
||||
.try_compile("embed")
|
||||
.context("Failed to compile ext-php-rs C embed interface")?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Generates bindings to the Zend API.
|
||||
fn generate_bindings(defines: &[(&str, &str)], includes: &[PathBuf]) -> Result<String> {
|
||||
let mut bindgen = bindgen::Builder::default()
|
||||
let mut bindgen = bindgen::Builder::default();
|
||||
|
||||
#[cfg(feature = "embed")]
|
||||
{
|
||||
bindgen = bindgen.header("src/embed/embed.h");
|
||||
}
|
||||
|
||||
bindgen = bindgen
|
||||
.header("src/wrapper.h")
|
||||
.clang_args(
|
||||
includes
|
||||
@ -206,6 +228,8 @@ fn check_php_version(info: &PHPInfo) -> Result<()> {
|
||||
|
||||
const PHP_82_API_VER: u32 = 20220829;
|
||||
|
||||
const PHP_83_API_VER: u32 = 20230831;
|
||||
|
||||
println!("cargo:rustc-cfg=php80");
|
||||
|
||||
if (PHP_81_API_VER..PHP_82_API_VER).contains(&version) {
|
||||
@ -216,6 +240,10 @@ fn check_php_version(info: &PHPInfo) -> Result<()> {
|
||||
println!("cargo:rustc-cfg=php82");
|
||||
}
|
||||
|
||||
if version >= PHP_83_API_VER {
|
||||
println!("cargo:rustc-cfg=php83");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -226,6 +254,8 @@ fn main() -> Result<()> {
|
||||
for path in [
|
||||
manifest.join("src").join("wrapper.h"),
|
||||
manifest.join("src").join("wrapper.c"),
|
||||
manifest.join("src").join("embed").join("embed.h"),
|
||||
manifest.join("src").join("embed").join("embed.c"),
|
||||
manifest.join("allowed_bindings.rs"),
|
||||
manifest.join("windows_build.rs"),
|
||||
manifest.join("unix_build.rs"),
|
||||
@ -257,6 +287,10 @@ fn main() -> Result<()> {
|
||||
|
||||
check_php_version(&info)?;
|
||||
build_wrapper(&defines, &includes)?;
|
||||
|
||||
#[cfg(feature = "embed")]
|
||||
build_embed(&defines, &includes)?;
|
||||
|
||||
let bindings = generate_bindings(&defines, &includes)?;
|
||||
|
||||
let out_file =
|
||||
|
@ -5,7 +5,7 @@ repository = "https://github.com/davidcole1340/ext-php-rs"
|
||||
homepage = "https://github.com/davidcole1340/ext-php-rs"
|
||||
license = "MIT OR Apache-2.0"
|
||||
keywords = ["php", "ffi", "zend"]
|
||||
version = "0.1.8"
|
||||
version = "0.1.9"
|
||||
authors = ["David Cole <david.cole1340@gmail.com>"]
|
||||
edition = "2018"
|
||||
categories = ["api-bindings", "command-line-interface"]
|
||||
|
@ -4,6 +4,7 @@ use anyhow::{Context, Result};
|
||||
use ext_php_rs::describe::Description;
|
||||
use libloading::os::unix::{Library, Symbol};
|
||||
|
||||
#[allow(improper_ctypes_definitions)]
|
||||
pub struct Ext {
|
||||
// These need to be here to keep the libraries alive. The extension library needs to be alive
|
||||
// to access the describe function. Missing here is the lifetime on `Symbol<'a, fn() ->
|
||||
|
@ -4,7 +4,7 @@ description = "Derive macros for ext-php-rs."
|
||||
repository = "https://github.com/davidcole1340/ext-php-rs"
|
||||
homepage = "https://github.com/davidcole1340/ext-php-rs"
|
||||
license = "MIT OR Apache-2.0"
|
||||
version = "0.10.0"
|
||||
version = "0.10.1"
|
||||
authors = ["David Cole <david.cole1340@gmail.com>"]
|
||||
edition = "2018"
|
||||
|
||||
|
@ -26,6 +26,7 @@ pub struct Arg {
|
||||
pub ty: String,
|
||||
pub nullable: bool,
|
||||
pub default: Option<String>,
|
||||
pub as_ref: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@ -249,12 +250,19 @@ pub fn get_return_type(output_type: &ReturnType) -> Result<Option<(String, bool)
|
||||
}
|
||||
|
||||
impl Arg {
|
||||
pub fn new(name: String, ty: String, nullable: bool, default: Option<String>) -> Self {
|
||||
pub fn new(
|
||||
name: String,
|
||||
ty: String,
|
||||
nullable: bool,
|
||||
default: Option<String>,
|
||||
as_ref: bool,
|
||||
) -> Self {
|
||||
Self {
|
||||
name,
|
||||
ty,
|
||||
nullable,
|
||||
default,
|
||||
as_ref,
|
||||
}
|
||||
}
|
||||
|
||||
@ -268,6 +276,7 @@ impl Arg {
|
||||
match ty {
|
||||
Type::Path(TypePath { path, .. }) => {
|
||||
let mut path = path.clone();
|
||||
let mut pass_by_ref = false;
|
||||
path.drop_lifetimes();
|
||||
|
||||
let seg = path.segments.last()?;
|
||||
@ -283,9 +292,45 @@ impl Arg {
|
||||
None
|
||||
}
|
||||
});
|
||||
|
||||
// For for types that are `Option<&mut T>` to turn them into `Option<&T>`,
|
||||
// marking the Arg as as "passed by reference".
|
||||
let option = Some(seg)
|
||||
.filter(|seg| seg.ident == "Option")
|
||||
.and_then(|seg| {
|
||||
if let PathArguments::AngleBracketed(args) = &seg.arguments {
|
||||
args.args
|
||||
.iter()
|
||||
.find(|arg| matches!(arg, GenericArgument::Type(_)))
|
||||
.map(|ga| {
|
||||
let new_ga = match ga {
|
||||
GenericArgument::Type(ty) => {
|
||||
let _rtype = match ty {
|
||||
Type::Reference(r) => {
|
||||
let mut new_ref = r.clone();
|
||||
new_ref.mutability = None;
|
||||
pass_by_ref = true;
|
||||
Type::Reference(new_ref)
|
||||
}
|
||||
_ => ty.clone(),
|
||||
};
|
||||
GenericArgument::Type(_rtype)
|
||||
}
|
||||
_ => ga.clone(),
|
||||
};
|
||||
new_ga.to_token_stream().to_string()
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
|
||||
let stringified = match result {
|
||||
Some(result) if is_return => result,
|
||||
_ => path.to_token_stream().to_string(),
|
||||
_ => match option {
|
||||
Some(result) => result,
|
||||
None => path.to_token_stream().to_string(),
|
||||
},
|
||||
};
|
||||
|
||||
Some(Arg::new(
|
||||
@ -293,6 +338,7 @@ impl Arg {
|
||||
stringified,
|
||||
seg.ident == "Option" || default.is_some(),
|
||||
default,
|
||||
pass_by_ref,
|
||||
))
|
||||
}
|
||||
Type::Reference(ref_) => {
|
||||
@ -302,6 +348,7 @@ impl Arg {
|
||||
ref_.to_token_stream().to_string(),
|
||||
false,
|
||||
default,
|
||||
ref_.mutability.is_some(),
|
||||
))
|
||||
}
|
||||
_ => None,
|
||||
@ -361,6 +408,7 @@ impl Arg {
|
||||
let ty = self.get_type_ident();
|
||||
|
||||
let null = self.nullable.then(|| quote! { .allow_null() });
|
||||
let passed_by_ref = self.as_ref.then(|| quote! { .as_ref() });
|
||||
let default = self.default.as_ref().map(|val| {
|
||||
quote! {
|
||||
.default(#val)
|
||||
@ -368,7 +416,7 @@ impl Arg {
|
||||
});
|
||||
|
||||
quote! {
|
||||
::ext_php_rs::args::Arg::new(#name, #ty) #null #default
|
||||
::ext_php_rs::args::Arg::new(#name, #ty) #null #passed_by_ref #default
|
||||
}
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -23,12 +23,16 @@
|
||||
- [Object](./types/object.md)
|
||||
- [Class Object](./types/class_object.md)
|
||||
- [Closure](./types/closure.md)
|
||||
- [Functions & methods](./types/functions.md)
|
||||
- [Async futures](./macros/async_impl.md)
|
||||
- [Macros](./macros/index.md)
|
||||
- [Module](./macros/module.md)
|
||||
- [Module Startup Function](./macros/module_startup.md)
|
||||
- [Function](./macros/function.md)
|
||||
- [Classes](./macros/classes.md)
|
||||
- [`impl`s](./macros/impl.md)
|
||||
- [async `impl`s](./macros/async_impl.md)
|
||||
- [Constants](./macros/constant.md)
|
||||
- [`ZvalConvert`](./macros/zval_convert.md)
|
||||
- [Exceptions](./exceptions.md)
|
||||
- [INI Settings](./ini-settings.md)
|
||||
|
48
guide/src/ini-settings.md
Normal file
48
guide/src/ini-settings.md
Normal file
@ -0,0 +1,48 @@
|
||||
# INI Settings
|
||||
|
||||
Your PHP Extension may want to provide it's own PHP INI settings to configure behaviour. This can be done in the `#[php_startup]` annotated startup function.
|
||||
|
||||
## Registering INI Settings
|
||||
|
||||
All PHP INI definitions must be registered with PHP to get / set their values via the `php.ini` file or `ini_get() / ini_set()`.
|
||||
|
||||
|
||||
```rust,no_run
|
||||
# #![cfg_attr(windows, feature(abi_vectorcall))]
|
||||
# extern crate ext_php_rs;
|
||||
# use ext_php_rs::prelude::*;
|
||||
# use ext_php_rs::zend::IniEntryDef;
|
||||
# use ext_php_rs::flags::IniEntryPermission;
|
||||
|
||||
#[php_startup]
|
||||
pub fn startup_function(ty: i32, module_number: i32) {
|
||||
let ini_entries: Vec<IniEntryDef> = vec![
|
||||
IniEntryDef::new(
|
||||
"my_extension.display_emoji".to_owned(),
|
||||
"yes".to_owned(),
|
||||
IniEntryPermission::All,
|
||||
),
|
||||
];
|
||||
IniEntryDef::register(ini_entries, module_number);
|
||||
}
|
||||
# fn main() {}
|
||||
```
|
||||
|
||||
## Getting INI Settings
|
||||
|
||||
The INI values are stored as part of the `GlobalExecutor`, and can be accessed via the `ini_values()` function. To retrieve the value for a registered INI setting
|
||||
|
||||
```rust,no_run
|
||||
# #![cfg_attr(windows, feature(abi_vectorcall))]
|
||||
# extern crate ext_php_rs;
|
||||
# use ext_php_rs::prelude::*;
|
||||
# use ext_php_rs::zend::ExecutorGlobals;
|
||||
|
||||
#[php_startup]
|
||||
pub fn startup_function(ty: i32, module_number: i32) {
|
||||
// Get all INI values
|
||||
let ini_values = ExecutorGlobals::get().ini_values(); // HashMap<String, Option<String>>
|
||||
let my_ini_value = ini_values.get("my_extension.display_emoji"); // Option<Option<String>>
|
||||
}
|
||||
# fn main() {}
|
||||
```
|
130
guide/src/macros/async_impl.md
Normal file
130
guide/src/macros/async_impl.md
Normal file
@ -0,0 +1,130 @@
|
||||
# `#[php_async_impl]`
|
||||
|
||||
Using `#[php_async_impl]` instead of `#[php_impl]` allows us to expose any async Rust library to PHP, using [PHP fibers](https://www.php.net/manual/en/language.fibers.php), [php-tokio](https://github.com/danog/php-tokio) and the [PHP Revolt event loop](https://revolt.run) under the hood to handle async interoperability.
|
||||
|
||||
This allows full compatibility with [amphp](https://amphp.org), [PSL](https://github.com/azjezz/psl), [reactphp](https://reactphp.org) and any other async PHP library based on [Revolt](https://revolt.run).
|
||||
|
||||
Traits annotated with `#[php_async_impl]` can freely expose any async function, using `await` and any async Rust library.
|
||||
|
||||
Make sure to also expose the `php_tokio::EventLoop::init` and `php_tokio::EventLoop::wakeup` functions to PHP in order to initialize the event loop, as specified in the full example [here »](#async-example).
|
||||
|
||||
Also, make sure to invoke `EventLoop::shutdown` in the request shutdown handler to clean up the tokio event loop before finishing the request.
|
||||
|
||||
## Async example
|
||||
|
||||
In this example, we're exposing an async Rust HTTP client library called [reqwest](https://docs.rs/reqwest/latest/reqwest/) to PHP, using [PHP fibers](https://www.php.net/manual/en/language.fibers.php), [php-tokio](https://github.com/danog/php-tokio) and the [PHP Revolt event loop](https://revolt.run) under the hood to handle async interoperability.
|
||||
|
||||
This allows full compatibility with [amphp](https://amphp.org), [PSL](https://github.com/azjezz/psl), [reactphp](https://reactphp.org) and any other async PHP library based on [Revolt](https://revolt.run).
|
||||
|
||||
Make sure to require [php-tokio](https://github.com/danog/php-tokio) as a dependency before proceeding.
|
||||
|
||||
```rust,ignore
|
||||
use ext_php_rs::prelude::*;
|
||||
use php_tokio::{php_async_impl, EventLoop};
|
||||
|
||||
#[php_class]
|
||||
struct Client {}
|
||||
|
||||
#[php_async_impl]
|
||||
impl Client {
|
||||
pub fn init() -> PhpResult<u64> {
|
||||
EventLoop::init()
|
||||
}
|
||||
pub fn wakeup() -> PhpResult<()> {
|
||||
EventLoop::wakeup()
|
||||
}
|
||||
pub async fn get(url: &str) -> anyhow::Result<String> {
|
||||
Ok(reqwest::get(url).await?.text().await?)
|
||||
}
|
||||
}
|
||||
|
||||
pub extern "C" fn request_shutdown(_type: i32, _module_number: i32) -> i32 {
|
||||
EventLoop::shutdown();
|
||||
0
|
||||
}
|
||||
|
||||
#[php_module]
|
||||
pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
|
||||
module.request_shutdown_function(request_shutdown)
|
||||
}
|
||||
```
|
||||
|
||||
Here's the async PHP code we use to interact with the Rust class we just exposed.
|
||||
|
||||
The `Client::init` method needs to be called only once in order to initialize the Revolt event loop and link it to the Tokio event loop, as shown by the following code.
|
||||
|
||||
See [here »](https://amphp.org) for more info on async PHP using [amphp](https://amphp.org) + [revolt](https://revolt.run).
|
||||
|
||||
```php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Reqwest;
|
||||
|
||||
use Revolt\EventLoop;
|
||||
|
||||
use function Amp\async;
|
||||
use function Amp\Future\await;
|
||||
|
||||
final class Client
|
||||
{
|
||||
private static ?string $id = null;
|
||||
|
||||
public static function init(): void
|
||||
{
|
||||
if (self::$id !== null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$f = \fopen("php://fd/".\Client::init(), 'r+');
|
||||
\stream_set_blocking($f, false);
|
||||
self::$id = EventLoop::onReadable($f, fn () => \Client::wakeup());
|
||||
}
|
||||
|
||||
public static function reference(): void
|
||||
{
|
||||
EventLoop::reference(self::$id);
|
||||
}
|
||||
public static function unreference(): void
|
||||
{
|
||||
EventLoop::unreference(self::$id);
|
||||
}
|
||||
|
||||
public static function __callStatic(string $name, array $args): mixed
|
||||
{
|
||||
return \Client::$name(...$args);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Client::init();
|
||||
|
||||
function test(int $delay): void
|
||||
{
|
||||
$url = "https://httpbin.org/delay/$delay";
|
||||
$t = time();
|
||||
echo "Making async reqwest to $url that will return after $delay seconds...".PHP_EOL;
|
||||
Client::get($url);
|
||||
$t = time() - $t;
|
||||
echo "Got response from $url after ~".$t." seconds!".PHP_EOL;
|
||||
};
|
||||
|
||||
$futures = [];
|
||||
$futures []= async(test(...), 5);
|
||||
$futures []= async(test(...), 5);
|
||||
$futures []= async(test(...), 5);
|
||||
|
||||
await($futures);
|
||||
```
|
||||
|
||||
Result:
|
||||
|
||||
```
|
||||
Making async reqwest to https://httpbin.org/delay/5 that will return after 5 seconds...
|
||||
Making async reqwest to https://httpbin.org/delay/5 that will return after 5 seconds...
|
||||
Making async reqwest to https://httpbin.org/delay/5 that will return after 5 seconds...
|
||||
Got response from https://httpbin.org/delay/5 after ~5 seconds!
|
||||
Got response from https://httpbin.org/delay/5 after ~5 seconds!
|
||||
Got response from https://httpbin.org/delay/5 after ~5 seconds!
|
||||
```
|
||||
|
||||
[`php_function`]: ./function.md
|
@ -8,6 +8,8 @@ implementations cannot be exported to PHP.
|
||||
If you do not want a function exported to PHP, you should place it in a separate
|
||||
`impl` block.
|
||||
|
||||
If you want to use async Rust, use `#[php_async_impl]`, instead: see [here »](./async_impl.md) for more info.
|
||||
|
||||
## Methods
|
||||
|
||||
Methods basically follow the same rules as functions, so read about the
|
||||
@ -162,4 +164,4 @@ var_dump(Human::get_max_age()); // int(100)
|
||||
var_dump(Human::MAX_AGE); // int(100)
|
||||
```
|
||||
|
||||
[`php_function`]: ./function.md
|
||||
[`php_async_impl`]: ./async_impl.md
|
||||
|
@ -45,3 +45,17 @@ pub fn test_bool(input: bool) -> String {
|
||||
var_dump(test_bool(true)); // string(4) "Yes!"
|
||||
var_dump(test_bool(false)); // string(3) "No!"
|
||||
```
|
||||
|
||||
## Rust example, taking by reference
|
||||
|
||||
```rust,no_run
|
||||
# #![cfg_attr(windows, feature(abi_vectorcall))]
|
||||
# extern crate ext_php_rs;
|
||||
# use ext_php_rs::prelude::*;
|
||||
# use ext_php_rs::types;
|
||||
#[php_function]
|
||||
pub fn test_bool(input: &mut types::Zval) {
|
||||
input.reference_mut().unwrap().set_bool(false);
|
||||
}
|
||||
# fn main() {}
|
||||
```
|
||||
|
28
guide/src/types/functions.md
Normal file
28
guide/src/types/functions.md
Normal file
@ -0,0 +1,28 @@
|
||||
# Functions & methods
|
||||
|
||||
PHP functions and methods are represented by the `Function` struct.
|
||||
|
||||
You can use the `try_from_function` and `try_from_method` methods to obtain a Function struct corresponding to the passed function or static method name.
|
||||
It's heavily recommended you reuse returned `Function` objects, to avoid the overhead of looking up the function/method name.
|
||||
|
||||
```rust,no_run
|
||||
# #![cfg_attr(windows, feature(abi_vectorcall))]
|
||||
# extern crate ext_php_rs;
|
||||
use ext_php_rs::prelude::*;
|
||||
|
||||
use ext_php_rs::zend::Function;
|
||||
|
||||
#[php_function]
|
||||
pub fn test_function() -> () {
|
||||
let var_dump = Function::try_from_function("var_dump").unwrap();
|
||||
let _ = var_dump.try_call(vec![&"abc"]);
|
||||
}
|
||||
|
||||
#[php_function]
|
||||
pub fn test_method() -> () {
|
||||
let f = Function::try_from_method("ClassName", "staticMethod").unwrap();
|
||||
let _ = f.try_call(vec![&"abc"]);
|
||||
}
|
||||
|
||||
# fn main() {}
|
||||
```
|
@ -15,6 +15,25 @@ object.
|
||||
|
||||
## Examples
|
||||
|
||||
### Calling a method
|
||||
|
||||
```rust,no_run
|
||||
# #![cfg_attr(windows, feature(abi_vectorcall))]
|
||||
# extern crate ext_php_rs;
|
||||
use ext_php_rs::{prelude::*, types::ZendObject};
|
||||
|
||||
// Take an object reference and also return it.
|
||||
#[php_function]
|
||||
pub fn take_obj(obj: &mut ZendObject) -> () {
|
||||
let _ = obj.try_call_method("hello", vec![&"arg1", &"arg2"]);
|
||||
}
|
||||
# #[php_module]
|
||||
# pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
|
||||
# module
|
||||
# }
|
||||
# fn main() {}
|
||||
```
|
||||
|
||||
### Taking an object reference
|
||||
|
||||
```rust,no_run
|
||||
|
@ -123,6 +123,7 @@ impl<'a> Arg<'a> {
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `params` - A list of parameters to call the function with.
|
||||
#[inline(always)]
|
||||
pub fn try_call(&self, params: Vec<&dyn IntoZvalDyn>) -> Result<Zval> {
|
||||
self.zval.as_ref().ok_or(Error::Callable)?.try_call(params)
|
||||
}
|
||||
|
@ -161,10 +161,10 @@ impl ClassBuilder {
|
||||
/// Panics if the class name associated with `T` is not the same as the
|
||||
/// class name specified when creating the builder.
|
||||
pub fn object_override<T: RegisteredClass>(mut self) -> Self {
|
||||
extern "C" fn create_object<T: RegisteredClass>(_: *mut ClassEntry) -> *mut ZendObject {
|
||||
extern "C" fn create_object<T: RegisteredClass>(ce: *mut ClassEntry) -> *mut ZendObject {
|
||||
// SAFETY: After calling this function, PHP will always call the constructor
|
||||
// defined below, which assumes that the object is uninitialized.
|
||||
let obj = unsafe { ZendClassObject::<T>::new_uninit() };
|
||||
let obj = unsafe { ZendClassObject::<T>::new_uninit(ce.as_ref()) };
|
||||
obj.into_raw().get_mut_zend_obj()
|
||||
}
|
||||
|
||||
|
@ -32,7 +32,9 @@ impl<T> Deref for Vec<T> {
|
||||
|
||||
impl<T> Drop for Vec<T> {
|
||||
fn drop(&mut self) {
|
||||
unsafe { Box::from_raw(std::ptr::slice_from_raw_parts_mut(self.ptr, self.len)) };
|
||||
unsafe {
|
||||
let _ = Box::from_raw(std::ptr::slice_from_raw_parts_mut(self.ptr, self.len));
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -52,7 +52,7 @@ impl ToStub for Module {
|
||||
// Inserts a value into the entries hashmap. Takes a key and an entry, creating
|
||||
// the internal vector if it doesn't already exist.
|
||||
let mut insert = |ns, entry| {
|
||||
let bucket = entries.entry(ns).or_insert_with(StdVec::new);
|
||||
let bucket = entries.entry(ns).or_default();
|
||||
bucket.push(entry);
|
||||
};
|
||||
|
||||
|
15
src/embed/embed.c
Normal file
15
src/embed/embed.c
Normal file
@ -0,0 +1,15 @@
|
||||
#include "embed.h"
|
||||
|
||||
// We actually use the PHP embed API to run PHP code in test
|
||||
// At some point we might want to use our own SAPI to do that
|
||||
void* ext_php_rs_embed_callback(int argc, char** argv, void* (*callback)(void *), void *ctx) {
|
||||
void *result = NULL;
|
||||
|
||||
PHP_EMBED_START_BLOCK(argc, argv)
|
||||
|
||||
result = callback(ctx);
|
||||
|
||||
PHP_EMBED_END_BLOCK()
|
||||
|
||||
return result;
|
||||
}
|
4
src/embed/embed.h
Normal file
4
src/embed/embed.h
Normal file
@ -0,0 +1,4 @@
|
||||
#include "zend.h"
|
||||
#include "sapi/embed/php_embed.h"
|
||||
|
||||
void* ext_php_rs_embed_callback(int argc, char** argv, void* (*callback)(void *), void *ctx);
|
16
src/embed/ffi.rs
Normal file
16
src/embed/ffi.rs
Normal file
@ -0,0 +1,16 @@
|
||||
//! Raw FFI bindings to the Zend API.
|
||||
|
||||
#![allow(clippy::all)]
|
||||
#![allow(warnings)]
|
||||
|
||||
use std::ffi::{c_char, c_int, c_void};
|
||||
|
||||
#[link(name = "wrapper")]
|
||||
extern "C" {
|
||||
pub fn ext_php_rs_embed_callback(
|
||||
argc: c_int,
|
||||
argv: *mut *mut c_char,
|
||||
func: unsafe extern "C" fn(*const c_void) -> *const c_void,
|
||||
ctx: *const c_void,
|
||||
) -> *mut c_void;
|
||||
}
|
277
src/embed/mod.rs
Normal file
277
src/embed/mod.rs
Normal file
@ -0,0 +1,277 @@
|
||||
//! Provides implementations for running php code from rust.
|
||||
//! It only works on linux for now and you should have `php-embed` installed
|
||||
//!
|
||||
//! This crate was only test with PHP 8.2 please report any issue with other
|
||||
//! version You should only use this crate for test purpose, it's not production
|
||||
//! ready
|
||||
|
||||
mod ffi;
|
||||
|
||||
use crate::boxed::ZBox;
|
||||
use crate::embed::ffi::ext_php_rs_embed_callback;
|
||||
use crate::ffi::{
|
||||
_zend_file_handle__bindgen_ty_1, php_execute_script, zend_eval_string, zend_file_handle,
|
||||
zend_stream_init_filename, ZEND_RESULT_CODE_SUCCESS,
|
||||
};
|
||||
use crate::types::{ZendObject, Zval};
|
||||
use crate::zend::{panic_wrapper, try_catch, ExecutorGlobals};
|
||||
use parking_lot::{const_rwlock, RwLock};
|
||||
use std::ffi::{c_char, c_void, CString, NulError};
|
||||
use std::panic::{resume_unwind, RefUnwindSafe};
|
||||
use std::path::Path;
|
||||
use std::ptr::null_mut;
|
||||
|
||||
pub struct Embed;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum EmbedError {
|
||||
InitError,
|
||||
ExecuteError(Option<ZBox<ZendObject>>),
|
||||
ExecuteScriptError,
|
||||
InvalidEvalString(NulError),
|
||||
InvalidPath,
|
||||
CatchError,
|
||||
}
|
||||
|
||||
impl EmbedError {
|
||||
pub fn is_bailout(&self) -> bool {
|
||||
matches!(self, EmbedError::CatchError)
|
||||
}
|
||||
}
|
||||
|
||||
static RUN_FN_LOCK: RwLock<()> = const_rwlock(());
|
||||
|
||||
impl Embed {
|
||||
/// Run a php script from a file
|
||||
///
|
||||
/// This function will only work correctly when used inside the `Embed::run`
|
||||
/// function otherwise behavior is unexpected
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Ok(())` - The script was executed successfully
|
||||
/// * `Err(EmbedError)` - An error occured during the execution of the
|
||||
/// script
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use ext_php_rs::embed::Embed;
|
||||
///
|
||||
/// Embed::run(|| {
|
||||
/// let result = Embed::run_script("src/embed/test-script.php");
|
||||
///
|
||||
/// assert!(result.is_ok());
|
||||
/// });
|
||||
/// ```
|
||||
pub fn run_script<P: AsRef<Path>>(path: P) -> Result<(), EmbedError> {
|
||||
let path = match path.as_ref().to_str() {
|
||||
Some(path) => match CString::new(path) {
|
||||
Ok(path) => path,
|
||||
Err(err) => return Err(EmbedError::InvalidEvalString(err)),
|
||||
},
|
||||
None => return Err(EmbedError::InvalidPath),
|
||||
};
|
||||
|
||||
let mut file_handle = zend_file_handle {
|
||||
handle: _zend_file_handle__bindgen_ty_1 { fp: null_mut() },
|
||||
filename: null_mut(),
|
||||
opened_path: null_mut(),
|
||||
type_: 0,
|
||||
primary_script: false,
|
||||
in_list: false,
|
||||
buf: null_mut(),
|
||||
len: 0,
|
||||
};
|
||||
|
||||
unsafe {
|
||||
zend_stream_init_filename(&mut file_handle, path.as_ptr());
|
||||
}
|
||||
|
||||
let exec_result = try_catch(|| unsafe { php_execute_script(&mut file_handle) });
|
||||
|
||||
match exec_result {
|
||||
Err(_) => Err(EmbedError::CatchError),
|
||||
Ok(true) => Ok(()),
|
||||
Ok(false) => Err(EmbedError::ExecuteScriptError),
|
||||
}
|
||||
}
|
||||
|
||||
/// Start and run embed sapi engine
|
||||
///
|
||||
/// This function will allow to run php code from rust, the same PHP context
|
||||
/// is keep between calls inside the function passed to this method.
|
||||
/// Which means subsequent calls to `Embed::eval` or `Embed::run_script`
|
||||
/// will be able to access variables defined in previous calls
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * R - The result of the function passed to this method
|
||||
///
|
||||
/// R must implement [`Default`] so it can be returned in case of a bailout
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use ext_php_rs::embed::Embed;
|
||||
///
|
||||
/// Embed::run(|| {
|
||||
/// let _ = Embed::eval("$foo = 'foo';");
|
||||
/// let foo = Embed::eval("$foo;");
|
||||
/// assert!(foo.is_ok());
|
||||
/// assert_eq!(foo.unwrap().string().unwrap(), "foo");
|
||||
/// });
|
||||
/// ```
|
||||
pub fn run<R, F: FnMut() -> R + RefUnwindSafe>(func: F) -> R
|
||||
where
|
||||
R: Default,
|
||||
{
|
||||
// @TODO handle php thread safe
|
||||
//
|
||||
// This is to prevent multiple threads from running php at the same time
|
||||
// At some point we should detect if php is compiled with thread safety and
|
||||
// avoid doing that in this case
|
||||
let _guard = RUN_FN_LOCK.write();
|
||||
|
||||
let panic = unsafe {
|
||||
ext_php_rs_embed_callback(
|
||||
0,
|
||||
null_mut(),
|
||||
panic_wrapper::<R, F>,
|
||||
&func as *const F as *const c_void,
|
||||
)
|
||||
};
|
||||
|
||||
// This can happen if there is a bailout
|
||||
if panic.is_null() {
|
||||
return R::default();
|
||||
}
|
||||
|
||||
match unsafe { *Box::from_raw(panic as *mut std::thread::Result<R>) } {
|
||||
Ok(r) => r,
|
||||
Err(err) => {
|
||||
// we resume the panic here so it can be catched correctly by the test framework
|
||||
resume_unwind(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Evaluate a php code
|
||||
///
|
||||
/// This function will only work correctly when used inside the `Embed::run`
|
||||
/// function
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Ok(Zval)` - The result of the evaluation
|
||||
/// * `Err(EmbedError)` - An error occured during the evaluation
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use ext_php_rs::embed::Embed;
|
||||
///
|
||||
/// Embed::run(|| {
|
||||
/// let foo = Embed::eval("$foo = 'foo';");
|
||||
/// assert!(foo.is_ok());
|
||||
/// });
|
||||
/// ```
|
||||
pub fn eval(code: &str) -> Result<Zval, EmbedError> {
|
||||
let cstr = match CString::new(code) {
|
||||
Ok(cstr) => cstr,
|
||||
Err(err) => return Err(EmbedError::InvalidEvalString(err)),
|
||||
};
|
||||
|
||||
let mut result = Zval::new();
|
||||
|
||||
let exec_result = try_catch(|| unsafe {
|
||||
zend_eval_string(
|
||||
cstr.as_ptr() as *const c_char,
|
||||
&mut result,
|
||||
b"run\0".as_ptr() as *const _,
|
||||
)
|
||||
});
|
||||
|
||||
match exec_result {
|
||||
Err(_) => Err(EmbedError::CatchError),
|
||||
Ok(ZEND_RESULT_CODE_SUCCESS) => Ok(result),
|
||||
Ok(_) => Err(EmbedError::ExecuteError(ExecutorGlobals::take_exception())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Embed;
|
||||
|
||||
#[test]
|
||||
fn test_run() {
|
||||
Embed::run(|| {
|
||||
let result = Embed::eval("$foo = 'foo';");
|
||||
|
||||
assert!(result.is_ok());
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_run_error() {
|
||||
Embed::run(|| {
|
||||
let result = Embed::eval("stupid code;");
|
||||
|
||||
assert!(!result.is_ok());
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_run_script() {
|
||||
Embed::run(|| {
|
||||
let result = Embed::run_script("src/embed/test-script.php");
|
||||
|
||||
assert!(result.is_ok());
|
||||
|
||||
let zval = Embed::eval("$foo;").unwrap();
|
||||
|
||||
assert!(zval.is_object());
|
||||
|
||||
let obj = zval.object().unwrap();
|
||||
|
||||
assert_eq!(obj.get_class_name().unwrap(), "Test");
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_run_script_error() {
|
||||
Embed::run(|| {
|
||||
let result = Embed::run_script("src/embed/test-script-exception.php");
|
||||
|
||||
assert!(!result.is_ok());
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn test_panic() {
|
||||
Embed::run(|| {
|
||||
panic!("test panic");
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_return() {
|
||||
let foo = Embed::run(|| {
|
||||
return "foo";
|
||||
});
|
||||
|
||||
assert_eq!(foo, "foo");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_eval_bailout() {
|
||||
Embed::run(|| {
|
||||
let result = Embed::eval("str_repeat('a', 100_000_000_000_000);");
|
||||
|
||||
assert!(result.is_err());
|
||||
assert!(result.unwrap_err().is_bailout());
|
||||
});
|
||||
}
|
||||
}
|
3
src/embed/test-script-exception.php
Normal file
3
src/embed/test-script-exception.php
Normal file
@ -0,0 +1,3 @@
|
||||
<?php
|
||||
|
||||
throw new \RuntimeException('This is a test exception');
|
7
src/embed/test-script.php
Normal file
7
src/embed/test-script.php
Normal file
@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
class Test {
|
||||
public function __construct() {}
|
||||
}
|
||||
|
||||
$foo = new Test();
|
@ -57,6 +57,8 @@ pub enum Error {
|
||||
InvalidUtf8,
|
||||
/// Could not call the given function.
|
||||
Callable,
|
||||
/// An object was expected.
|
||||
Object,
|
||||
/// An invalid exception type was thrown.
|
||||
InvalidException(ClassFlags),
|
||||
/// Converting integer arguments resulted in an overflow.
|
||||
@ -89,6 +91,7 @@ impl Display for Error {
|
||||
),
|
||||
Error::InvalidUtf8 => write!(f, "Invalid Utf8 byte sequence."),
|
||||
Error::Callable => write!(f, "Could not call given function."),
|
||||
Error::Object => write!(f, "An object was expected."),
|
||||
Error::InvalidException(flags) => {
|
||||
write!(f, "Invalid exception type was thrown: {flags:?}")
|
||||
}
|
||||
|
@ -1,12 +1,14 @@
|
||||
//! Types and functions used for throwing exceptions from Rust to PHP.
|
||||
|
||||
use std::ffi::CString;
|
||||
use std::{ffi::CString, fmt::Debug};
|
||||
|
||||
use crate::{
|
||||
class::RegisteredClass,
|
||||
error::{Error, Result},
|
||||
ffi::zend_throw_exception_ex,
|
||||
ffi::zend_throw_exception_object,
|
||||
flags::ClassFlags,
|
||||
types::Zval,
|
||||
zend::{ce, ClassEntry},
|
||||
};
|
||||
|
||||
@ -25,6 +27,7 @@ pub struct PhpException {
|
||||
message: String,
|
||||
code: i32,
|
||||
ex: &'static ClassEntry,
|
||||
object: Option<Zval>,
|
||||
}
|
||||
|
||||
impl PhpException {
|
||||
@ -36,7 +39,12 @@ impl PhpException {
|
||||
/// * `code` - Integer code to go inside the exception.
|
||||
/// * `ex` - Exception type to throw.
|
||||
pub fn new(message: String, code: i32, ex: &'static ClassEntry) -> Self {
|
||||
Self { message, code, ex }
|
||||
Self {
|
||||
message,
|
||||
code,
|
||||
ex,
|
||||
object: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new default exception instance, using the default PHP
|
||||
@ -59,10 +67,25 @@ impl PhpException {
|
||||
Self::new(message, 0, T::get_metadata().ce())
|
||||
}
|
||||
|
||||
/// Set the Zval object for the exception.
|
||||
///
|
||||
/// Exceptions can be based of instantiated Zval objects when you are throwing a custom exception with
|
||||
/// stateful properties.
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `object` - The Zval object.
|
||||
pub fn set_object(&mut self, object: Option<Zval>) {
|
||||
self.object = object;
|
||||
}
|
||||
|
||||
/// Throws the exception, returning nothing inside a result if successful
|
||||
/// and an error otherwise.
|
||||
pub fn throw(self) -> Result<()> {
|
||||
throw_with_code(self.ex, self.code, &self.message)
|
||||
match self.object {
|
||||
Some(object) => throw_object(object),
|
||||
None => throw_with_code(self.ex, self.code, &self.message),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -146,3 +169,44 @@ pub fn throw_with_code(ex: &ClassEntry, code: i32, message: &str) -> Result<()>
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Throws an exception object.
|
||||
///
|
||||
/// Returns a result containing nothing if the exception was successfully
|
||||
/// thrown.
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `object` - The zval of type object
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// use ext_php_rs::prelude::*;
|
||||
/// use ext_php_rs::exception::throw_object;
|
||||
/// use crate::ext_php_rs::convert::IntoZval;
|
||||
///
|
||||
/// #[php_class]
|
||||
/// #[extends(ext_php_rs::zend::ce::exception())]
|
||||
/// pub struct JsException {
|
||||
/// #[prop(flags = ext_php_rs::flags::PropertyFlags::Public)]
|
||||
/// message: String,
|
||||
/// #[prop(flags = ext_php_rs::flags::PropertyFlags::Public)]
|
||||
/// code: i32,
|
||||
/// #[prop(flags = ext_php_rs::flags::PropertyFlags::Public)]
|
||||
/// file: String,
|
||||
/// }
|
||||
///
|
||||
/// #[php_module]
|
||||
/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
|
||||
/// module
|
||||
/// }
|
||||
///
|
||||
/// let error = JsException { message: "A JS error occurred.".to_string(), code: 100, file: "index.js".to_string() };
|
||||
/// throw_object( error.into_zval(true).unwrap() );
|
||||
/// ```
|
||||
pub fn throw_object(zval: Zval) -> Result<()> {
|
||||
let mut zv = core::mem::ManuallyDrop::new(zval);
|
||||
unsafe { zend_throw_exception_object(core::ptr::addr_of_mut!(zv).cast()) };
|
||||
Ok(())
|
||||
}
|
||||
|
@ -29,6 +29,13 @@ extern "C" {
|
||||
pub fn ext_php_rs_process_globals() -> *mut php_core_globals;
|
||||
pub fn ext_php_rs_sapi_globals() -> *mut sapi_globals_struct;
|
||||
pub fn ext_php_rs_file_globals() -> *mut php_file_globals;
|
||||
pub fn ext_php_rs_sapi_module() -> *mut sapi_module_struct;
|
||||
pub fn ext_php_rs_zend_try_catch(
|
||||
func: unsafe extern "C" fn(*const c_void) -> *const c_void,
|
||||
ctx: *const c_void,
|
||||
result: *mut *mut c_void,
|
||||
) -> bool;
|
||||
pub fn ext_php_rs_zend_bailout() -> !;
|
||||
}
|
||||
|
||||
include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
|
||||
|
30
src/flags.rs
30
src/flags.rs
@ -8,9 +8,10 @@ use crate::ffi::{
|
||||
CONST_CS, CONST_DEPRECATED, CONST_NO_FILE_CACHE, CONST_PERSISTENT, E_COMPILE_ERROR,
|
||||
E_COMPILE_WARNING, E_CORE_ERROR, E_CORE_WARNING, E_DEPRECATED, E_ERROR, E_NOTICE, E_PARSE,
|
||||
E_RECOVERABLE_ERROR, E_STRICT, E_USER_DEPRECATED, E_USER_ERROR, E_USER_NOTICE, E_USER_WARNING,
|
||||
E_WARNING, IS_ARRAY, IS_CALLABLE, IS_CONSTANT_AST, IS_DOUBLE, IS_FALSE, IS_LONG, IS_MIXED,
|
||||
IS_NULL, IS_OBJECT, IS_PTR, IS_REFERENCE, IS_RESOURCE, IS_STRING, IS_TRUE, IS_TYPE_COLLECTABLE,
|
||||
IS_TYPE_REFCOUNTED, IS_UNDEF, IS_VOID, ZEND_ACC_ABSTRACT, ZEND_ACC_ANON_CLASS,
|
||||
E_WARNING, IS_ARRAY, IS_CALLABLE, IS_CONSTANT_AST, IS_DOUBLE, IS_FALSE, IS_INDIRECT, IS_LONG,
|
||||
IS_MIXED, IS_NULL, IS_OBJECT, IS_PTR, IS_REFERENCE, IS_RESOURCE, IS_STRING, IS_TRUE,
|
||||
IS_TYPE_COLLECTABLE, IS_TYPE_REFCOUNTED, IS_UNDEF, IS_VOID, PHP_INI_ALL, PHP_INI_PERDIR,
|
||||
PHP_INI_SYSTEM, PHP_INI_USER, ZEND_ACC_ABSTRACT, ZEND_ACC_ANON_CLASS,
|
||||
ZEND_ACC_CALL_VIA_TRAMPOLINE, ZEND_ACC_CHANGED, ZEND_ACC_CLOSURE, ZEND_ACC_CONSTANTS_UPDATED,
|
||||
ZEND_ACC_CTOR, ZEND_ACC_DEPRECATED, ZEND_ACC_DONE_PASS_TWO, ZEND_ACC_EARLY_BINDING,
|
||||
ZEND_ACC_FAKE_CLOSURE, ZEND_ACC_FINAL, ZEND_ACC_GENERATOR, ZEND_ACC_HAS_FINALLY_BLOCK,
|
||||
@ -174,6 +175,16 @@ bitflags! {
|
||||
}
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
/// Represents permissions for where a configuration setting may be set.
|
||||
pub struct IniEntryPermission: u32 {
|
||||
const User = PHP_INI_USER;
|
||||
const PerDir = PHP_INI_PERDIR;
|
||||
const System = PHP_INI_SYSTEM;
|
||||
const All = PHP_INI_ALL;
|
||||
}
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
/// Represents error types when used via php_error_docref for example.
|
||||
pub struct ErrorType: u32 {
|
||||
@ -194,6 +205,7 @@ bitflags! {
|
||||
const UserDeprecated = E_USER_DEPRECATED;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Hash, Debug, Clone, Copy)]
|
||||
pub enum FunctionType {
|
||||
Internal,
|
||||
@ -234,6 +246,7 @@ pub enum DataType {
|
||||
Mixed,
|
||||
Bool,
|
||||
Ptr,
|
||||
Indirect,
|
||||
}
|
||||
|
||||
impl Default for DataType {
|
||||
@ -257,6 +270,7 @@ impl DataType {
|
||||
DataType::Object(_) => IS_OBJECT,
|
||||
DataType::Resource => IS_RESOURCE,
|
||||
DataType::Reference => IS_RESOURCE,
|
||||
DataType::Indirect => IS_INDIRECT,
|
||||
DataType::Callable => IS_CALLABLE,
|
||||
DataType::ConstantExpression => IS_CONSTANT_AST,
|
||||
DataType::Void => IS_VOID,
|
||||
@ -325,6 +339,7 @@ impl From<u32> for DataType {
|
||||
|
||||
contains!(IS_VOID, Void);
|
||||
contains!(IS_PTR, Ptr);
|
||||
contains!(IS_INDIRECT, Indirect);
|
||||
contains!(IS_CALLABLE, Callable);
|
||||
contains!(IS_CONSTANT_AST, ConstantExpression);
|
||||
contains!(IS_REFERENCE, Reference);
|
||||
@ -367,6 +382,7 @@ impl Display for DataType {
|
||||
DataType::Bool => write!(f, "Bool"),
|
||||
DataType::Mixed => write!(f, "Mixed"),
|
||||
DataType::Ptr => write!(f, "Pointer"),
|
||||
DataType::Indirect => write!(f, "Indirect"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -376,9 +392,9 @@ mod tests {
|
||||
use super::DataType;
|
||||
use crate::ffi::{
|
||||
IS_ARRAY, IS_ARRAY_EX, IS_CALLABLE, IS_CONSTANT_AST, IS_CONSTANT_AST_EX, IS_DOUBLE,
|
||||
IS_FALSE, IS_INTERNED_STRING_EX, IS_LONG, IS_NULL, IS_OBJECT, IS_OBJECT_EX, IS_PTR,
|
||||
IS_REFERENCE, IS_REFERENCE_EX, IS_RESOURCE, IS_RESOURCE_EX, IS_STRING, IS_STRING_EX,
|
||||
IS_TRUE, IS_UNDEF, IS_VOID,
|
||||
IS_FALSE, IS_INDIRECT, IS_INTERNED_STRING_EX, IS_LONG, IS_NULL, IS_OBJECT, IS_OBJECT_EX,
|
||||
IS_PTR, IS_REFERENCE, IS_REFERENCE_EX, IS_RESOURCE, IS_RESOURCE_EX, IS_STRING,
|
||||
IS_STRING_EX, IS_TRUE, IS_UNDEF, IS_VOID,
|
||||
};
|
||||
use std::convert::TryFrom;
|
||||
|
||||
@ -402,7 +418,7 @@ mod tests {
|
||||
test!(IS_RESOURCE, Resource);
|
||||
test!(IS_REFERENCE, Reference);
|
||||
test!(IS_CONSTANT_AST, ConstantExpression);
|
||||
test!(IS_CALLABLE, Callable);
|
||||
test!(IS_INDIRECT, Indirect);
|
||||
test!(IS_VOID, Void);
|
||||
test!(IS_PTR, Ptr);
|
||||
|
||||
|
@ -25,6 +25,8 @@ pub mod class;
|
||||
pub mod closure;
|
||||
pub mod constant;
|
||||
pub mod describe;
|
||||
#[cfg(feature = "embed")]
|
||||
pub mod embed;
|
||||
#[doc(hidden)]
|
||||
pub mod internal;
|
||||
pub mod props;
|
||||
@ -35,6 +37,7 @@ pub mod zend;
|
||||
/// A module typically glob-imported containing the typically required macros
|
||||
/// and imports.
|
||||
pub mod prelude {
|
||||
|
||||
pub use crate::builders::ModuleBuilder;
|
||||
#[cfg(any(docs, feature = "closure"))]
|
||||
#[cfg_attr(docs, doc(cfg(feature = "closure")))]
|
||||
|
@ -187,6 +187,33 @@ impl ZendHashTable {
|
||||
unsafe { zend_hash_str_find(self, str.as_ptr(), key.len() as _).as_ref() }
|
||||
}
|
||||
|
||||
/// Attempts to retrieve a value from the hash table with a string key.
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `key` - The key to search for in the hash table.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Some(&Zval)` - A reference to the zval at the position in the hash
|
||||
/// table.
|
||||
/// * `None` - No value at the given position was found.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```no_run
|
||||
/// use ext_php_rs::types::ZendHashTable;
|
||||
///
|
||||
/// let mut ht = ZendHashTable::new();
|
||||
///
|
||||
/// ht.insert("test", "hello world");
|
||||
/// assert_eq!(ht.get("test").and_then(|zv| zv.str()), Some("hello world"));
|
||||
/// ```
|
||||
pub fn get_mut(&self, key: &'_ str) -> Option<&mut Zval> {
|
||||
let str = CString::new(key).ok()?;
|
||||
unsafe { zend_hash_str_find(self, str.as_ptr(), key.len() as _).as_mut() }
|
||||
}
|
||||
|
||||
/// Attempts to retrieve a value from the hash table with an index.
|
||||
///
|
||||
/// # Parameters
|
||||
@ -213,6 +240,32 @@ impl ZendHashTable {
|
||||
unsafe { zend_hash_index_find(self, key).as_ref() }
|
||||
}
|
||||
|
||||
/// Attempts to retrieve a value from the hash table with an index.
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `key` - The key to search for in the hash table.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Some(&Zval)` - A reference to the zval at the position in the hash
|
||||
/// table.
|
||||
/// * `None` - No value at the given position was found.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```no_run
|
||||
/// use ext_php_rs::types::ZendHashTable;
|
||||
///
|
||||
/// let mut ht = ZendHashTable::new();
|
||||
///
|
||||
/// ht.push(100);
|
||||
/// assert_eq!(ht.get_index(0).and_then(|zv| zv.long()), Some(100));
|
||||
/// ```
|
||||
pub fn get_index_mut(&self, key: u64) -> Option<&mut Zval> {
|
||||
unsafe { zend_hash_index_find(self, key).as_mut() }
|
||||
}
|
||||
|
||||
/// Attempts to remove a value from the hash table with a string key.
|
||||
///
|
||||
/// # Parameters
|
||||
@ -715,7 +768,7 @@ impl<'a> FromZval<'a> for &'a ZendHashTable {
|
||||
}
|
||||
|
||||
///////////////////////////////////////////
|
||||
//// HashMap
|
||||
/// HashMap
|
||||
///////////////////////////////////////////
|
||||
|
||||
impl<'a, V> TryFrom<&'a ZendHashTable> for HashMap<String, V>
|
||||
@ -784,7 +837,7 @@ where
|
||||
}
|
||||
|
||||
///////////////////////////////////////////
|
||||
//// Vec
|
||||
/// Vec
|
||||
///////////////////////////////////////////
|
||||
|
||||
impl<'a, T> TryFrom<&'a ZendHashTable> for Vec<T>
|
||||
|
@ -99,6 +99,7 @@ impl<'a> ZendCallable<'a> {
|
||||
/// let result = strpos.try_call(vec![&"hello", &"e"]).unwrap();
|
||||
/// assert_eq!(result.long(), Some(1));
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
pub fn try_call(&self, params: Vec<&dyn IntoZvalDyn>) -> Result<Zval> {
|
||||
if !self.0.is_callable() {
|
||||
return Err(Error::Callable);
|
||||
|
@ -5,6 +5,7 @@ use std::{
|
||||
fmt::Debug,
|
||||
mem,
|
||||
ops::{Deref, DerefMut},
|
||||
os::raw::c_char,
|
||||
ptr::{self, NonNull},
|
||||
};
|
||||
|
||||
@ -19,6 +20,7 @@ use crate::{
|
||||
},
|
||||
flags::DataType,
|
||||
types::{ZendObject, Zval},
|
||||
zend::ClassEntry,
|
||||
};
|
||||
|
||||
/// Representation of a Zend class object in memory.
|
||||
@ -43,7 +45,7 @@ impl<T: RegisteredClass> ZendClassObject<T> {
|
||||
/// Panics if memory was unable to be allocated for the new object.
|
||||
pub fn new(val: T) -> ZBox<Self> {
|
||||
// SAFETY: We are providing a value to initialize the object with.
|
||||
unsafe { Self::internal_new(Some(val)) }
|
||||
unsafe { Self::internal_new(Some(val), None) }
|
||||
}
|
||||
|
||||
/// Creates a new [`ZendClassObject`] of type `T`, with an uninitialized
|
||||
@ -67,8 +69,8 @@ impl<T: RegisteredClass> ZendClassObject<T> {
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if memory was unable to be allocated for the new object.
|
||||
pub unsafe fn new_uninit() -> ZBox<Self> {
|
||||
Self::internal_new(None)
|
||||
pub unsafe fn new_uninit(ce: Option<&'static ClassEntry>) -> ZBox<Self> {
|
||||
Self::internal_new(None, ce)
|
||||
}
|
||||
|
||||
/// Creates a new [`ZendObject`] of type `T`, storing the given (and
|
||||
@ -102,10 +104,10 @@ impl<T: RegisteredClass> ZendClassObject<T> {
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if memory was unable to be allocated for the new object.
|
||||
unsafe fn internal_new(val: Option<T>) -> ZBox<Self> {
|
||||
unsafe fn internal_new(val: Option<T>, ce: Option<&'static ClassEntry>) -> ZBox<Self> {
|
||||
let size = mem::size_of::<ZendClassObject<T>>();
|
||||
let meta = T::get_metadata();
|
||||
let ce = meta.ce() as *const _ as *mut _;
|
||||
let ce = ce.unwrap_or_else(|| meta.ce()) as *const _ as *mut _;
|
||||
let obj = ext_php_rs_zend_object_alloc(size as _, ce) as *mut ZendClassObject<T>;
|
||||
let obj = obj
|
||||
.as_mut()
|
||||
@ -155,18 +157,19 @@ impl<T: RegisteredClass> ZendClassObject<T> {
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `obj` - The zend object to get the [`ZendClassObject`] for.
|
||||
#[allow(clippy::needless_pass_by_ref_mut)]
|
||||
pub fn from_zend_obj_mut(std: &mut zend_object) -> Option<&mut Self> {
|
||||
Self::_from_zend_obj(std)
|
||||
}
|
||||
|
||||
fn _from_zend_obj(std: &zend_object) -> Option<&mut Self> {
|
||||
let std = std as *const zend_object as *const i8;
|
||||
let std = std as *const zend_object as *const c_char;
|
||||
let ptr = unsafe {
|
||||
let ptr = std.offset(0 - Self::std_offset() as isize) as *const Self;
|
||||
(ptr as *mut Self).as_mut()?
|
||||
};
|
||||
|
||||
if ptr.std.is_instance::<T>() {
|
||||
if ptr.std.instance_of(T::get_metadata().ce()) {
|
||||
Some(ptr)
|
||||
} else {
|
||||
None
|
||||
|
@ -1,16 +1,17 @@
|
||||
//! Represents an object in PHP. Allows for overriding the internal object used
|
||||
//! by classes, allowing users to store Rust data inside a PHP object.
|
||||
|
||||
use std::{convert::TryInto, fmt::Debug, ops::DerefMut};
|
||||
use std::{convert::TryInto, fmt::Debug, ops::DerefMut, os::raw::c_char};
|
||||
|
||||
use crate::{
|
||||
boxed::{ZBox, ZBoxable},
|
||||
class::RegisteredClass,
|
||||
convert::{FromZendObject, FromZval, FromZvalMut, IntoZval},
|
||||
convert::{FromZendObject, FromZval, FromZvalMut, IntoZval, IntoZvalDyn},
|
||||
error::{Error, Result},
|
||||
ffi::{
|
||||
ext_php_rs_zend_object_release, zend_call_known_function, zend_object, zend_objects_new,
|
||||
HashTable, ZEND_ISEMPTY, ZEND_PROPERTY_EXISTS, ZEND_PROPERTY_ISSET,
|
||||
ext_php_rs_zend_object_release, object_properties_init, zend_call_known_function,
|
||||
zend_function, zend_hash_str_find_ptr_lc, zend_object, zend_objects_new, HashTable,
|
||||
ZEND_ISEMPTY, ZEND_PROPERTY_EXISTS, ZEND_PROPERTY_ISSET,
|
||||
},
|
||||
flags::DataType,
|
||||
rc::PhpRc,
|
||||
@ -41,7 +42,18 @@ impl ZendObject {
|
||||
// SAFETY: Using emalloc to allocate memory inside Zend arena. Casting `ce` to
|
||||
// `*mut` is valid as the function will not mutate `ce`.
|
||||
unsafe {
|
||||
let ptr = zend_objects_new(ce as *const _ as *mut _);
|
||||
let ptr = match ce.__bindgen_anon_2.create_object {
|
||||
None => {
|
||||
let ptr = zend_objects_new(ce as *const _ as *mut _);
|
||||
if ptr.is_null() {
|
||||
panic!("Failed to allocate memory for Zend object")
|
||||
}
|
||||
object_properties_init(ptr, ce as *const _ as *mut _);
|
||||
ptr
|
||||
}
|
||||
Some(v) => v(ce as *const _ as *mut _),
|
||||
};
|
||||
|
||||
ZBox::from_raw(
|
||||
ptr.as_mut()
|
||||
.expect("Failed to allocate memory for Zend object"),
|
||||
@ -121,6 +133,38 @@ impl ZendObject {
|
||||
(self.ce as *const ClassEntry).eq(&(T::get_metadata().ce() as *const _))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn try_call_method(&self, name: &str, params: Vec<&dyn IntoZvalDyn>) -> Result<Zval> {
|
||||
let mut retval = Zval::new();
|
||||
let len = params.len();
|
||||
let params = params
|
||||
.into_iter()
|
||||
.map(|val| val.as_zval(false))
|
||||
.collect::<Result<Vec<_>>>()?;
|
||||
let packed = params.into_boxed_slice();
|
||||
|
||||
unsafe {
|
||||
let res = zend_hash_str_find_ptr_lc(
|
||||
&(*self.ce).function_table,
|
||||
name.as_ptr() as *const c_char,
|
||||
name.len(),
|
||||
) as *mut zend_function;
|
||||
if res.is_null() {
|
||||
return Err(Error::Callable);
|
||||
}
|
||||
zend_call_known_function(
|
||||
res,
|
||||
self as *const _ as *mut _,
|
||||
self.ce,
|
||||
&mut retval,
|
||||
len as _,
|
||||
packed.as_ptr() as *mut _,
|
||||
std::ptr::null_mut(),
|
||||
)
|
||||
};
|
||||
|
||||
Ok(retval)
|
||||
}
|
||||
/// Attempts to read a property from the Object. Returns a result containing
|
||||
/// the value of the property if it exists and can be read, and an
|
||||
/// [`Error`] otherwise.
|
||||
|
@ -453,3 +453,23 @@ impl<'a> FromZval<'a> for &'a str {
|
||||
zval.str()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[cfg(feature = "embed")]
|
||||
mod tests {
|
||||
use crate::embed::Embed;
|
||||
|
||||
#[test]
|
||||
fn test_string() {
|
||||
Embed::run(|| {
|
||||
let result = Embed::eval("'foo';");
|
||||
|
||||
assert!(result.is_ok());
|
||||
|
||||
let zval = result.as_ref().unwrap();
|
||||
|
||||
assert!(zval.is_string());
|
||||
assert_eq!(zval.string().unwrap(), "foo");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -206,6 +206,31 @@ impl Zval {
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn try_call_method(&self, name: &str, params: Vec<&dyn IntoZvalDyn>) -> Result<Zval> {
|
||||
self.object()
|
||||
.ok_or(Error::Object)?
|
||||
.try_call_method(name, params)
|
||||
}
|
||||
|
||||
/// Returns the value of the zval if it is an internal indirect reference.
|
||||
pub fn indirect(&self) -> Option<&Zval> {
|
||||
if self.is_indirect() {
|
||||
Some(unsafe { &*(self.value.zv as *mut Zval) })
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the zval if it is an internal indirect reference.
|
||||
pub fn indirect_mut(&self) -> Option<&mut Zval> {
|
||||
if self.is_indirect() {
|
||||
Some(unsafe { &mut *(self.value.zv as *mut Zval) })
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the value of the zval if it is a reference.
|
||||
pub fn reference(&self) -> Option<&Zval> {
|
||||
if self.is_reference() {
|
||||
@ -257,6 +282,7 @@ impl Zval {
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `params` - A list of parameters to call the function with.
|
||||
#[inline(always)]
|
||||
pub fn try_call(&self, params: Vec<&dyn IntoZvalDyn>) -> Result<Zval> {
|
||||
self.callable().ok_or(Error::Callable)?.try_call(params)
|
||||
}
|
||||
@ -321,6 +347,11 @@ impl Zval {
|
||||
self.get_type() == DataType::Reference
|
||||
}
|
||||
|
||||
/// Returns true if the zval is a reference, false otherwise.
|
||||
pub fn is_indirect(&self) -> bool {
|
||||
self.get_type() == DataType::Indirect
|
||||
}
|
||||
|
||||
/// Returns true if the zval is callable, false otherwise.
|
||||
pub fn is_callable(&self) -> bool {
|
||||
let ptr: *const Self = self;
|
||||
@ -593,6 +624,7 @@ impl Debug for Zval {
|
||||
DataType::ConstantExpression => field!(Option::<()>::None),
|
||||
DataType::Void => field!(Option::<()>::None),
|
||||
DataType::Bool => field!(self.bool()),
|
||||
DataType::Indirect => field!(self.indirect()),
|
||||
// SAFETY: We are not accessing the pointer.
|
||||
DataType::Ptr => field!(unsafe { self.ptr::<c_void>() }),
|
||||
};
|
||||
|
@ -52,7 +52,6 @@ php_core_globals *ext_php_rs_process_globals() {
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
sapi_globals_struct *ext_php_rs_sapi_globals() {
|
||||
#ifdef ZTS
|
||||
#ifdef ZEND_ENABLE_STATIC_TSRMLS_CACHE
|
||||
@ -65,7 +64,6 @@ sapi_globals_struct *ext_php_rs_sapi_globals() {
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
php_file_globals *ext_php_rs_file_globals() {
|
||||
#ifdef ZTS
|
||||
return TSRMG_FAST_BULK(file_globals_id, php_file_globals *);
|
||||
@ -73,3 +71,21 @@ php_file_globals *ext_php_rs_file_globals() {
|
||||
return &file_globals;
|
||||
#endif
|
||||
}
|
||||
|
||||
sapi_module_struct *ext_php_rs_sapi_module() {
|
||||
return &sapi_module;
|
||||
}
|
||||
|
||||
bool ext_php_rs_zend_try_catch(void* (*callback)(void *), void *ctx, void **result) {
|
||||
zend_try {
|
||||
*result = callback(ctx);
|
||||
} zend_catch {
|
||||
return true;
|
||||
} zend_end_try();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void ext_php_rs_zend_bailout() {
|
||||
zend_bailout();
|
||||
}
|
||||
|
@ -22,8 +22,9 @@
|
||||
#include "zend_exceptions.h"
|
||||
#include "zend_inheritance.h"
|
||||
#include "zend_interfaces.h"
|
||||
#include "SAPI.h"
|
||||
#include "php_variables.h"
|
||||
#include "zend_ini.h"
|
||||
#include "main/SAPI.h"
|
||||
|
||||
zend_string *ext_php_rs_zend_string_init(const char *str, size_t len, bool persistent);
|
||||
void ext_php_rs_zend_string_release(zend_string *zs);
|
||||
@ -37,3 +38,6 @@ zend_executor_globals *ext_php_rs_executor_globals();
|
||||
php_core_globals *ext_php_rs_process_globals();
|
||||
sapi_globals_struct *ext_php_rs_sapi_globals();
|
||||
php_file_globals *ext_php_rs_file_globals();
|
||||
sapi_module_struct *ext_php_rs_sapi_module();
|
||||
bool ext_php_rs_zend_try_catch(void* (*callback)(void *), void *ctx, void **result);
|
||||
void ext_php_rs_zend_bailout();
|
||||
|
@ -1,6 +1,12 @@
|
||||
//! Builder and objects for creating classes in the PHP world.
|
||||
|
||||
use crate::{ffi::zend_class_entry, flags::ClassFlags, types::ZendStr, zend::ExecutorGlobals};
|
||||
use crate::{
|
||||
boxed::ZBox,
|
||||
ffi::zend_class_entry,
|
||||
flags::ClassFlags,
|
||||
types::{ZendObject, ZendStr},
|
||||
zend::ExecutorGlobals,
|
||||
};
|
||||
use std::{convert::TryInto, fmt::Debug, ops::DerefMut};
|
||||
|
||||
/// A PHP class entry.
|
||||
@ -22,6 +28,17 @@ impl ClassEntry {
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new [`ZendObject`], returned inside an [`ZBox<ZendObject>`]
|
||||
/// wrapper.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics when allocating memory for the new object fails.
|
||||
#[allow(clippy::new_ret_no_self)]
|
||||
pub fn new(&self) -> ZBox<ZendObject> {
|
||||
ZendObject::new(self)
|
||||
}
|
||||
|
||||
/// Returns the class flags.
|
||||
pub fn flags(&self) -> ClassFlags {
|
||||
ClassFlags::from_bits_truncate(self.ce_flags)
|
||||
|
@ -6,6 +6,8 @@ use crate::{
|
||||
types::{ZendClassObject, ZendObject, Zval},
|
||||
};
|
||||
|
||||
use super::function::Function;
|
||||
|
||||
/// Execute data passed when a function is called from PHP.
|
||||
///
|
||||
/// This generally contains things related to the call, including but not
|
||||
@ -194,6 +196,16 @@ impl ExecuteData {
|
||||
self.This.object_mut()
|
||||
}
|
||||
|
||||
/// Attempt to retrieve the function that is being called.
|
||||
pub fn function(&self) -> Option<&Function> {
|
||||
unsafe { self.func.as_ref() }
|
||||
}
|
||||
|
||||
/// Attempt to retrieve the previous execute data on the call stack.
|
||||
pub fn previous(&self) -> Option<&Self> {
|
||||
unsafe { self.prev_execute_data.as_ref() }
|
||||
}
|
||||
|
||||
/// Translation of macro `ZEND_CALL_ARG(call, n)`
|
||||
/// zend_compile.h:578
|
||||
///
|
||||
|
@ -2,7 +2,18 @@
|
||||
|
||||
use std::{fmt::Debug, os::raw::c_char, ptr};
|
||||
|
||||
use crate::ffi::zend_function_entry;
|
||||
use crate::{
|
||||
convert::IntoZvalDyn,
|
||||
error::Result,
|
||||
ffi::{
|
||||
zend_call_known_function, zend_fetch_function_str, zend_function, zend_function_entry,
|
||||
zend_hash_str_find_ptr_lc,
|
||||
},
|
||||
flags::FunctionType,
|
||||
types::Zval,
|
||||
};
|
||||
|
||||
use super::ClassEntry;
|
||||
|
||||
/// A Zend function entry.
|
||||
pub type FunctionEntry = zend_function_entry;
|
||||
@ -36,3 +47,86 @@ impl FunctionEntry {
|
||||
Box::into_raw(Box::new(self))
|
||||
}
|
||||
}
|
||||
|
||||
pub type Function = zend_function;
|
||||
|
||||
impl Function {
|
||||
pub fn function_type(&self) -> FunctionType {
|
||||
FunctionType::from(unsafe { self.type_ })
|
||||
}
|
||||
|
||||
pub fn try_from_function(name: &str) -> Option<Self> {
|
||||
unsafe {
|
||||
let res = zend_fetch_function_str(name.as_ptr() as *const c_char, name.len());
|
||||
if res.is_null() {
|
||||
return None;
|
||||
}
|
||||
Some(*res)
|
||||
}
|
||||
}
|
||||
pub fn try_from_method(class: &str, name: &str) -> Option<Self> {
|
||||
match ClassEntry::try_find(class) {
|
||||
None => None,
|
||||
Some(ce) => unsafe {
|
||||
let res = zend_hash_str_find_ptr_lc(
|
||||
&ce.function_table,
|
||||
name.as_ptr() as *const c_char,
|
||||
name.len(),
|
||||
) as *mut zend_function;
|
||||
if res.is_null() {
|
||||
return None;
|
||||
}
|
||||
Some(*res)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Attempts to call the callable with a list of arguments to pass to the
|
||||
/// function.
|
||||
///
|
||||
/// You should not call this function directly, rather through the
|
||||
/// [`call_user_func`] macro.
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `params` - A list of parameters to call the function with.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// Returns the result wrapped in [`Ok`] upon success. If calling the
|
||||
/// callable fails, or an exception is thrown, an [`Err`] is returned.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```no_run
|
||||
/// use ext_php_rs::types::ZendCallable;
|
||||
///
|
||||
/// let strpos = ZendCallable::try_from_name("strpos").unwrap();
|
||||
/// let result = strpos.try_call(vec![&"hello", &"e"]).unwrap();
|
||||
/// assert_eq!(result.long(), Some(1));
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
pub fn try_call(&self, params: Vec<&dyn IntoZvalDyn>) -> Result<Zval> {
|
||||
let mut retval = Zval::new();
|
||||
let len = params.len();
|
||||
let params = params
|
||||
.into_iter()
|
||||
.map(|val| val.as_zval(false))
|
||||
.collect::<Result<Vec<_>>>()?;
|
||||
let packed = params.into_boxed_slice();
|
||||
|
||||
unsafe {
|
||||
zend_call_known_function(
|
||||
self as *const _ as *mut _,
|
||||
std::ptr::null_mut(),
|
||||
std::ptr::null_mut(),
|
||||
&mut retval,
|
||||
len as _,
|
||||
packed.as_ptr() as *mut _,
|
||||
std::ptr::null_mut(),
|
||||
)
|
||||
};
|
||||
|
||||
Ok(retval)
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
//! Types related to the PHP executor, sapi and process globals.
|
||||
use std::ffi::CStr;
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use std::slice;
|
||||
use std::str;
|
||||
@ -15,15 +16,24 @@ use crate::ffi::{
|
||||
sapi_globals_struct, sapi_header_struct, sapi_headers_struct, sapi_request_info,
|
||||
zend_is_auto_global, TRACK_VARS_COOKIE, TRACK_VARS_ENV, TRACK_VARS_FILES, TRACK_VARS_GET,
|
||||
TRACK_VARS_POST, TRACK_VARS_REQUEST, TRACK_VARS_SERVER,
|
||||
_sapi_globals_struct, _sapi_module_struct, _zend_executor_globals, ext_php_rs_executor_globals,
|
||||
ext_php_rs_sapi_globals, ext_php_rs_sapi_module, zend_ini_entry,
|
||||
};
|
||||
|
||||
use crate::types::{ZendHashTable, ZendObject, ZendStr};
|
||||
|
||||
use super::linked_list::ZendLinkedListIterator;
|
||||
use crate::types::{ZendHashTable, ZendObject};
|
||||
|
||||
/// Stores global variables used in the PHP executor.
|
||||
pub type ExecutorGlobals = _zend_executor_globals;
|
||||
|
||||
/// Stores global SAPI variables used in the PHP executor.
|
||||
pub type SapiGlobals = _sapi_globals_struct;
|
||||
|
||||
/// Stores the SAPI module used in the PHP executor.
|
||||
pub type SapiModule = _sapi_module_struct;
|
||||
|
||||
impl ExecutorGlobals {
|
||||
/// Returns a reference to the PHP executor globals.
|
||||
///
|
||||
@ -62,6 +72,41 @@ impl ExecutorGlobals {
|
||||
unsafe { self.class_table.as_ref() }
|
||||
}
|
||||
|
||||
/// Attempts to retrieve the global functions hash table.
|
||||
pub fn function_table(&self) -> Option<&ZendHashTable> {
|
||||
unsafe { self.function_table.as_ref() }
|
||||
}
|
||||
|
||||
/// Attempts to retrieve the global functions hash table as mutable.
|
||||
pub fn function_table_mut(&self) -> Option<&mut ZendHashTable> {
|
||||
unsafe { self.function_table.as_mut() }
|
||||
}
|
||||
|
||||
/// Retrieves the ini values for all ini directives in the current executor
|
||||
/// context..
|
||||
pub fn ini_values(&self) -> HashMap<String, Option<String>> {
|
||||
let hash_table = unsafe { &*self.ini_directives };
|
||||
let mut ini_hash_map: HashMap<String, Option<String>> = HashMap::new();
|
||||
for (_index, key, value) in hash_table.iter() {
|
||||
if let Some(key) = key {
|
||||
ini_hash_map.insert(key, unsafe {
|
||||
let ini_entry = &*value.ptr::<zend_ini_entry>().expect("Invalid ini entry");
|
||||
if ini_entry.value.is_null() {
|
||||
None
|
||||
} else {
|
||||
Some(
|
||||
(*ini_entry.value)
|
||||
.as_str()
|
||||
.expect("Ini value is not a string")
|
||||
.to_owned(),
|
||||
)
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
ini_hash_map
|
||||
}
|
||||
|
||||
/// Attempts to retrieve the global constants table.
|
||||
pub fn constants(&self) -> Option<&ZendHashTable> {
|
||||
unsafe { self.zend_constants.as_ref() }
|
||||
@ -97,6 +142,87 @@ impl ExecutorGlobals {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Cancel a requested an interrupt of the PHP VM.
|
||||
pub fn cancel_interrupt(&mut self) {
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(php82)] {
|
||||
unsafe {
|
||||
zend_atomic_bool_store(&mut self.vm_interrupt, false);
|
||||
}
|
||||
} else {
|
||||
self.vm_interrupt = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SapiGlobals {
|
||||
/// Returns a reference to the PHP SAPI globals.
|
||||
///
|
||||
/// The executor globals are guarded by a RwLock. There can be multiple
|
||||
/// immutable references at one time but only ever one mutable reference.
|
||||
/// Attempting to retrieve the globals while already holding the global
|
||||
/// guard will lead to a deadlock. Dropping the globals guard will release
|
||||
/// the lock.
|
||||
pub fn get() -> GlobalReadGuard<Self> {
|
||||
// SAFETY: PHP executor globals are statically declared therefore should never
|
||||
// return an invalid pointer.
|
||||
let globals = unsafe { ext_php_rs_sapi_globals().as_ref() }
|
||||
.expect("Static executor globals were invalid");
|
||||
let guard = SAPI_LOCK.read();
|
||||
GlobalReadGuard { globals, guard }
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the PHP executor globals.
|
||||
///
|
||||
/// The executor globals are guarded by a RwLock. There can be multiple
|
||||
/// immutable references at one time but only ever one mutable reference.
|
||||
/// Attempting to retrieve the globals while already holding the global
|
||||
/// guard will lead to a deadlock. Dropping the globals guard will release
|
||||
/// the lock.
|
||||
pub fn get_mut() -> GlobalWriteGuard<Self> {
|
||||
// SAFETY: PHP executor globals are statically declared therefore should never
|
||||
// return an invalid pointer.
|
||||
let globals = unsafe { ext_php_rs_sapi_globals().as_mut() }
|
||||
.expect("Static executor globals were invalid");
|
||||
let guard = SAPI_LOCK.write();
|
||||
GlobalWriteGuard { globals, guard }
|
||||
}
|
||||
}
|
||||
|
||||
impl SapiModule {
|
||||
/// Returns a reference to the PHP SAPI module.
|
||||
///
|
||||
/// The executor globals are guarded by a RwLock. There can be multiple
|
||||
/// immutable references at one time but only ever one mutable reference.
|
||||
/// Attempting to retrieve the globals while already holding the global
|
||||
/// guard will lead to a deadlock. Dropping the globals guard will release
|
||||
/// the lock.
|
||||
pub fn get() -> GlobalReadGuard<Self> {
|
||||
// SAFETY: PHP executor globals are statically declared therefore should never
|
||||
// return an invalid pointer.
|
||||
let globals = unsafe { ext_php_rs_sapi_module().as_ref() }
|
||||
.expect("Static executor globals were invalid");
|
||||
let guard = SAPI_MODULE_LOCK.read();
|
||||
GlobalReadGuard { globals, guard }
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the PHP executor globals.
|
||||
///
|
||||
/// The executor globals are guarded by a RwLock. There can be multiple
|
||||
/// immutable references at one time but only ever one mutable reference.
|
||||
/// Attempting to retrieve the globals while already holding the global
|
||||
/// guard will lead to a deadlock. Dropping the globals guard will release
|
||||
/// the lock.
|
||||
pub fn get_mut() -> GlobalWriteGuard<Self> {
|
||||
// SAFETY: PHP executor globals are statically declared therefore should never
|
||||
// return an invalid pointer.
|
||||
let globals = unsafe { ext_php_rs_sapi_module().as_mut() }
|
||||
.expect("Static executor globals were invalid");
|
||||
let guard = SAPI_MODULE_LOCK.write();
|
||||
GlobalWriteGuard { globals, guard }
|
||||
}
|
||||
}
|
||||
|
||||
/// Stores global variables used in the PHP executor.
|
||||
@ -430,6 +556,18 @@ static PROCESS_GLOBALS_LOCK: RwLock<()> = const_rwlock(());
|
||||
static SAPI_GLOBALS_LOCK: RwLock<()> = const_rwlock(());
|
||||
static FILE_GLOBALS_LOCK: RwLock<()> = const_rwlock(());
|
||||
|
||||
/// SAPI globals rwlock.
|
||||
///
|
||||
/// PHP provides no indication if the executor globals are being accessed so
|
||||
/// this is only effective on the Rust side.
|
||||
static SAPI_LOCK: RwLock<()> = const_rwlock(());
|
||||
|
||||
/// SAPI globals rwlock.
|
||||
///
|
||||
/// PHP provides no indication if the executor globals are being accessed so
|
||||
/// this is only effective on the Rust side.
|
||||
static SAPI_MODULE_LOCK: RwLock<()> = const_rwlock(());
|
||||
|
||||
/// Wrapper guard that contains a reference to a given type `T`. Dropping a
|
||||
/// guard releases the lock on the relevant rwlock.
|
||||
pub struct GlobalReadGuard<T: 'static> {
|
||||
|
@ -238,6 +238,7 @@ impl ZendObjectHandlers {
|
||||
let mut zv = Zval::new();
|
||||
val.get(self_, &mut zv)?;
|
||||
|
||||
#[allow(clippy::unnecessary_mut_passed)]
|
||||
if zend_is_true(&mut zv) == 1 {
|
||||
return Ok(1);
|
||||
}
|
||||
|
58
src/zend/ini_entry_def.rs
Normal file
58
src/zend/ini_entry_def.rs
Normal file
@ -0,0 +1,58 @@
|
||||
//! Builder for creating inis and methods in PHP.
|
||||
//! See <https://www.phpinternalsbook.com/php7/extensions_design/ini_settings.html> for details.
|
||||
|
||||
use std::{ffi::CString, os::raw::c_char, ptr};
|
||||
|
||||
use crate::{ffi::zend_ini_entry_def, ffi::zend_register_ini_entries, flags::IniEntryPermission};
|
||||
|
||||
/// A Zend ini entry definition.
|
||||
///
|
||||
/// To register ini definitions for extensions, the IniEntryDef builder should
|
||||
/// be used. Ini entries should be registered in your module's startup_function
|
||||
/// via `IniEntryDef::register(Vec<IniEntryDef>)`.
|
||||
pub type IniEntryDef = zend_ini_entry_def;
|
||||
|
||||
impl IniEntryDef {
|
||||
/// Returns an empty ini entry, signifying the end of a ini list.
|
||||
pub fn new(name: String, default_value: String, permission: IniEntryPermission) -> Self {
|
||||
let mut template = Self::end();
|
||||
let name = CString::new(name).expect("Unable to create CString from name");
|
||||
let value = CString::new(default_value).expect("Unable to create CString from value");
|
||||
template.name_length = name.as_bytes().len() as _;
|
||||
template.name = name.into_raw();
|
||||
template.value_length = value.as_bytes().len() as _;
|
||||
template.value = value.into_raw();
|
||||
template.modifiable = IniEntryPermission::PerDir.bits() as _;
|
||||
template.modifiable = permission.bits() as _;
|
||||
template
|
||||
}
|
||||
|
||||
/// Returns an empty ini entry def, signifying the end of a ini list.
|
||||
pub fn end() -> Self {
|
||||
Self {
|
||||
name: ptr::null() as *const c_char,
|
||||
on_modify: None,
|
||||
mh_arg1: std::ptr::null_mut(),
|
||||
mh_arg2: std::ptr::null_mut(),
|
||||
mh_arg3: std::ptr::null_mut(),
|
||||
value: std::ptr::null_mut(),
|
||||
displayer: None,
|
||||
modifiable: 0,
|
||||
value_length: 0,
|
||||
name_length: 0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts the ini entry into a raw and pointer, releasing it to the
|
||||
/// C world.
|
||||
pub fn into_raw(self) -> *mut Self {
|
||||
Box::into_raw(Box::new(self))
|
||||
}
|
||||
|
||||
pub fn register(mut entries: Vec<Self>, module_number: i32) {
|
||||
entries.push(Self::end());
|
||||
let entries = Box::into_raw(entries.into_boxed_slice()) as *const Self;
|
||||
|
||||
unsafe { zend_register_ini_entries(entries, module_number) };
|
||||
}
|
||||
}
|
@ -8,14 +8,20 @@ mod function;
|
||||
mod globals;
|
||||
mod handlers;
|
||||
mod linked_list;
|
||||
mod ini_entry_def;
|
||||
mod module;
|
||||
mod try_catch;
|
||||
|
||||
use crate::{error::Result, ffi::php_printf};
|
||||
use crate::{
|
||||
error::Result,
|
||||
ffi::{php_printf, sapi_module},
|
||||
};
|
||||
use std::ffi::CString;
|
||||
|
||||
pub use _type::ZendType;
|
||||
pub use class::ClassEntry;
|
||||
pub use ex::ExecuteData;
|
||||
pub use function::Function;
|
||||
pub use function::FunctionEntry;
|
||||
pub use globals::ExecutorGlobals;
|
||||
pub use globals::FileGlobals;
|
||||
@ -23,7 +29,14 @@ pub use globals::ProcessGlobals;
|
||||
pub use globals::SapiGlobals;
|
||||
pub use handlers::ZendObjectHandlers;
|
||||
pub use linked_list::ZendLinkedList;
|
||||
pub use globals::SapiGlobals;
|
||||
pub use globals::SapiModule;
|
||||
pub use handlers::ZendObjectHandlers;
|
||||
pub use ini_entry_def::IniEntryDef;
|
||||
pub use module::ModuleEntry;
|
||||
#[cfg(feature = "embed")]
|
||||
pub(crate) use try_catch::panic_wrapper;
|
||||
pub use try_catch::{bailout, try_catch};
|
||||
|
||||
// Used as the format string for `php_printf`.
|
||||
const FORMAT_STR: &[u8] = b"%s\0";
|
||||
@ -47,3 +60,9 @@ pub fn printf(message: &str) -> Result<()> {
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get the name of the SAPI module.
|
||||
pub fn php_sapi_name() -> String {
|
||||
let c_str = unsafe { std::ffi::CStr::from_ptr(sapi_module.name) };
|
||||
c_str.to_str().expect("Unable to parse CStr").to_string()
|
||||
}
|
||||
|
166
src/zend/try_catch.rs
Normal file
166
src/zend/try_catch.rs
Normal file
@ -0,0 +1,166 @@
|
||||
use crate::ffi::{ext_php_rs_zend_bailout, ext_php_rs_zend_try_catch};
|
||||
use std::ffi::c_void;
|
||||
use std::panic::{catch_unwind, resume_unwind, RefUnwindSafe};
|
||||
use std::ptr::null_mut;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct CatchError;
|
||||
|
||||
pub(crate) unsafe extern "C" fn panic_wrapper<R, F: FnMut() -> R + RefUnwindSafe>(
|
||||
ctx: *const c_void,
|
||||
) -> *const c_void {
|
||||
// we try to catch panic here so we correctly shutdown php if it happens
|
||||
// mandatory when we do assert on test as other test would not run correctly
|
||||
let panic = catch_unwind(|| (*(ctx as *mut F))());
|
||||
|
||||
Box::into_raw(Box::new(panic)) as *mut c_void
|
||||
}
|
||||
|
||||
/// PHP propose a try catch mechanism in C using setjmp and longjmp (bailout)
|
||||
/// It store the arg of setjmp into the bailout field of the global executor
|
||||
/// If a bailout is triggered, the executor will jump to the setjmp and restore
|
||||
/// the previous setjmp
|
||||
///
|
||||
/// try_catch allow to use this mechanism
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Ok(R)` - The result of the function
|
||||
/// * `Err(CatchError)` - A bailout occurred during the execution
|
||||
pub fn try_catch<R, F: FnMut() -> R + RefUnwindSafe>(func: F) -> Result<R, CatchError> {
|
||||
let mut panic_ptr = null_mut();
|
||||
let has_bailout = unsafe {
|
||||
ext_php_rs_zend_try_catch(
|
||||
panic_wrapper::<R, F>,
|
||||
&func as *const F as *const c_void,
|
||||
(&mut panic_ptr) as *mut *mut c_void,
|
||||
)
|
||||
};
|
||||
|
||||
let panic = panic_ptr as *mut std::thread::Result<R>;
|
||||
|
||||
// can be null if there is a bailout
|
||||
if panic.is_null() || has_bailout {
|
||||
return Err(CatchError);
|
||||
}
|
||||
|
||||
match unsafe { *Box::from_raw(panic as *mut std::thread::Result<R>) } {
|
||||
Ok(r) => Ok(r),
|
||||
Err(err) => {
|
||||
// we resume the panic here so it can be catched correctly by the test framework
|
||||
resume_unwind(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Trigger a bailout
|
||||
///
|
||||
/// This function will stop the execution of the current script
|
||||
/// and jump to the last try catch block
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// This function is unsafe because it can cause memory leaks
|
||||
/// Since it will jump to the last try catch block, it will not call the
|
||||
/// destructor of the current scope
|
||||
///
|
||||
/// When using this function you should ensure that all the memory allocated in
|
||||
/// the current scope is released
|
||||
pub unsafe fn bailout() -> ! {
|
||||
ext_php_rs_zend_bailout();
|
||||
}
|
||||
|
||||
#[cfg(feature = "embed")]
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::embed::Embed;
|
||||
use crate::zend::{bailout, try_catch};
|
||||
use std::ptr::null_mut;
|
||||
|
||||
#[test]
|
||||
fn test_catch() {
|
||||
Embed::run(|| {
|
||||
let catch = try_catch(|| {
|
||||
unsafe {
|
||||
bailout();
|
||||
}
|
||||
|
||||
#[allow(unreachable_code)]
|
||||
{
|
||||
assert!(false);
|
||||
}
|
||||
});
|
||||
|
||||
assert!(catch.is_err());
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_no_catch() {
|
||||
Embed::run(|| {
|
||||
let catch = try_catch(|| {
|
||||
assert!(true);
|
||||
});
|
||||
|
||||
assert!(catch.is_ok());
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bailout() {
|
||||
Embed::run(|| {
|
||||
unsafe {
|
||||
bailout();
|
||||
}
|
||||
|
||||
#[allow(unreachable_code)]
|
||||
{
|
||||
assert!(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn test_panic() {
|
||||
Embed::run(|| {
|
||||
let _ = try_catch(|| {
|
||||
panic!("should panic");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_return() {
|
||||
let foo = Embed::run(|| {
|
||||
let result = try_catch(|| {
|
||||
return "foo";
|
||||
});
|
||||
|
||||
assert!(result.is_ok());
|
||||
|
||||
result.unwrap()
|
||||
});
|
||||
|
||||
assert_eq!(foo, "foo");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_memory_leak() {
|
||||
let mut ptr = null_mut();
|
||||
|
||||
let _ = try_catch(|| {
|
||||
let mut result = "foo".to_string();
|
||||
ptr = &mut result;
|
||||
|
||||
unsafe {
|
||||
bailout();
|
||||
}
|
||||
});
|
||||
|
||||
// Check that the string is never released
|
||||
let result = unsafe { &*ptr as &str };
|
||||
|
||||
assert_eq!(result, "foo");
|
||||
}
|
||||
}
|
44
tests/module.rs
Normal file
44
tests/module.rs
Normal file
@ -0,0 +1,44 @@
|
||||
#![cfg_attr(windows, feature(abi_vectorcall))]
|
||||
extern crate ext_php_rs;
|
||||
|
||||
#[cfg(feature = "embed")]
|
||||
use ext_php_rs::embed::Embed;
|
||||
#[cfg(feature = "embed")]
|
||||
use ext_php_rs::ffi::zend_register_module_ex;
|
||||
use ext_php_rs::prelude::*;
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "embed")]
|
||||
fn test_module() {
|
||||
Embed::run(|| {
|
||||
// Allow to load the module
|
||||
unsafe { zend_register_module_ex(get_module()) };
|
||||
|
||||
let result = Embed::eval("$foo = hello_world('foo');");
|
||||
|
||||
assert!(result.is_ok());
|
||||
|
||||
let zval = result.unwrap();
|
||||
|
||||
assert!(zval.is_string());
|
||||
|
||||
let string = zval.string().unwrap();
|
||||
|
||||
assert_eq!(string.to_string(), "Hello, foo!");
|
||||
});
|
||||
}
|
||||
|
||||
/// Gives you a nice greeting!
|
||||
///
|
||||
/// @param string $name Your name.
|
||||
///
|
||||
/// @return string Nice greeting!
|
||||
#[php_function]
|
||||
pub fn hello_world(name: String) -> String {
|
||||
format!("Hello, {}!", name)
|
||||
}
|
||||
|
||||
#[php_module]
|
||||
pub fn module(module: ModuleBuilder) -> ModuleBuilder {
|
||||
module
|
||||
}
|
@ -55,4 +55,11 @@ impl<'a> PHPProvider<'a> for Provider {
|
||||
fn get_defines(&self) -> Result<Vec<(&'static str, &'static str)>> {
|
||||
Ok(vec![])
|
||||
}
|
||||
|
||||
fn print_extra_link_args(&self) -> Result<()> {
|
||||
#[cfg(feature = "embed")]
|
||||
println!("cargo:rustc-link-lib=php");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user