diff --git a/guide/src/SUMMARY.md b/guide/src/SUMMARY.md index b3fa7fc6b..b4756e9bf 100644 --- a/guide/src/SUMMARY.md +++ b/guide/src/SUMMARY.md @@ -35,6 +35,7 @@ - [`ZvalConvert`](./macros/zval_convert.md) - [`Attributes`](./macros/php.md) - [Exceptions](./exceptions.md) +- [Output](./output.md) - [INI Settings](./ini-settings.md) # Advanced Topics diff --git a/guide/src/output.md b/guide/src/output.md new file mode 100644 index 000000000..9618021fd --- /dev/null +++ b/guide/src/output.md @@ -0,0 +1,112 @@ +# Output + +`ext-php-rs` provides several macros and functions for writing output to PHP's +stdout and stderr streams. These are essential when your extension needs to +produce output that integrates with PHP's output buffering system. + +## Text Output + +For regular text output (strings without NUL bytes), use the `php_print!` and +`php_println!` macros. These work similarly to Rust's `print!` and `println!` +macros. + +### `php_print!` + +Prints to PHP's standard output without a trailing newline. + +```rust,ignore +use ext_php_rs::prelude::*; + +#[php_function] +pub fn greet(name: &str) { + php_print!("Hello, {}!", name); +} +``` + +### `php_println!` + +Prints to PHP's standard output with a trailing newline. + +```rust,ignore +use ext_php_rs::prelude::*; + +#[php_function] +pub fn greet(name: &str) { + php_println!("Hello, {}!", name); +} +``` + +> **Note:** `php_print!` and `php_println!` will panic if the string contains +> NUL bytes (`\0`). For binary-safe output, use `php_write!` instead. + +## Binary-Safe Output + +When working with binary data that may contain NUL bytes, use the binary-safe +output functions. These are essential for outputting raw bytes, binary file +contents, or any data that might contain `\0` characters. + +### `php_write!` + +Writes binary data to PHP's standard output. This macro is binary-safe and can +handle data containing NUL bytes. It uses the SAPI module's `ub_write` function. + +```rust,ignore +use ext_php_rs::prelude::*; + +#[php_function] +pub fn output_binary() -> i64 { + // Write a byte literal + php_write!(b"Hello World"); + + // Write binary data with NUL bytes (would panic with php_print!) + let bytes_written = php_write!(b"Hello\x00World"); + + // Write a byte slice + let data: &[u8] = &[0x48, 0x65, 0x6c, 0x6c, 0x6f]; // "Hello" + php_write!(data); + + bytes_written as i64 +} +``` + +The macro returns the number of bytes written, which can be useful for verifying +that all data was output successfully. + +## Function API + +In addition to macros, you can use the underlying functions directly: + +| Function | Target | Binary-Safe | Description | +|----------|--------|-------------|-------------| +| `zend::printf()` | stdout | No | Printf-style output (used by `php_print!`) | +| `zend::write()` | stdout | Yes | Binary-safe stdout output | + +### Example using functions directly + +```rust,ignore +use ext_php_rs::zend::write; + +fn output_data(data: &[u8]) { + let bytes_written = write(data); + if bytes_written != data.len() { + eprintln!("Warning: incomplete write"); + } +} +``` + +## Comparison + +| Macro/Function | Binary-Safe | Supports Formatting | Target | +|----------------|-------------|---------------------|--------| +| `php_print!` | No | Yes | stdout | +| `php_println!` | No | Yes | stdout | +| `php_write!` | Yes | No | stdout | + +## When to Use Each + +- **`php_print!` / `php_println!`**: Use for text output with format strings, + similar to Rust's `print!` and `println!`. Best for human-readable messages. + +- **`php_write!`**: Use when outputting binary data, file contents, or any data + that might contain NUL bytes. Also useful when you need to know exactly how + many bytes were written. diff --git a/src/embed/mod.rs b/src/embed/mod.rs index 97df7e0aa..b96d088b3 100644 --- a/src/embed/mod.rs +++ b/src/embed/mod.rs @@ -297,4 +297,36 @@ mod tests { assert!(result.unwrap_err().is_bailout()); }); } + + #[test] + fn test_php_write() { + use crate::zend::write; + + Embed::run(|| { + // Test write function with regular data + let bytes_written = write(b"Hello"); + assert_eq!(bytes_written, 5); + + // Test write function with binary data containing NUL bytes + let bytes_written = write(b"Hello\x00World"); + assert_eq!(bytes_written, 11); + + // Test php_write! macro with byte literal + let bytes_written = php_write!(b"Test"); + assert_eq!(bytes_written, 4); + + // Test php_write! macro with binary data containing NUL bytes + let bytes_written = php_write!(b"Binary\x00Data\x00Here"); + assert_eq!(bytes_written, 16); + + // Test php_write! macro with byte slice variable + let data: &[u8] = &[0x48, 0x65, 0x6c, 0x6c, 0x6f]; // "Hello" + let bytes_written = php_write!(data); + assert_eq!(bytes_written, 5); + + // Test empty data + let bytes_written = write(b""); + assert_eq!(bytes_written, 0); + }); + } } diff --git a/src/lib.rs b/src/lib.rs index 3da2710b0..1f9c53a53 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -54,6 +54,7 @@ pub mod prelude { pub use crate::php_enum; pub use crate::php_print; pub use crate::php_println; + pub use crate::php_write; pub use crate::types::ZendCallable; pub use crate::{ ZvalConvert, php_class, php_const, php_extern, php_function, php_impl, php_interface, diff --git a/src/macros.rs b/src/macros.rs index 88d6bf825..67a63a725 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -425,3 +425,36 @@ macro_rules! php_println { $crate::php_print!(concat!($fmt, "\n"), $($arg)*); }; } + +/// Writes binary data to the PHP standard output. +/// +/// Unlike [`php_print!`], this macro is binary-safe and can handle data +/// containing `NUL` bytes. It uses the SAPI module's `ub_write` function. +/// +/// # Arguments +/// +/// * `$data` - A byte slice (`&[u8]`) or byte literal (`b"..."`) to write. +/// +/// # Returns +/// +/// The number of bytes written. +/// +/// # Examples +/// +/// ```ignore +/// use ext_php_rs::php_write; +/// +/// // Write a byte literal +/// php_write!(b"Hello World"); +/// +/// // Write binary data with NUL bytes (would panic with php_print!) +/// php_write!(b"Hello\x00World"); +/// +/// // Write a byte slice +/// let data: &[u8] = &[0x48, 0x65, 0x6c, 0x6c, 0x6f]; +/// php_write!(data); +/// ``` +#[macro_export] +macro_rules! php_write { + ($data: expr) => {{ $crate::zend::write($data) }}; +} diff --git a/src/zend/mod.rs b/src/zend/mod.rs index 02d44b67e..5dcfc8c05 100644 --- a/src/zend/mod.rs +++ b/src/zend/mod.rs @@ -18,6 +18,7 @@ use crate::{ ffi::{php_printf, sapi_module}, }; use std::ffi::CString; +use std::os::raw::c_char; pub use _type::ZendType; pub use class::ClassEntry; @@ -62,6 +63,43 @@ pub fn printf(message: &str) -> Result<()> { Ok(()) } +/// Writes binary data to PHP's output stream (stdout). +/// +/// Unlike [`printf`], this function is binary-safe and can handle data +/// containing NUL bytes. It uses the SAPI module's `ub_write` function +/// which accepts a pointer and length, allowing arbitrary binary data. +/// +/// Also see the [`php_write!`] macro. +/// +/// # Arguments +/// +/// * `data` - The binary data to write to stdout. +/// +/// # Returns +/// +/// The number of bytes written, or 0 if the SAPI's `ub_write` function +/// is not available. +/// +/// # Example +/// +/// ```ignore +/// use ext_php_rs::zend::write; +/// +/// // Write binary data including NUL bytes +/// let data = b"Hello\x00World"; +/// write(data); +/// ``` +#[must_use] +pub fn write(data: &[u8]) -> usize { + unsafe { + if let Some(ub_write) = sapi_module.ub_write { + ub_write(data.as_ptr().cast::(), data.len()) + } else { + 0 + } + } +} + /// Get the name of the SAPI module. /// /// # Panics