Skip to content

Commit 67c2ff0

Browse files
committed
Improve docs
1 parent fbaee63 commit 67c2ff0

File tree

3 files changed

+164
-163
lines changed

3 files changed

+164
-163
lines changed

INTERNALS.md

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
## Build
2+
3+
### Building PHP
4+
5+
Building PHP itself is straightforward. Here's the basic configuration:
6+
7+
```sh
8+
git clone https://github.com/php/php-src.git
9+
cd php-src
10+
./buildconf
11+
./configure --enable-shared --enable-embed=shared --enable-zts --without-iconv --with-pdo-mysql=mysqlnd --with-mysqli=mysqlnd --with-openssl --with-curl --enable-mbstring
12+
make -j$([[ "$(uname)" == "Darwin" ]] && sysctl -n hw.physicalcpu || nproc)
13+
sudo make install
14+
```
15+
16+
We'll probably want to build with additional extensions later, but this is a
17+
good starting point. Extensions should be able to load dynamically anyway,
18+
so easy enough to add them separately.
19+
20+
The libphp.{so,dylib} files should be copied to the root of this project.
21+
22+
### Building the Node.js module
23+
24+
To link with the libphp in the project directory, an environment variable
25+
must be set to adjust the rpath to `$ORIGIN` in the build output.
26+
27+
```sh
28+
RUSTFLAGS="-C link-args=-Wl,-rpath,\$ORIGIN" npm run build
29+
```
30+
31+
## Various learnings
32+
33+
### php://input
34+
35+
PHP has no concept of a "socket", it instead has its own form of streams which
36+
can be mounted into a request run. The `php://input` stream represents the body
37+
of an incoming request.
38+
39+
### php://output
40+
41+
As with `php://input`, `php://output` is a stream that can be mounted into a
42+
request run, but is instead used for writing out to the response.
43+
44+
### superglobals
45+
46+
As PHP uses its input and output streams for transmitting _only_ the request
47+
and response bodies, headers must be passed in separately. The way this is done
48+
from the perspective of PHP is via what it calls "superglobals". These are
49+
special variables which are global to every script.
50+
51+
The main superglobals of interest are:
52+
- `$_SERVER` contains information about the server and the request.
53+
- `$_GET` contains query string parameters.
54+
- `$_POST` contains form data.
55+
- `$_FILES` contains file uploads.
56+
- `$_COOKIE` contains cookies.
57+
- `$_SESSION` contains session data.
58+
- `$_REQUEST` is a mix of `$_GET`, `$_POST`, and `$_COOKIE`.
59+
- `$_ENV` contains environment variables.
60+
61+
Super globals are set from C prior to initiating the request using the
62+
`SG(...)` macro. For example, `SG(request_info).request_method` is set to the
63+
request method. The names given to `SG(...)` are poorly matched to the names of
64+
the superglobals they are assigned to, so it is necessary to look at the
65+
`php_variables.h` file to determine the correct name.
66+
67+
### SAPI -- The "recommended" embedding API
68+
69+
PHP has a concept of a "Server API" (SAPI) which is the interface between PHP
70+
and the web server. The SAPI is responsible for handling the request and
71+
response, and is the recommended way to embed PHP into a C application.
72+
73+
It is a simplification of the CGI interface, but is _too_ simplified to be
74+
useful for our purposes. When used directly, it spins up an entirely fresh
75+
instance of PHP for each request, which is suffers from a lot of startup cost,
76+
and doesn't allow sharing code compilation between requests.
77+
78+
### Using the Zend API directly
79+
80+
All that SAPI actually does _internally_ is squash three (possibly four?)
81+
nested scopes into one, but these are more useful to us separated.
82+
83+
#### (Optional) php_tsrm_startup (Thread Safe Resource Management)
84+
85+
Provides thread safety for running multiple PHP environments in parallel.
86+
87+
#### zend_signal_startup (Signal Handling)
88+
89+
Defines globally how PHP should handle signals, not configurable with SAPI.
90+
91+
#### sapi_startup (Server API)
92+
93+
Initializes the SAPI, and provides a way to configure it. This is really just
94+
a container for loading INI settings, extensions, and allocating space for
95+
superglobals on the current thread.
96+
97+
#### php_embed_module.startup
98+
99+
This is the only actually _configurable_ part of SAPI. It treats the
100+
PHP server you're trying to construct as just another a module/extension,
101+
which is a bit odd as the thing that is supposed to be orchestrating
102+
everything.
103+
104+
Configuration of this stages is done through [one-big-struct](https://github.com/php/php-src/blob/6024122e54f4e8a4f35c0abe9b46425856a11e6c/main/SAPI.h#L237-L290)
105+
which contains individual functions for:
106+
107+
- reading POST data to populate `$_POST`
108+
- reading GET data to populate `$_GET`
109+
- reading cookies to populate `$_COOKIE`
110+
- reading environment variables to populate `$_ENV`
111+
- reading request headers to populate `$_SERVER`
112+
- reading request body to populate `php://input`
113+
- writing response headers
114+
- writing response body from `php://output`
115+
- Handling errors
116+
117+
#### php_request_startup (Request Startup)
118+
119+
This is the scope in which the actual request can occur. It allocates space
120+
for the request-related superglobals, and sets up the request environment.
121+
Within this scope PHP code can then be run with those request-specific
122+
superglobals populated.
123+
124+
Within SAPI this stage is bundled into the startup of the entire SAPI system,
125+
and so a SAPI construction can only handle a single request before tearing down
126+
everything completely.
127+
128+
The _better_ way is to reuse this stage and then probably construct a separate
129+
php_embed_module also for each request. In this way most of the PHP environment
130+
can be shared between requests, and only the request-specific data needs to be
131+
updated.
132+
133+
### Maybe PHP can also be concurrent?
134+
135+
PHP is designed to allow an environment to be shared across multiple threads
136+
with the `tsrm` system. But as input and output are _streams_ it may also be
137+
possible to run multiple requests on the same thread concurrently, to some
138+
extent, by switching out their superglobal states whenever stream data would
139+
be read, or when writing out would block the current request.
140+
141+
A caveat here is that _other_ than the input and output streams, things are
142+
generally synchronous. For example, typical database drivers would block the
143+
thread. Being _partially_ async may still be an improvement though, and there's
144+
always the possibility of us writing our own async components, which would get
145+
us better performance while also possibly locking in our users a bit more.

README.md

Lines changed: 16 additions & 163 deletions
Original file line numberDiff line numberDiff line change
@@ -1,56 +1,32 @@
1-
# php-stackable
1+
# @platformatic/php
22

3-
Proof-of-concept PHP stackable. Not yet working...
3+
Delegate handling of HTTP requests to a thread pool of PHP instances.
44

5-
## Build Notes
5+
## Requirements
66

7-
### Building PHP
7+
PHP dynamically links against several system libraries. These must be installed
8+
as listed below:
89

9-
Building PHP itself is straightforward. Here's the basic configuration:
10+
### Linux
1011

1112
```sh
12-
git clone https://github.com/php/php-src.git
13-
cd php-src
14-
./buildconf
15-
./configure --enable-shared --enable-embed=shared --enable-zts --without-iconv --with-pdo-mysql=mysqlnd --with-mysqli=mysqlnd --with-openssl --with-curl --enable-mbstring
16-
make -j$([[ "$(uname)" == "Darwin" ]] && sysctl -n hw.physicalcpu || nproc)
17-
sudo make install
13+
sudo apt-get update
14+
sudo apt-get install -y libssl-dev libcurl4-openssl-dev libxml2-dev \
15+
libsqlite3-dev libonig-dev re2c
1816
```
1917

20-
We'll probably want to build with additional extensions later, but this is a
21-
good starting point. Extensions should be able to load dynamically anyway,
22-
so easy enough to add them separately.
18+
### macOS
2319

24-
### ext-php-rs
25-
26-
This presently expects the [Complete SAPI Implementation PR](https://github.com/platformatic/ext-php-rs/pull/1)
27-
to be checked out at the same level as this repo under the name ext-php-rs.
28-
29-
```
30-
base
31-
├── ext-php-rs
32-
└── php-stackable
20+
```sh
21+
brew install openssl@3 curl sqlite libxml2 oniguruma
3322
```
3423

35-
### Building the Node.js module
36-
37-
Presently the rpath to link needs to be configured via environment variable.
38-
This tells the linker where to find the PHP shared library.
24+
## Install
3925

4026
```sh
41-
RUSTFLAGS="-C link-args=-Wl,-rpath,/usr/local/lib" npm run build
27+
npm install @platformatic/php
4228
```
4329

44-
By default rpath is left to its default, which I _think_ means cwd, but I need
45-
to verify that. It can be configured in `build.rs` to a different location,
46-
but that is likely platform-specific so would need to figure out the correct
47-
locations if we want to use platform-available PHP builds.
48-
49-
Alternatively, we could ship our own libphp alongside the .node file, but I
50-
need to figure out how to configure the rpath correctly to work with the
51-
relative path. This may be the better option, but would also need to figure
52-
out if that then dictates where extensions need to live.
53-
5430
## Usage
5531

5632
```js
@@ -61,22 +37,15 @@ import { Php, Request } from '@platformatic/php'
6137
// Presently the file contents must be passed in as a string,
6238
// but it could be made to take only a filename and read the file
6339
// contents itself.
64-
//
65-
// NOTE: This presently only supports eval-mode, not tag-mode, meaning no
66-
// interleaving with html using <?php ?> tags. Tag mode will be ready soon.
6740
const php = new Php({
6841
file: 'index.php',
69-
code: `
42+
code: `<?php
7043
$headers = apache_request_headers();
7144
echo $headers["X-Test"];
72-
`
45+
?>`
7346
})
7447

7548
// This is a container to help translate Node.js requests into PHP requests.
76-
//
77-
// Future ideas:
78-
// - Support passing in a Node.js IncomingMessage object directly?
79-
// - Support web standard Request objects?
8049
const req = new Request({
8150
method: 'GET',
8251
url: 'http://example.com/test.php',
@@ -107,119 +76,3 @@ console.log({
10776
// Headers is a multimap which implements all the standard Map methods plus
10877
// some additional helpers. See the tests in __test__ for more details.
10978
```
110-
111-
## Various learnings
112-
113-
### php://input
114-
115-
PHP has no concept of a "socket", it instead has its own form of streams which
116-
can be mounted into a request run. The `php://input` stream represents the body
117-
of an incoming request.
118-
119-
### php://output
120-
121-
As with `php://input`, `php://output` is a stream that can be mounted into a
122-
request run, but is instead used for writing out to the response.
123-
124-
### superglobals
125-
126-
As PHP uses its input and output streams for transmitting _only_ the request
127-
and response bodies, headers must be passed in separately. The way this is done
128-
from the perspective of PHP is via what it calls "superglobals". These are
129-
special variables which are global to every script.
130-
131-
The main superglobals of interest are:
132-
- `$_SERVER` contains information about the server and the request.
133-
- `$_GET` contains query string parameters.
134-
- `$_POST` contains form data.
135-
- `$_FILES` contains file uploads.
136-
- `$_COOKIE` contains cookies.
137-
- `$_SESSION` contains session data.
138-
- `$_REQUEST` is a mix of `$_GET`, `$_POST`, and `$_COOKIE`.
139-
- `$_ENV` contains environment variables.
140-
141-
Super globals are set from C prior to initiating the request using the
142-
`SG(...)` macro. For example, `SG(request_info).request_method` is set to the
143-
request method. The names given to `SG(...)` are poorly matched to the names of
144-
the superglobals they are assigned to, so it is necessary to look at the
145-
`php_variables.h` file to determine the correct name.
146-
147-
### SAPI -- The "recommended" embedding API
148-
149-
PHP has a concept of a "Server API" (SAPI) which is the interface between PHP
150-
and the web server. The SAPI is responsible for handling the request and
151-
response, and is the recommended way to embed PHP into a C application.
152-
153-
It is a simplification of the CGI interface, but is _too_ simplified to be
154-
useful for our purposes. When used directly, it spins up an entirely fresh
155-
instance of PHP for each request, which is suffers from a lot of startup cost,
156-
and doesn't allow sharing code compilation between requests.
157-
158-
### Using the Zend API directly
159-
160-
All that SAPI actually does _internally_ is squash three (possibly four?)
161-
nested scopes into one, but these are more useful to us separated.
162-
163-
#### (Optional) php_tsrm_startup (Thread Safe Resource Management)
164-
165-
Provides thread safety for running multiple PHP environments in parallel.
166-
167-
#### zend_signal_startup (Signal Handling)
168-
169-
Defines globally how PHP should handle signals, not configurable with SAPI.
170-
171-
#### sapi_startup (Server API)
172-
173-
Initializes the SAPI, and provides a way to configure it. This is really just
174-
a container for loading INI settings, extensions, and allocating space for
175-
superglobals on the current thread.
176-
177-
#### php_embed_module.startup
178-
179-
This is the only actually _configurable_ part of SAPI. It treats the
180-
PHP server you're trying to construct as just another a module/extension,
181-
which is a bit odd as the thing that is supposed to be orchestrating
182-
everything.
183-
184-
Configuration of this stages is done through [one-big-struct](https://github.com/php/php-src/blob/6024122e54f4e8a4f35c0abe9b46425856a11e6c/main/SAPI.h#L237-L290)
185-
which contains individual functions for:
186-
187-
- reading POST data to populate `$_POST`
188-
- reading GET data to populate `$_GET`
189-
- reading cookies to populate `$_COOKIE`
190-
- reading environment variables to populate `$_ENV`
191-
- reading request headers to populate `$_SERVER`
192-
- reading request body to populate `php://input`
193-
- writing response headers
194-
- writing response body from `php://output`
195-
- Handling errors
196-
197-
#### php_request_startup (Request Startup)
198-
199-
This is the scope in which the actual request can occur. It allocates space
200-
for the request-related superglobals, and sets up the request environment.
201-
Within this scope PHP code can then be run with those request-specific
202-
superglobals populated.
203-
204-
Within SAPI this stage is bundled into the startup of the entire SAPI system,
205-
and so a SAPI construction can only handle a single request before tearing down
206-
everything completely.
207-
208-
The _better_ way is to reuse this stage and the probably construct a separate
209-
php_embed_module also for each request. In this way most of the PHP environment
210-
can be shared between requests, and only the request-specific data needs to be
211-
updated.
212-
213-
### Maybe PHP can also be concurrent?
214-
215-
PHP is designed to allow an environment to be shared across multiple threads
216-
with the `tsrm` system. But as input and output are _streams_ it may also be
217-
possible to run multiple requests on the same thread concurrently, to some
218-
extent, by switching out their superglobal states whenever stream data would
219-
be read, or when writing out would block the current request.
220-
221-
A caveat here is that _other_ than the input and output streams, things are
222-
generally synchronous. For example, typical database drivers would block the
223-
thread. Being _partially_ async may still be an improvement though, and there's
224-
always the possibility of us writing our own async components, which would get
225-
us better performance while also possibly locking in our users a bit more.

crates/php_node/src/request.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@ pub struct PhpRequest {
4141
request: Request,
4242
}
4343

44+
// Future ideas:
45+
// - Support passing in a Node.js IncomingMessage object directly?
46+
// - Support web standard Request objects?
4447
#[napi]
4548
impl PhpRequest {
4649
/// Create a new PHP request.

0 commit comments

Comments
 (0)