Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions guide/src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
112 changes: 112 additions & 0 deletions guide/src/output.md
Original file line number Diff line number Diff line change
@@ -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.
32 changes: 32 additions & 0 deletions src/embed/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});
}
}
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
33 changes: 33 additions & 0 deletions src/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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) }};
}
38 changes: 38 additions & 0 deletions src/zend/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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::<c_char>(), data.len())
} else {
0
}
}
}

/// Get the name of the SAPI module.
///
/// # Panics
Expand Down
Loading