Skip to content

Commit 12c5589

Browse files
SammyKnikic
andauthored
Add some documentation to the embed SAPI (#6856)
* Add some documentation to the embed SAPI * Apply suggestions from code review Co-authored-by: Nikita Popov <nikita.ppv@googlemail.com> * Remove superfluous sapi/ include step Co-authored-by: Nikita Popov <nikita.ppv@googlemail.com>
1 parent 916071d commit 12c5589

File tree

2 files changed

+261
-46
lines changed

2 files changed

+261
-46
lines changed

sapi/embed/README.md

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
# The embed SAPI
2+
3+
A server application programming interface (SAPI) is the entry point into the Zend Engine. The embed SAPI is a lightweight SAPI for calling into the Zend Engine from C or other languages that have C bindings.
4+
5+
## Basic Example
6+
7+
Below is a basic example in C that uses the embed SAPI to boot up the Zend Engine, start a request, and print the number of functions loaded in the function table.
8+
9+
```c
10+
/* embed_sapi_basic_example.c */
11+
12+
#include <sapi/embed/php_embed.h>
13+
14+
int main(int argc, char **argv)
15+
{
16+
/* Invokes the Zend Engine initialization phase: SAPI (SINIT), modules
17+
* (MINIT), and request (RINIT). It also opens a 'zend_try' block to catch
18+
* a zend_bailout().
19+
*/
20+
PHP_EMBED_START_BLOCK(argc, argv)
21+
22+
php_printf(
23+
"Number of functions loaded: %d\n",
24+
zend_hash_num_elements(EG(function_table))
25+
);
26+
27+
/* Close the 'zend_try' block and invoke the shutdown phase: request
28+
* (RSHUTDOWN), modules (MSHUTDOWN), and SAPI (SSHUTDOWN).
29+
*/
30+
PHP_EMBED_END_BLOCK()
31+
}
32+
```
33+
34+
To compile this, we must point the compiler to the PHP header files. The paths to the header files are listed from `php-config --includes`.
35+
36+
We must also point the linker and the runtime loader to the `libphp.so` shared lib for linking PHP (`-lphp`) which is located at `$(php-config --prefix)/lib`. So the complete command to compile ends up being:
37+
38+
```bash
39+
$ gcc \
40+
$(php-config --includes) \
41+
-L$(php-config --prefix)/lib \
42+
embed_sapi_basic_example.c \
43+
-lphp \
44+
-Wl,-rpath=$(php-config --prefix)/lib
45+
```
46+
47+
> :memo: The embed SAPI is disabled by default. In order for the above example to compile, PHP must be built with the embed SAPI enabled. To see what SAPIs are installed, run `php-config --php-sapis`. If you don't see `embed` in the list, you'll need to rebuild PHP with `./configure --enable-embed`. The PHP shared library `libphp.so` is built when the embed SAPI is enabled.
48+
49+
If all goes to plan you should be able to run the program.
50+
51+
```bash
52+
$ ./a.out
53+
Number of functions loaded: 1046
54+
```
55+
56+
## Function call example
57+
58+
The following example calls `mt_rand()` and `var_dump()`s the return value.
59+
60+
```c
61+
#include <main/php.h>
62+
#include <ext/standard/php_var.h>
63+
#include <sapi/embed/php_embed.h>
64+
65+
int main(int argc, char **argv)
66+
{
67+
PHP_EMBED_START_BLOCK(argc, argv)
68+
69+
zval retval = {0};
70+
zend_fcall_info fci = {0};
71+
zend_fcall_info_cache fci_cache = {0};
72+
73+
zend_string *func_name = zend_string_init(ZEND_STRL("mt_rand"), 0);
74+
ZVAL_STR(&fci.function_name, func_name);
75+
76+
fci.size = sizeof fci;
77+
fci.retval = &retval;
78+
79+
if (zend_call_function(&fci, &fci_cache) == SUCCESS) {
80+
php_var_dump(&retval, 1);
81+
}
82+
83+
zend_string_release(func_name);
84+
85+
PHP_EMBED_END_BLOCK()
86+
}
87+
```
88+
89+
## Execute a PHP script example
90+
91+
```php
92+
<?php
93+
94+
# example.php
95+
96+
echo 'Hello from userland!' . PHP_EOL;
97+
```
98+
99+
```c
100+
#include <sapi/embed/php_embed.h>
101+
102+
int main(int argc, char **argv)
103+
{
104+
PHP_EMBED_START_BLOCK(argc, argv)
105+
106+
zend_file_handle file_handle;
107+
zend_stream_init_filename(&file_handle, "example.php");
108+
109+
if (php_execute_script(&file_handle) == FAILURE) {
110+
php_printf("Failed to execute PHP script.\n");
111+
}
112+
113+
PHP_EMBED_END_BLOCK()
114+
}
115+
```
116+
117+
## INI defaults
118+
119+
The default value for 'error_prepend_string' is 'NULL'. The following example sets the INI default for 'error_prepend_string' to 'Embed SAPI error:'.
120+
121+
```c
122+
#include <sapi/embed/php_embed.h>
123+
124+
/* This callback is invoked as soon as the configuration hash table is
125+
* allocated so any INI settings added via this callback will have the lowest
126+
* precedence and will allow INI files to overwrite them.
127+
*/
128+
static void example_ini_defaults(HashTable *configuration_hash)
129+
{
130+
zval ini_value;
131+
ZVAL_NEW_STR(&ini_value, zend_string_init(ZEND_STRL("Embed SAPI error:"), /* persistent */ 1));
132+
zend_hash_str_update(configuration_hash, ZEND_STRL("error_prepend_string"), &ini_value);
133+
}
134+
135+
int main(int argc, char **argv)
136+
{
137+
php_embed_module.ini_defaults = example_ini_defaults;
138+
139+
PHP_EMBED_START_BLOCK(argc, argv)
140+
141+
zval retval;
142+
143+
/* Generates an error by accessing an undefined variable '$a'. */
144+
if (zend_eval_stringl(ZEND_STRL("var_dump($a);"), &retval, "example") == FAILURE) {
145+
php_printf("Failed to eval PHP.\n");
146+
}
147+
148+
PHP_EMBED_END_BLOCK()
149+
}
150+
```
151+
152+
After compiling and running, you should see:
153+
154+
```
155+
Embed SAPI error:
156+
Warning: Undefined variable $a in example on line 1
157+
NULL
158+
```
159+
160+
This default value is overwritable from INI files. We'll update one of the INI files (which can be found by running `$ php --ini`), and set `error_prepend_string="Oops!"`. We don't have to recompile the program, we can just run it again and we should see:
161+
162+
```
163+
Oops!
164+
Warning: Undefined variable $a in example on line 1
165+
NULL
166+
```

sapi/embed/php_embed.c

Lines changed: 95 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,12 @@ static int php_embed_deactivate(void)
4646
return SUCCESS;
4747
}
4848

49+
/* Here we prefer to use write(), which is unbuffered, over fwrite(), which is
50+
* buffered. Using an unbuffered write operation to stdout will ensure PHP's
51+
* output buffering feature does not compete with a SAPI output buffer and
52+
* therefore we avoid situations wherein flushing the output buffer results in
53+
* nondeterministic behavior.
54+
*/
4955
static inline size_t php_embed_single_write(const char *str, size_t str_length)
5056
{
5157
#ifdef PHP_WRITE_STDOUT
@@ -62,7 +68,10 @@ static inline size_t php_embed_single_write(const char *str, size_t str_length)
6268
#endif
6369
}
6470

65-
71+
/* SAPIs only have unbuffered write operations. This is because PHP's output
72+
* buffering feature will handle any buffering of the output and invoke the
73+
* SAPI unbuffered write operation when it flushes the buffer.
74+
*/
6675
static size_t php_embed_ub_write(const char *str, size_t str_length)
6776
{
6877
const char *ptr = str;
@@ -92,6 +101,11 @@ static void php_embed_send_header(sapi_header_struct *sapi_header, void *server_
92101
{
93102
}
94103

104+
/* The SAPI error logger that is called when the 'error_log' INI setting is not
105+
* set.
106+
*
107+
* https://www.php.net/manual/en/errorfunc.configuration.php#ini.error-log
108+
*/
95109
static void php_embed_log_message(const char *message, int syslog_type_int)
96110
{
97111
fprintf(stderr, "%s\n", message);
@@ -102,9 +116,10 @@ static void php_embed_register_variables(zval *track_vars_array)
102116
php_import_environment_variables(track_vars_array);
103117
}
104118

119+
/* Module initialization (MINIT) */
105120
static int php_embed_startup(sapi_module_struct *sapi_module)
106121
{
107-
if (php_module_startup(sapi_module, NULL, 0)==FAILURE) {
122+
if (php_module_startup(sapi_module, NULL, 0) == FAILURE) {
108123
return FAILURE;
109124
}
110125
return SUCCESS;
@@ -114,30 +129,30 @@ EMBED_SAPI_API sapi_module_struct php_embed_module = {
114129
"embed", /* name */
115130
"PHP Embedded Library", /* pretty name */
116131

117-
php_embed_startup, /* startup */
132+
php_embed_startup, /* startup */
118133
php_module_shutdown_wrapper, /* shutdown */
119134

120135
NULL, /* activate */
121-
php_embed_deactivate, /* deactivate */
136+
php_embed_deactivate, /* deactivate */
122137

123-
php_embed_ub_write, /* unbuffered write */
124-
php_embed_flush, /* flush */
138+
php_embed_ub_write, /* unbuffered write */
139+
php_embed_flush, /* flush */
125140
NULL, /* get uid */
126141
NULL, /* getenv */
127142

128143
php_error, /* error handler */
129144

130145
NULL, /* header handler */
131146
NULL, /* send headers handler */
132-
php_embed_send_header, /* send header handler */
147+
php_embed_send_header, /* send header handler */
133148

134149
NULL, /* read POST data */
135-
php_embed_read_cookies, /* read Cookies */
150+
php_embed_read_cookies, /* read Cookies */
136151

137-
php_embed_register_variables, /* register server variables */
138-
php_embed_log_message, /* Log message */
139-
NULL, /* Get request time */
140-
NULL, /* Child terminate */
152+
php_embed_register_variables, /* register server variables */
153+
php_embed_log_message, /* Log message */
154+
NULL, /* Get request time */
155+
NULL, /* Child terminate */
141156

142157
STANDARD_SAPI_MODULE_PROPERTIES
143158
};
@@ -150,8 +165,6 @@ static const zend_function_entry additional_functions[] = {
150165

151166
EMBED_SAPI_API int php_embed_init(int argc, char **argv)
152167
{
153-
zend_llist global_vars;
154-
155168
#if defined(SIGPIPE) && defined(SIG_IGN)
156169
signal(SIGPIPE, SIG_IGN); /* ignore SIGPIPE in standalone mode so
157170
that sockets created via fsockopen()
@@ -162,63 +175,99 @@ EMBED_SAPI_API int php_embed_init(int argc, char **argv)
162175
#endif
163176

164177
#ifdef ZTS
165-
php_tsrm_startup();
178+
php_tsrm_startup();
166179
# ifdef PHP_WIN32
167-
ZEND_TSRMLS_CACHE_UPDATE();
180+
ZEND_TSRMLS_CACHE_UPDATE();
168181
# endif
169182
#endif
170183

171184
zend_signal_startup();
172185

173-
sapi_startup(&php_embed_module);
186+
/* SAPI initialization (SINIT)
187+
*
188+
* Initialize the SAPI globals (memset to 0). After this point we can set
189+
* SAPI globals via the SG() macro.
190+
*
191+
* Reentrancy startup.
192+
*
193+
* This also sets 'php_embed_module.ini_entries = NULL' so we cannot
194+
* allocate the INI entries until after this call.
195+
*/
196+
sapi_startup(&php_embed_module);
174197

175198
#ifdef PHP_WIN32
176-
_fmode = _O_BINARY; /*sets default for file streams to binary */
177-
setmode(_fileno(stdin), O_BINARY); /* make the stdio mode be binary */
178-
setmode(_fileno(stdout), O_BINARY); /* make the stdio mode be binary */
179-
setmode(_fileno(stderr), O_BINARY); /* make the stdio mode be binary */
199+
_fmode = _O_BINARY; /*sets default for file streams to binary */
200+
setmode(_fileno(stdin), O_BINARY); /* make the stdio mode be binary */
201+
setmode(_fileno(stdout), O_BINARY); /* make the stdio mode be binary */
202+
setmode(_fileno(stderr), O_BINARY); /* make the stdio mode be binary */
180203
#endif
181204

182-
php_embed_module.ini_entries = malloc(sizeof(HARDCODED_INI));
183-
memcpy(php_embed_module.ini_entries, HARDCODED_INI, sizeof(HARDCODED_INI));
184-
185-
php_embed_module.additional_functions = additional_functions;
186-
187-
if (argv) {
188-
php_embed_module.executable_location = argv[0];
189-
}
205+
/* This hard-coded string of INI settings is parsed and read into PHP's
206+
* configuration hash table at the very end of php_init_config(). This
207+
* means these settings will overwrite any INI settings that were set from
208+
* an INI file.
209+
*
210+
* To provide overwritable INI defaults, hook the ini_defaults function
211+
* pointer that is part of the sapi_module_struct
212+
* (php_embed_module.ini_defaults).
213+
*
214+
* void (*ini_defaults)(HashTable *configuration_hash);
215+
*
216+
* This callback is invoked as soon as the configuration hash table is
217+
* allocated so any INI settings added via this callback will have the
218+
* lowest precedence and will allow INI files to overwrite them.
219+
*/
220+
php_embed_module.ini_entries = malloc(sizeof(HARDCODED_INI));
221+
memcpy(php_embed_module.ini_entries, HARDCODED_INI, sizeof(HARDCODED_INI));
222+
223+
/* SAPI-provided functions. */
224+
php_embed_module.additional_functions = additional_functions;
225+
226+
if (argv) {
227+
php_embed_module.executable_location = argv[0];
228+
}
190229

191-
if (php_embed_module.startup(&php_embed_module)==FAILURE) {
192-
return FAILURE;
193-
}
230+
/* Module initialization (MINIT) */
231+
if (php_embed_module.startup(&php_embed_module) == FAILURE) {
232+
return FAILURE;
233+
}
194234

195-
zend_llist_init(&global_vars, sizeof(char *), NULL, 0);
235+
/* Do not chdir to the script's directory. This is akin to calling the CGI
236+
* SAPI with '-C'.
237+
*/
238+
SG(options) |= SAPI_OPTION_NO_CHDIR;
196239

197-
/* Set some Embedded PHP defaults */
198-
SG(options) |= SAPI_OPTION_NO_CHDIR;
199-
SG(request_info).argc=argc;
200-
SG(request_info).argv=argv;
240+
SG(request_info).argc=argc;
241+
SG(request_info).argv=argv;
201242

202-
if (php_request_startup()==FAILURE) {
203-
php_module_shutdown();
204-
return FAILURE;
205-
}
243+
/* Request initialization (RINIT) */
244+
if (php_request_startup() == FAILURE) {
245+
php_module_shutdown();
246+
return FAILURE;
247+
}
206248

207-
SG(headers_sent) = 1;
208-
SG(request_info).no_headers = 1;
209-
php_register_variable("PHP_SELF", "-", NULL);
249+
SG(headers_sent) = 1;
250+
SG(request_info).no_headers = 1;
251+
php_register_variable("PHP_SELF", "-", NULL);
210252

211-
return SUCCESS;
253+
return SUCCESS;
212254
}
213255

214256
EMBED_SAPI_API void php_embed_shutdown(void)
215257
{
258+
/* Request shutdown (RSHUTDOWN) */
216259
php_request_shutdown((void *) 0);
260+
261+
/* Module shutdown (MSHUTDOWN) */
217262
php_module_shutdown();
263+
264+
/* SAPI shutdown (SSHUTDOWN) */
218265
sapi_shutdown();
266+
219267
#ifdef ZTS
220-
tsrm_shutdown();
268+
tsrm_shutdown();
221269
#endif
270+
222271
if (php_embed_module.ini_entries) {
223272
free(php_embed_module.ini_entries);
224273
php_embed_module.ini_entries = NULL;

0 commit comments

Comments
 (0)