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
6 changes: 6 additions & 0 deletions caddy/module.go
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,12 @@ func (f *FrankenPHPModule) ServeHTTP(w http.ResponseWriter, r *http.Request, _ c
return caddyhttp.Error(http.StatusInternalServerError, err)
}

placeholders := fr.Context().Value(frankenphp.PlaceholdersContextKey).(map[string]string)

for k, v := range placeholders {
repl.Set(k, v)
}

return nil
}

Expand Down
8 changes: 7 additions & 1 deletion context.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ type frankenPHPContext struct {
startedAt time.Time
}

type placeholdersContextKeyStruct struct{}
var PlaceholdersContextKey = placeholdersContextKeyStruct{}

// fromContext extracts the frankenPHPContext from a context.
func fromContext(ctx context.Context) (fctx *frankenPHPContext, ok bool) {
fctx, ok = ctx.Value(contextKey).(*frankenPHPContext)
Expand Down Expand Up @@ -80,8 +83,11 @@ func NewRequestWithContext(r *http.Request, opts ...RequestOption) (*http.Reques
}

c := context.WithValue(r.Context(), contextKey, fc)
c = context.WithValue(c, PlaceholdersContextKey, make(map[string]string))
Copy link
Contributor

@AlliBalliBaba AlliBalliBaba Oct 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you defer context creation to go_set_caddy_placeholder? Otherwise this map will most of the time end up being unused

Copy link
Author

@Starfox64 Starfox64 Oct 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've been trying to work on this but I'm stuck and don't know how to go about it since this is only my second time ever working with Go.

I've been able to defer creation to the first call to go_set_caddy_placeholder but I can't access the updated request context from caddy in ServeHTTP after the request is handled by FrankenPHP. That is because frankenPHPContext is private so I can't access the updated request and it's context.

I feel like my only way forward would be to make frankenPHPContext public and store the placeholders there directly instead of the request context.

What do you think?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that probably makes sense. If you were to use this feature outside Caddy (i.e. frankenphp as a library), then it’d also be useful.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we plan to expose other things about the request, then making parts of FrankenPHPContext public probably is the way to go. What else would you like to expose @withinboredom ?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should I go ahead and make FrankenPHPContext public?

If so, is there anything I should be aware of before mass replacing frankenPHPContext by FrankenPHPContext? Is it safe to proceed like that? (asking because it's a relatively large diff)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm not sure, since there aren't other obvious use cases, I'd rather not make it public. I think this might still be the best option, I also can't think of a better way right now.

c = context.WithValue(c, PlaceholdersContextKey, make(map[string]string))

r = r.WithContext(c)
fc.request = r

return r.WithContext(c), nil
return r, nil
}

// newDummyContext creates a fake context from a request path
Expand Down
22 changes: 22 additions & 0 deletions docs/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,28 @@ where the FrankenPHP process was started. You can instead also specify one or mo

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

### Placeholders

You can set Caddy placeholders from your PHP code using the `frankenphp_set_caddy_placeholder(string $key, string $value)` function.
These placeholders can then be used in Caddy directives like `log_append`.

Example usage:

```php
frankenphp_set_caddy_placeholder('frankenphp.custom_placeholder', 'Look at my placeholder!');
```

```caddyfile
http:// {
log
route {
# ...

log_append my_placeholder {frankenphp.custom_placeholder}
}
}
```

## Matching the worker to a path

In traditional PHP applications, scripts are always placed in the public directory.
Expand Down
12 changes: 12 additions & 0 deletions frankenphp.c
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,18 @@ PHP_FUNCTION(frankenphp_getenv) {
}
} /* }}} */

/* {{{ Set a placeholder on the current Caddy request */
PHP_FUNCTION(frankenphp_set_caddy_placeholder) {
zend_string *key, *value;

ZEND_PARSE_PARAMETERS_START(2, 2)
Z_PARAM_STR(key)
Z_PARAM_STR(value)
ZEND_PARSE_PARAMETERS_END();

go_set_caddy_placeholder(thread_index, ZSTR_VAL(key), ZSTR_VAL(value));
} /* }}} */

/* {{{ Fetch all HTTP request headers */
PHP_FUNCTION(frankenphp_request_headers) {
ZEND_PARSE_PARAMETERS_NONE();
Expand Down
15 changes: 15 additions & 0 deletions frankenphp.go
Original file line number Diff line number Diff line change
Expand Up @@ -565,6 +565,21 @@ func go_log(message *C.char, level C.int) {
}
}

//export go_set_caddy_placeholder
func go_set_caddy_placeholder(threadIndex C.uintptr_t, key *C.char, value *C.char) {
fc := phpThreads[threadIndex].getRequestContext()

placeholders, _ := fc.request.Context().Value(PlaceholdersContextKey).(map[string]string)

if placeholders == nil {
logger.LogAttrs(context.Background(), slog.LevelDebug, "frankenphp_set_caddy_placeholder() called in non-HTTP context", slog.String("worker", fc.scriptFilename))

return
}

placeholders[C.GoString(key)] = C.GoString(value)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe these keys should always be prefixed with 'frankenphp', otherwise devs might end up replacing existing placeholders

}

//export go_is_context_done
func go_is_context_done(threadIndex C.uintptr_t) C.bool {
return C.bool(phpThreads[threadIndex].getRequestContext().isDone)
Expand Down
3 changes: 3 additions & 0 deletions frankenphp.h
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@ int frankenphp_execute_script(char *file_name);
int frankenphp_execute_script_cli(char *script, int argc, char **argv,
bool eval);

void frankenphp_set_caddy_placeholder(uintptr_t thread_index, zend_string *key,
zend_string *value);

void frankenphp_register_variables_from_request_info(
zval *track_vars_array, zend_string *content_type,
zend_string *path_translated, zend_string *query_string,
Expand Down
1 change: 1 addition & 0 deletions frankenphp.stub.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,4 @@ function frankenphp_response_headers(): array|bool {}
*/
function apache_response_headers(): array|bool {}

function frankenphp_set_caddy_placeholder(string $key, string $value): void {}
10 changes: 9 additions & 1 deletion frankenphp_arginfo.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/* This is a generated file, edit the .stub.php file instead.
* Stub hash: 05ebde17137c559e891362fba6524fad1e0a2dfe */
* Stub hash: c5318079b1c5629258a1f4b682c7aaf327588b71 */

ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_frankenphp_handle_request, 0, 1,
_IS_BOOL, 0)
Expand Down Expand Up @@ -30,11 +30,18 @@ ZEND_END_ARG_INFO()

#define arginfo_apache_response_headers arginfo_frankenphp_response_headers

ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(
arginfo_frankenphp_set_caddy_placeholder, 0, 2, IS_VOID, 0)
ZEND_ARG_TYPE_INFO(0, key, IS_STRING, 0)
ZEND_ARG_TYPE_INFO(0, value, IS_STRING, 0)
ZEND_END_ARG_INFO()

ZEND_FUNCTION(frankenphp_handle_request);
ZEND_FUNCTION(headers_send);
ZEND_FUNCTION(frankenphp_finish_request);
ZEND_FUNCTION(frankenphp_request_headers);
ZEND_FUNCTION(frankenphp_response_headers);
ZEND_FUNCTION(frankenphp_set_caddy_placeholder);

// clang-format off
static const zend_function_entry ext_functions[] = {
Expand All @@ -47,6 +54,7 @@ static const zend_function_entry ext_functions[] = {
ZEND_FALIAS(getallheaders, frankenphp_request_headers, arginfo_getallheaders)
ZEND_FE(frankenphp_response_headers, arginfo_frankenphp_response_headers)
ZEND_FALIAS(apache_response_headers, frankenphp_response_headers, arginfo_apache_response_headers)
ZEND_FE(frankenphp_set_caddy_placeholder, arginfo_frankenphp_set_caddy_placeholder)
ZEND_FE_END
};
// clang-format on
Loading