Skip to content

Commit b692de1

Browse files
authored
Merge branch 'main' into docs/apk-packages
2 parents 4dcbd5e + f4667e3 commit b692de1

File tree

9 files changed

+299
-19
lines changed

9 files changed

+299
-19
lines changed

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,8 @@ Go to `https://localhost`, and enjoy!
151151
- [Worker mode](https://frankenphp.dev/docs/worker/)
152152
- [Early Hints support (103 HTTP status code)](https://frankenphp.dev/docs/early-hints/)
153153
- [Real-time](https://frankenphp.dev/docs/mercure/)
154+
- [Logging](https://frankenphp.dev/docs/logging/)
155+
- [Hot reloading](https://frankenphp.dev/docs/hot-reload/)
154156
- [Efficiently Serving Large Static Files](https://frankenphp.dev/docs/x-sendfile/)
155157
- [Configuration](https://frankenphp.dev/docs/config/)
156158
- [Writing PHP Extensions in Go](https://frankenphp.dev/docs/extensions/)
@@ -161,6 +163,7 @@ Go to `https://localhost`, and enjoy!
161163
- [Create static binaries](https://frankenphp.dev/docs/static/)
162164
- [Compile from sources](https://frankenphp.dev/docs/compile/)
163165
- [Monitoring FrankenPHP](https://frankenphp.dev/docs/metrics/)
166+
- [WordPress integration](https://frankenphp.dev/docs/wordpress/)
164167
- [Laravel integration](https://frankenphp.dev/docs/laravel/)
165168
- [Known issues](https://frankenphp.dev/docs/known-issues/)
166169
- [Demo app (Symfony) and benchmarks](https://github.com/dunglas/frankenphp-demo)

build-static.sh

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,11 @@ fi
178178

179179
# Embed PHP app, if any
180180
if [ -n "${EMBED}" ] && [ -d "${EMBED}" ]; then
181-
SPC_OPT_BUILD_ARGS="${SPC_OPT_BUILD_ARGS} --with-frankenphp-app=${EMBED}"
181+
if [[ "${EMBED}" != /* ]]; then
182+
EMBED="${CURRENT_DIR}/${EMBED}"
183+
fi
184+
# shellcheck disable=SC2089
185+
SPC_OPT_BUILD_ARGS="${SPC_OPT_BUILD_ARGS} --with-frankenphp-app='${EMBED}'"
182186
fi
183187

184188
SPC_OPT_INSTALL_ARGS="go-xcaddy"
@@ -204,7 +208,7 @@ done
204208
# shellcheck disable=SC2086
205209
${spcCommand} download --with-php="${PHP_VERSION}" --for-extensions="${PHP_EXTENSIONS}" --for-libs="${PHP_EXTENSION_LIBS}" ${SPC_OPT_DOWNLOAD_ARGS}
206210
export FRANKENPHP_SOURCE_PATH="${CURRENT_DIR}"
207-
# shellcheck disable=SC2086
211+
# shellcheck disable=SC2086,SC2090
208212
${spcCommand} build --enable-zts --build-embed --build-frankenphp ${SPC_OPT_BUILD_ARGS} "${PHP_EXTENSIONS}" --with-libs="${PHP_EXTENSION_LIBS}"
209213

210214
if [ -n "$CI" ]; then

docs/config.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -213,8 +213,10 @@ This is useful for development environments.
213213
}
214214
```
215215

216-
If the `watch` directory is not specified, it will fall back to `./**/*.{php,yaml,yml,twig,env}`,
217-
which watches all `.php`, `.yaml`, `.yml`, `.twig` and `.env` files in the directory and subdirectories
216+
This feature is often used in combination with [hot reload](hot-reload.md).
217+
218+
If the `watch` directory is not specified, it will fall back to `./**/*.{env,php,twig,yaml,yml}`,
219+
which watches all `.env`, `.php`, `.twig`, `.yaml` and `.yml` files in the directory and subdirectories
218220
where the FrankenPHP process was started. You can instead also specify one or more directories via a
219221
[shell filename pattern](https://pkg.go.dev/path/filepath#Match):
220222

@@ -239,7 +241,7 @@ where the FrankenPHP process was started. You can instead also specify one or mo
239241

240242
The file watcher is based on [e-dant/watcher](https://github.com/e-dant/watcher).
241243

242-
## Matching the worker to a path
244+
## Matching the Worker To a Path
243245

244246
In traditional PHP applications, scripts are always placed in the public directory.
245247
This is also true for worker scripts, which are treated like any other PHP script.

docs/extensions.md

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -88,20 +88,20 @@ While some variable types have the same memory representation between C/PHP and
8888
This table summarizes what you need to know:
8989

9090
| PHP type | Go type | Direct conversion | C to Go helper | Go to C helper | Class Methods Support |
91-
|--------------------|-------------------------------|-------------------|-----------------------------------|------------------------------------|-----------------------|
92-
| `int` | `int64` | | - | - | |
93-
| `?int` | `*int64` | | - | - | |
94-
| `float` | `float64` | | - | - | |
95-
| `?float` | `*float64` | | - | - | |
96-
| `bool` | `bool` | | - | - | |
97-
| `?bool` | `*bool` | | - | - | |
98-
| `string`/`?string` | `*C.zend_string` | | `frankenphp.GoString()` | `frankenphp.PHPString()` | |
99-
| `array` | `frankenphp.AssociativeArray` | | `frankenphp.GoAssociativeArray()` | `frankenphp.PHPAssociativeArray()` | |
100-
| `array` | `map[string]any` | | `frankenphp.GoMap()` | `frankenphp.PHPMap()` | |
101-
| `array` | `[]any` | | `frankenphp.GoPackedArray()` | `frankenphp.PHPPackedArray()` | |
102-
| `mixed` | `any` | | `GoValue()` | `PHPValue()` | |
103-
| `callable` | `*C.zval` | | - | frankenphp.CallPHPCallable() | |
104-
| `object` | `struct` | | _Not yet implemented_ | _Not yet implemented_ | |
91+
| ------------------ | ----------------------------- | ----------------- | --------------------------------- | ---------------------------------- | --------------------- |
92+
| `int` | `int64` || - | - ||
93+
| `?int` | `*int64` || - | - ||
94+
| `float` | `float64` || - | - ||
95+
| `?float` | `*float64` || - | - ||
96+
| `bool` | `bool` || - | - ||
97+
| `?bool` | `*bool` || - | - ||
98+
| `string`/`?string` | `*C.zend_string` || `frankenphp.GoString()` | `frankenphp.PHPString()` ||
99+
| `array` | `frankenphp.AssociativeArray` || `frankenphp.GoAssociativeArray()` | `frankenphp.PHPAssociativeArray()` ||
100+
| `array` | `map[string]any` || `frankenphp.GoMap()` | `frankenphp.PHPMap()` ||
101+
| `array` | `[]any` || `frankenphp.GoPackedArray()` | `frankenphp.PHPPackedArray()` ||
102+
| `mixed` | `any` || `GoValue()` | `PHPValue()` ||
103+
| `callable` | `*C.zval` || - | frankenphp.CallPHPCallable() ||
104+
| `object` | `struct` || _Not yet implemented_ | _Not yet implemented_ ||
105105

106106
> [!NOTE]
107107
>

docs/hot-reload.md

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
# Hot Reload
2+
3+
FrankenPHP includes a built-in **hot reload** feature designed to vastly improve the developer experience.
4+
5+
![Mercure](hot-reload.png)
6+
7+
This feature provides a workflow similar to **Hot Module Replacement (HMR)** found in modern JavaScript tooling (like Vite or webpack).
8+
Instead of manually refreshing the browser after every file change (PHP code, templates, JavaScript and CSS files...),
9+
FrankenPHP updates the content in real-time.
10+
11+
Hot Reload natively works with WordPress, Laravel, Symfony, and any other PHP application or framework.
12+
13+
When enabled, FrankenPHP watches your current working directory for filesystem changes.
14+
When a file is modified, it pushes a [Mercure](mercure.md) update to the browser.
15+
16+
Depending on your setup, the browser will either:
17+
18+
- **Morph the DOM** (preserving scroll position and input state) if [Idiomorph](https://github.com/bigskysoftware/idiomorph) is loaded.
19+
- **Reload the page** (standard live reload) if Idiomorph is not present.
20+
21+
## Configuration
22+
23+
To enable hot reloading, enable Mercure, then add the `hot_reload` sub-directive to the `php_server` directive in your `Caddyfile`.
24+
25+
> [!WARNING]
26+
> This feature is intended for **development environments only**.
27+
> Do not enable `hot_reload` in production, as watching the filesystem incurs performance overhead and exposes internal endpoints.
28+
29+
```caddyfile
30+
localhost
31+
32+
mercure {
33+
anonymous
34+
}
35+
36+
root public/
37+
php_server {
38+
hot_reload
39+
}
40+
```
41+
42+
By default, FrankenPHP will watch all files in the current working directory matching this glob pattern: `./**/*.{css,env,gif,htm,html,jpg,jpeg,js,mjs,php,png,svg,twig,webp,xml,yaml,yml}`
43+
44+
It's possible to explicitly set the files to watch using the glob syntax:
45+
46+
```caddyfile
47+
localhost
48+
49+
mercure {
50+
anonymous
51+
}
52+
53+
root public/
54+
php_server {
55+
hot_reload src/**/*{.php,.js} config/**/*.yaml
56+
}
57+
```
58+
59+
Use the long form to specify the Mercure topic to use as well as which directories or files to watch by providing paths to the `hot_reload` option:
60+
61+
```caddyfile
62+
localhost
63+
64+
mercure {
65+
anonymous
66+
}
67+
68+
root public/
69+
php_server {
70+
hot_reload {
71+
topic hot-reload-topic
72+
watch src/**/*.php
73+
watch assets/**/*.{ts,json}
74+
watch templates/
75+
watch public/css/
76+
}
77+
}
78+
```
79+
80+
## Client-Side Integration
81+
82+
While the server detects changes, the browser needs to subscribe to these events to update the page.
83+
FrankenPHP exposes the Mercure Hub URL to use for subscribing to file changes via the `$_SERVER['FRANKENPHP_HOT_RELOAD']` environment variable.
84+
85+
A convenience JavaScript library, [frankenphp-hot-reload](https://www.npmjs.com/package/frankenphp-hot-reload), is also available to handle the client-side logic.
86+
To use it, add the following to your main layout:
87+
88+
```php
89+
<!DOCTYPE html>
90+
<title>FrankenPHP Hot Reload</title>
91+
<?php if (isset($_SERVER['FRANKENPHP_HOT_RELOAD'])): ?>
92+
<meta name="frankenphp-hot-reload:url" content="<?=$_SERVER['FRANKENPHP_HOT_RELOAD']?>">
93+
<script src="https://cdn.jsdelivr.net/npm/idiomorph"></script>
94+
<script src="https://cdn.jsdelivr.net/npm/frankenphp-hot-reload/+esm" type="module"></script>
95+
<?php endif ?>
96+
```
97+
98+
The library will automatically subscribe to the Mercure hub, fetch the current URL in the background when a file change is detected and morph the DOM.
99+
It is available as a [npm](https://www.npmjs.com/package/frankenphp-hot-reload) package and on [GitHub](https://github.com/dunglas/frankenphp-hot-reload).
100+
101+
Alternatively, you can implement your own client-side logic by subscribing directly to the Mercure hub using the `EventSource` native JavaScript class.
102+
103+
### Worker Mode
104+
105+
If you are running your application in [Worker Mode](https://frankenphp.dev/docs/worker/), your application script remains in memory.
106+
This means changes to your PHP code will not be reflected immediately, even if the browser reloads.
107+
108+
For the best developer experience, you should combine `hot_reload` with [the `watch` sub-directive in the `worker` directive](config.md#watching-for-file-changes).
109+
110+
- `hot_reload`: refreshes the **browser** when files change
111+
- `worker.watch`: restarts the worker when files change
112+
113+
```caddy
114+
localhost
115+
116+
mercure {
117+
anonymous
118+
}
119+
120+
root public/
121+
php_server {
122+
hot_reload
123+
worker {
124+
file /path/to/my_worker.php
125+
watch
126+
}
127+
}
128+
```
129+
130+
### How it works
131+
132+
1. **Watch**: FrankenPHP monitors the filesystem for modifications using [the `e-dant/watcher` library](https://github.com/e-dant/watcher) under the hood (we contributed the Go binding).
133+
2. **Restart (Worker Mode)**: if `watch` is enabled in the worker config, the PHP worker is restarted to load the new code.
134+
3. **Push**: a JSON payload containing the list of changed files is sent to the built-in [Mercure hub](https://mercure.rocks).
135+
4. **Receive**: The browser, listening via the JavaScript library, receives the Mercure event.
136+
5. **Update**:
137+
138+
- If **Idiomorph** is detected, it fetches the updated content and morphs the current HTML to match the new state, applying changes instantly without losing state.
139+
- Otherwise, `window.location.reload()` is called to refresh the page.

docs/hot-reload.png

386 KB
Loading

docs/logging.md

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
# Logging
2+
3+
FrankenPHP integrates seamlessly with [Caddy's logging system](https://caddyserver.com/docs/logging).
4+
You can log messages using standard PHP functions or leverage the dedicated `frankenphp_log()` function for advanced
5+
structured logging capabilities.
6+
7+
## `frankenphp_log()`
8+
9+
The `frankenphp_log()` function allows you to emit structured logs directly from your PHP application,
10+
making ingestion into platforms like Datadog, Grafana Loki, or Elastic, as well as OpenTelemetry support, much easier.
11+
12+
Under the hood, `frankenphp_log()` wraps [Go's `log/slog` package](https://pkg.go.dev/log/slog) to provide rich logging
13+
features.
14+
15+
These logs include the severity level and optional context data.
16+
17+
```php
18+
function frankenphp_log(string $message, int $level = FRANKENPHP_LOG_LEVEL_INFO, array $context = []): void
19+
```
20+
21+
### Parameters
22+
23+
- **`message`**: The log message string.
24+
- **`level`**: The severity level of the log. Can be any arbitrary integer. Convenience constants are provided for common levels: `FRANKENPHP_LOG_LEVEL_DEBUG` (`-4`), `FRANKENPHP_LOG_LEVEL_INFO` (`0`), `FRANKENPHP_LOG_LEVEL_WARN` (`4`) and `FRANKENPHP_LOG_LEVEL_ERROR` (`8`)). Default is `FRANKENPHP_LOG_LEVEL_INFO`.
25+
- **`context`**: An associative array of additional data to include in the log entry.
26+
27+
### Example
28+
29+
```php
30+
<?php
31+
32+
// Log a simple informational message
33+
frankenphp_log("Hello from FrankenPHP!");
34+
35+
// Log a warning with context data
36+
frankenphp_log(
37+
"Memory usage high",
38+
FRANKENPHP_LOG_LEVEL_WARN,
39+
[
40+
'current_usage' => memory_get_usage(),
41+
'peak_usage' => memory_get_peak_usage(),
42+
],
43+
);
44+
45+
```
46+
47+
When viewing the logs (e.g., via `docker compose logs`), the output will appear as structured JSON:
48+
49+
```json
50+
{"level":"info","ts":1704067200,"logger":"frankenphp","msg":"Hello from FrankenPHP!"}
51+
{"level":"warn","ts":1704067200,"logger":"frankenphp","msg":"Memory usage high","current_usage":10485760,"peak_usage":12582912}
52+
```
53+
54+
## `error_log()`
55+
56+
FrankenPHP also allows logging using the standard `error_log()` function. If the `$message_type` parameter is `4` (SAPI),
57+
these messages are routed to the Caddy logger.
58+
59+
By default, messages sent via `error_log()` are treated as unstructured text.
60+
They are useful for compatibility with existing applications or libraries that rely on the standard PHP library.
61+
62+
### Example
63+
64+
```php
65+
error_log("Database connection failed", 4);
66+
```
67+
68+
This will appear in the Caddy logs, often prefixed to indicate it originated from PHP.
69+
70+
> [!TIP]
71+
> For better observability in production environments, prefer `frankenphp_log()`
72+
> as it allows you to filter logs by level (Debug, Error, etc.)
73+
> and query specific fields in your logging infrastructure.

docs/wordpress.md

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# WordPress
2+
3+
Run [WordPress](https://wordpress.org/) with FrankenPHP to enjoy a modern, high-performance stack with automatic HTTPS, HTTP/3, and Zstandard compression.
4+
5+
## Minimal Installation
6+
7+
1. [Download WordPress](https://wordpress.org/download/)
8+
2. Extract the ZIP archive and open a terminal in the extracted directory
9+
3. Run:
10+
```console
11+
frankenphp php-server
12+
```
13+
4. Go to `http://localhost/wp-admin/` and follow the installation instructions
14+
5. Enjoy!
15+
16+
For a production-ready setup, prefer using `frankenphp run` with a `Caddyfile` like this one:
17+
18+
```caddyfile
19+
example.com
20+
21+
php_server
22+
encode zstd br gzip
23+
log
24+
```
25+
26+
## Hot Reload
27+
28+
To use the [hot reload](hot-reload.md) feature with WordPress, enable [Mercure](mercure.md) and add the `hot_reload` sub-directive to the `php_server` directive in your `Caddyfile`:
29+
30+
```caddyfile
31+
localhost
32+
33+
mercure {
34+
anonymous
35+
}
36+
37+
php_server {
38+
hot_reload
39+
}
40+
```
41+
42+
Then, add the code needed to load the JavaScript libraries in the `functions.php` file of your WordPress theme:
43+
44+
```php
45+
function hot_reload() {
46+
?>
47+
<?php if (isset($_SERVER['FRANKENPHP_HOT_RELOAD'])): ?>
48+
<meta name="frankenphp-hot-reload:url" content="<?=$_SERVER['FRANKENPHP_HOT_RELOAD']?>">
49+
<script src="https://cdn.jsdelivr.net/npm/idiomorph"></script>
50+
<script src="https://cdn.jsdelivr.net/npm/frankenphp-hot-reload/+esm" type="module"></script>
51+
<?php endif ?>
52+
<?php
53+
}
54+
add_action('wp_head', 'hot_reload');
55+
```
56+
57+
Finally, run `frankenphp run` from the WordPress root directory.

docs/worker.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ The following command will trigger a restart if any file ending in `.php` in the
3535
frankenphp php-server --worker /path/to/your/worker/script.php --watch="/path/to/your/app/**/*.php"
3636
```
3737

38+
This feature is often used in combination with [hot reloading](hot-reload.md).
39+
3840
## Symfony Runtime
3941

4042
> [!TIP]

0 commit comments

Comments
 (0)