• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

extphprs / ext-php-rs / 21747316974

06 Feb 2026 10:28AM UTC coverage: 34.83% (+0.4%) from 34.453%
21747316974

Pull #671

github

web-flow
Merge 23971eb43 into b40709753
Pull Request #671: feat: eval PHP code from files

33 of 35 new or added lines in 1 file covered. (94.29%)

2118 of 6081 relevant lines covered (34.83%)

23.5 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

94.29
/src/php_eval.rs
1
//! Execute embedded PHP code within a running PHP extension.
2
//!
3
//! This module provides a way to compile and execute PHP code that has been
4
//! embedded into the extension binary at compile time using `include_bytes!`.
5
//!
6
//! Uses `zend_compile_string` + `zend_execute` (not `zend_eval_string`)
7
//! to avoid security scanner false positives and compatibility issues
8
//! with hardened PHP configurations.
9
//!
10
//! # Example
11
//!
12
//! ```rust,ignore
13
//! use ext_php_rs::php_eval;
14
//!
15
//! const SETUP: &[u8] = include_bytes!("../php/setup.php");
16
//!
17
//! php_eval::execute(SETUP).expect("failed to execute embedded PHP");
18
//! ```
19

20
use crate::ffi;
21
use crate::types::ZendStr;
22
use crate::zend::try_catch;
23
use std::panic::AssertUnwindSafe;
24

25
/// Errors that can occur when executing embedded PHP code.
26
#[derive(Debug)]
27
pub enum PhpEvalError {
28
    /// PHP failed to compile the code (syntax error).
29
    CompilationFailed,
30
    /// The code executed but threw an unhandled exception.
31
    ExecutionFailed,
32
    /// A PHP fatal error (bailout) occurred during execution.
33
    Bailout,
34
}
35

36
/// Execute embedded PHP code within the running PHP engine.
37
///
38
/// The code can include a `<?php` opening tag -- it is stripped before
39
/// compilation. The C wrapper uses `ZEND_COMPILE_POSITION_AFTER_OPEN_TAG`
40
/// (on PHP 8.2+) so the scanner starts directly in PHP mode.
41
///
42
/// Error reporting is suppressed during execution and restored afterward,
43
/// matching the pattern used by production PHP extensions like Blackfire.
44
///
45
/// # Arguments
46
///
47
/// * `code` - Raw PHP source bytes, typically from `include_bytes!`.
48
///
49
/// # Errors
50
///
51
/// Returns [`PhpEvalError`] if compilation fails, an exception is thrown,
52
/// or a fatal error occurs.
53
pub fn execute(code: &[u8]) -> Result<(), PhpEvalError> {
8✔
54
    let code = strip_bom(code);
24✔
55
    let code = strip_php_open_tag(code);
24✔
56

57
    if code.is_empty() {
16✔
58
        return Ok(());
1✔
59
    }
60

61
    let source = ZendStr::new(code, false);
21✔
62

63
    let result = try_catch(AssertUnwindSafe(|| unsafe {
14✔
64
        let eg = ffi::ext_php_rs_executor_globals();
14✔
65
        let prev_error_reporting = (*eg).error_reporting;
14✔
66
        // Suppress error reporting so compilation warnings from embedded
67
        // code don't bubble up to the application's error handler.
68
        (*eg).error_reporting = 0;
7✔
69

70
        let op_array = ffi::ext_php_rs_zend_compile_string(
14✔
71
            source.as_ptr().cast_mut(),
14✔
72
            c"embedded_php".as_ptr(),
14✔
73
        );
74

75
        if op_array.is_null() {
14✔
76
            (*eg).error_reporting = prev_error_reporting;
1✔
77
            return Err(PhpEvalError::CompilationFailed);
1✔
78
        }
79

80
        ffi::ext_php_rs_zend_execute(op_array);
12✔
81

82
        (*eg).error_reporting = prev_error_reporting;
6✔
83

84
        if !(*eg).exception.is_null() {
6✔
NEW
85
            return Err(PhpEvalError::ExecutionFailed);
×
86
        }
87

88
        Ok(())
6✔
89
    }));
90

91
    match result {
7✔
NEW
92
        Err(_) => Err(PhpEvalError::Bailout),
×
93
        Ok(inner) => inner,
14✔
94
    }
95
}
96

97
fn strip_bom(code: &[u8]) -> &[u8] {
11✔
98
    if code.starts_with(&[0xEF, 0xBB, 0xBF]) {
33✔
99
        &code[3..]
2✔
100
    } else {
101
        code
9✔
102
    }
103
}
104

105
fn strip_php_open_tag(code: &[u8]) -> &[u8] {
8✔
106
    let trimmed = match code.iter().position(|b| !b.is_ascii_whitespace()) {
37✔
107
        Some(pos) => &code[pos..],
14✔
108
        None => return code,
1✔
109
    };
110

111
    if trimmed.starts_with(b"<?php") {
21✔
112
        trimmed[5..].trim_ascii_start()
3✔
113
    } else {
114
        code
4✔
115
    }
116
}
117

118
#[cfg(feature = "embed")]
119
#[cfg(test)]
120
mod tests {
121
    #![allow(clippy::unwrap_used)]
122
    use super::*;
123
    use crate::embed::Embed;
124

125
    #[test]
126
    fn test_execute_simple_code() {
127
        Embed::run(|| {
128
            let result = execute(b"$x = 1 + 2;");
129
            assert!(result.is_ok());
130
        });
131
    }
132

133
    #[test]
134
    fn test_execute_with_php_open_tag() {
135
        Embed::run(|| {
136
            let result = execute(b"<?php $x = 42;");
137
            assert!(result.is_ok());
138
        });
139
    }
140

141
    #[test]
142
    fn test_execute_with_php_open_tag_and_newline() {
143
        Embed::run(|| {
144
            let result = execute(b"<?php\n$x = 42;");
145
            assert!(result.is_ok());
146
        });
147
    }
148

149
    #[test]
150
    fn test_execute_compilation_error() {
151
        Embed::run(|| {
152
            let result = execute(b"this is not valid php {{{");
153
            assert!(matches!(result, Err(PhpEvalError::CompilationFailed)));
154
        });
155
    }
156

157
    #[test]
158
    fn test_execute_with_bom() {
159
        Embed::run(|| {
160
            let mut code = vec![0xEF, 0xBB, 0xBF];
161
            code.extend_from_slice(b"$x = 'bom_test';");
162
            let result = execute(&code);
163
            assert!(result.is_ok());
164
        });
165
    }
166

167
    #[test]
168
    fn test_execute_defines_variable() {
169
        Embed::run(|| {
170
            let result = execute(b"$embed_test = 'hello from embedded php';");
171
            assert!(result.is_ok());
172

173
            let val = Embed::eval("$embed_test;");
174
            assert!(val.is_ok());
175
            assert_eq!(val.unwrap().string().unwrap(), "hello from embedded php");
176
        });
177
    }
178

179
    #[test]
180
    fn test_execute_empty_code() {
181
        Embed::run(|| {
182
            let result = execute(b"");
183
            assert!(result.is_ok());
184
        });
185
    }
186

187
    #[test]
188
    fn test_execute_include_bytes_pattern() {
189
        Embed::run(|| {
190
            let code: &[u8] = b"<?php\n\
191
                $embedded_value = 42;\n\
192
                define('EMBEDDED_CONST', true);\n";
193
            let result = execute(code);
194
            assert!(result.is_ok());
195
        });
196
    }
197

198
    #[test]
199
    fn test_strip_bom() {
200
        let with_bom = &[0xEF, 0xBB, 0xBF, b'h', b'i'];
201
        assert_eq!(super::strip_bom(with_bom), b"hi");
202

203
        let without_bom = b"hello";
204
        assert_eq!(super::strip_bom(without_bom), b"hello");
205

206
        assert_eq!(super::strip_bom(b""), b"");
207
    }
208
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc