|
| 1 | +# Hello World |
| 2 | + |
| 3 | +## Project Setup |
| 4 | + |
| 5 | +We will start by creating a new Rust library crate: |
| 6 | + |
| 7 | +```sh |
| 8 | +$ cargo new hello_world --lib |
| 9 | +$ cd hello_world |
| 10 | +``` |
| 11 | + |
| 12 | +### `Cargo.toml` |
| 13 | + |
| 14 | +Let's set up our crate by adding `ext-php-rs` as a dependency and setting the |
| 15 | +crate type to `cdylib`. Update the `Cargo.toml` to look something like so: |
| 16 | + |
| 17 | +```toml |
| 18 | +[package] |
| 19 | +name = "hello_world" |
| 20 | +version = "0.1.0" |
| 21 | +edition = "2018" |
| 22 | + |
| 23 | +[lib] |
| 24 | +crate-type = ["cdylib"] |
| 25 | + |
| 26 | +[dependencies] |
| 27 | +ext-php-rs = "*" |
| 28 | + |
| 29 | +[profile.release] |
| 30 | +strip = "debuginfo" |
| 31 | +``` |
| 32 | + |
| 33 | +### `.cargo/config.toml` |
| 34 | + |
| 35 | +When compiling for Linux and macOS, we do not link directly to PHP, rather PHP |
| 36 | +will dynamically load the library. We need to tell the linker it's ok to have |
| 37 | +undefined symbols (as they will be resolved when loaded by PHP). |
| 38 | + |
| 39 | +On Windows, we also need to switch to using the `rust-lld` linker. |
| 40 | + |
| 41 | +> Microsoft Visual C++'s `link.exe` is supported, however you may run into |
| 42 | +> issues if your linker is not compatible with the linker used to compile PHP. |
| 43 | +
|
| 44 | +We do this by creating a Cargo config file in `.cargo/config.toml` with the |
| 45 | +following contents: |
| 46 | + |
| 47 | +```toml |
| 48 | +{{#include ../../../.cargo/config.toml}} |
| 49 | +``` |
| 50 | + |
| 51 | +## Writing our extension |
| 52 | + |
| 53 | +### `src/lib.rs` |
| 54 | + |
| 55 | +Let's actually write the extension code now. We start by importing the |
| 56 | +`ext-php-rs` prelude, which contains most of the imports required to make a |
| 57 | +basic extension. We will then write our basic `hello_world` function, which will |
| 58 | +take a string argument for the callers name, and we will return another string. |
| 59 | +Finally, we write a `get_module` function which is used by PHP to find out about |
| 60 | +your module. The `#[php_module]` attribute automatically registers your new |
| 61 | +function so we don't need to do anything except return the `ModuleBuilder` that |
| 62 | +we were given. |
| 63 | + |
| 64 | +We also need to enable the `abi_vectorcall` feature when compiling for Windows. |
| 65 | +This is a nightly-only feature so it is recommended to use the `#[cfg_attr]` |
| 66 | +macro to not enable the feature on other operating systems. |
| 67 | + |
| 68 | +```rust,ignore |
| 69 | +#![cfg_attr(windows, feature(abi_vectorcall))] |
| 70 | +use ext_php_rs::prelude::*; |
| 71 | +
|
| 72 | +#[php_function] |
| 73 | +pub fn hello_world(name: &str) -> String { |
| 74 | + format!("Hello, {}!", name) |
| 75 | +} |
| 76 | +
|
| 77 | +#[php_module] |
| 78 | +pub fn get_module(module: ModuleBuilder) -> ModuleBuilder { |
| 79 | + module |
| 80 | +} |
| 81 | +``` |
| 82 | + |
| 83 | +## Building the extension |
| 84 | + |
| 85 | +Now let's build our extension. |
| 86 | +This is done through `cargo` like any other Rust crate. |
| 87 | + |
| 88 | +If you installed php using a package manager in the previous chapter |
| 89 | +(or if the `php` and `php-config` binaries are already in your `$PATH`), |
| 90 | +then you can just run |
| 91 | + |
| 92 | +```sh |
| 93 | +cargo build |
| 94 | +``` |
| 95 | + |
| 96 | +If you have multiple PHP versions in your PATH, or your installation |
| 97 | +resides in a custom location, you can use the following environment variables: |
| 98 | + |
| 99 | +```sh |
| 100 | +# explicitly specifies the path to the PHP executable: |
| 101 | +export PHP=/path/to/php |
| 102 | +# explicitly specifies the path to the php-config executable: |
| 103 | +export PHP_CONFIG=/path/to/php-config |
| 104 | +``` |
| 105 | + |
| 106 | +As an alternative, if you compiled PHP from source and installed it under |
| 107 | +it's own prefix (`configure --prefix=/my/prefix`), you can just put |
| 108 | +this prefix in front of your PATH: |
| 109 | + |
| 110 | +```sh |
| 111 | +export PATH="/my/prefix:${PATH}" |
| 112 | +``` |
| 113 | + |
| 114 | +Once you've setup these variables, you can just run |
| 115 | + |
| 116 | +```sh |
| 117 | +cargo build |
| 118 | +``` |
| 119 | + |
| 120 | +Cargo will track changes to these environment variables and rebuild the library accordingly. |
| 121 | + |
| 122 | +## Testing our extension |
| 123 | + |
| 124 | +The extension we just built is stored inside the cargo target directory: |
| 125 | +`target/debug` if you did a debug build, `target/release` for release builds. |
| 126 | + |
| 127 | +The extension file name is OS-dependent. The naming works as follows: |
| 128 | + |
| 129 | +- let `S` be the empty string |
| 130 | +- append to `S` the value of [std::env::consts::DLL_PREFIX](https://doc.rust-lang.org/std/env/consts/constant.DLL_PREFIX.html) |
| 131 | + (empty on windows, `lib` on unixes) |
| 132 | +- append to `S` the lower-snake-case version of your crate name |
| 133 | +- append to `S` the value of [std::env::consts::DLL_SUFFIX](https://doc.rust-lang.org/std/env/consts/constant.DLL_SUFFIX.html) |
| 134 | + (`.dll` on windows, `.dylib` on macOS, `.so` on other unixes). |
| 135 | +- set the filename to the value of `S` |
| 136 | + |
| 137 | +Which in our case would give us: |
| 138 | + |
| 139 | +- linux: `libhello_world.so` |
| 140 | +- macOS: `libhello_world.dylib` |
| 141 | +- windows: `hello_world.dll` |
| 142 | + |
| 143 | +Now we need a way to tell the PHP CLI binary to load our extension. |
| 144 | +There are [several ways to do that](https://www.phpinternalsbook.com/php7/build_system/building_extensions.html#loading-shared-extensions). |
| 145 | +For now we'll simply pass the `-d extension=/path/to/extension` option to the PHP CLI binary. |
| 146 | + |
| 147 | +Let's make a test script: |
| 148 | + |
| 149 | +### `test.php` |
| 150 | + |
| 151 | +```php |
| 152 | +<?php |
| 153 | + |
| 154 | +var_dump(hello_world("David")); |
| 155 | +``` |
| 156 | + |
| 157 | +And run it: |
| 158 | + |
| 159 | +```sh |
| 160 | +$ php -d extension=./target/debug/libhello_world.so test.php |
| 161 | +string(13) "Hello, David!" |
| 162 | +``` |
0 commit comments