-
Notifications
You must be signed in to change notification settings - Fork 66
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(embed): add embed features, add test example which run php insid…
…e it (#270) * feat(embed): add embed features, add test example which run php inside it * feat(embed): use a guard to prevent running in parallel * chore(ci): update actions to not build and test with embed, add a specific build for embed testing * feat(embed): correcly start / shutdown embed api * chore(ci): use stable for rust in embed test * feat(embed): add documentation, manage potential errors
- Loading branch information
Showing
15 changed files
with
354 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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" ] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -35,6 +35,7 @@ zip = "0.6" | |
|
||
[features] | ||
closure = [] | ||
embed = [] | ||
|
||
[workspace] | ||
members = [ | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
#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) { | ||
PHP_EMBED_START_BLOCK(argc, argv) | ||
|
||
callback(ctx); | ||
|
||
PHP_EMBED_END_BLOCK() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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), | ||
ctx: *const c_void, | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,221 @@ | ||
//! 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::ExecutorGlobals; | ||
use parking_lot::{const_rwlock, RwLock}; | ||
use std::ffi::{c_char, c_void, CString, NulError}; | ||
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, | ||
} | ||
|
||
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()); | ||
} | ||
|
||
if unsafe { php_execute_script(&mut file_handle) } { | ||
Ok(()) | ||
} else { | ||
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 | ||
/// | ||
/// # 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<F: Fn()>(func: F) { | ||
// @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(); | ||
|
||
unsafe extern "C" fn wrapper<F: Fn()>(ctx: *const c_void) { | ||
(*(ctx as *const F))(); | ||
} | ||
|
||
unsafe { | ||
ext_php_rs_embed_callback( | ||
0, | ||
null_mut(), | ||
wrapper::<F>, | ||
&func as *const F as *const c_void, | ||
); | ||
} | ||
} | ||
|
||
/// 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(); | ||
|
||
// this eval is very limited as it only allow simple code, it's the same eval used by php -r | ||
let exec_result = unsafe { | ||
zend_eval_string( | ||
cstr.as_ptr() as *const c_char, | ||
&mut result, | ||
b"run\0".as_ptr() as *const _, | ||
) | ||
}; | ||
|
||
let exception = ExecutorGlobals::take_exception(); | ||
|
||
if exec_result != ZEND_RESULT_CODE_SUCCESS { | ||
Err(EmbedError::ExecuteError(exception)) | ||
} else { | ||
Ok(result) | ||
} | ||
} | ||
} | ||
|
||
#[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()); | ||
}); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
<?php | ||
|
||
throw new \RuntimeException('This is a test exception'); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
<?php | ||
|
||
class Test { | ||
public function __construct() {} | ||
} | ||
|
||
$foo = new Test(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.