Skip to content
Draft
Show file tree
Hide file tree
Changes from 4 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
25 changes: 25 additions & 0 deletions debugstate.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
package frankenphp

// #include "frankenphp.h"
import "C"
import (
"unsafe"
)

// EXPERIMENTAL: ThreadDebugState prints the state of a single PHP thread - debugging purposes only
type ThreadDebugState struct {
Index int
Expand Down Expand Up @@ -44,3 +50,22 @@ func threadDebugState(thread *phpThread) ThreadDebugState {
WaitingSinceMilliseconds: thread.state.waitTime(),
}
}

// EXPERIMENTAL: Expose the current thread's information to PHP
//
//export go_frankenphp_info
func go_frankenphp_info(threadIndex C.uintptr_t) unsafe.Pointer {
thread := phpThreads[threadIndex]
return PHPArray(&Array{
keys: []PHPKey{
PHPKey{Type: PHPStringKey, Str: "thread_name"},
PHPKey{Type: PHPStringKey, Str: "thread_index"},
PHPKey{Type: PHPStringKey, Str: "is_worker"},
},
values: []interface{}{
thread.name(),
int(threadIndex),
thread.handler.(*workerThread) != nil,
},
})
}
10 changes: 10 additions & 0 deletions frankenphp.c
Original file line number Diff line number Diff line change
Expand Up @@ -1213,3 +1213,13 @@ void register_extensions(zend_module_entry *m, int len) {
php_register_internal_extensions_func;
php_register_internal_extensions_func = register_internal_extensions;
}

/* EXPERIMENTAL */
PHP_FUNCTION(frankenphp_info) {
if (zend_parse_parameters_none() == FAILURE) {
RETURN_THROWS();
}

zend_array *result = go_frankenphp_info(thread_index);
RETURN_ARR(result);
}
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_info(): array {}
Copy link
Member

Choose a reason for hiding this comment

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

Do we have the array shape already so it can be part of the stub?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not yet, but good point! It might also be nice to add the FrankenPHP version, but I'm currently not sure if it's even available from inside of the process.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Looking at the PHPArray function, it might also make sense to instead allow direct convertion of either a slice or map to an array (unless that's something you already considered).

func SliceToPHPArray(slice []interface{})
func MapToPHPArray(map map[string]interface{})

Copy link
Member

Choose a reason for hiding this comment

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

This is a good idea on the paper. I think that we discussed this with @dunglas and the final word is that we'd like to avoid ending with a clunky API with to many functions. I'm not exactly sure if this was the conclusion of this discussion or another, but this is something that Kevin may confirm I guess

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This would save a few functions though, since on the go side you just manipulate the slice or map.

Instead of:

type PHPKeyType int
type PHPKey struct
type Array struct
func (arr *Array) SetInt
func (arr *Array) SetString
func (arr *Array) Append
func (arr *Array) getNextIntKey
func (arr *Array) Len
func (arr *Array) At

you'd just need something like:

type Array = map[string]interface{}
func PHPArray(a Array) unsafe.Pointer
func GoArray(arr unsafe.Pointer) Array

type PackedArray = []interface{}
func PHPPackedArray(a PackedArray) unsafe.Pointer
func GoPackedArray(arr unsafe.Pointer) PackedArray

The unfortunate thing here is that the PHP Array is a union of the 'packed' and 'unpacked' hash table. Like this you leave it up to the user to determine what they need.

Would also solve the problem that currently duplicate keys are possible in the Array struct.

Copy link
Member

Choose a reason for hiding this comment

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

There's also the problem that PHP hashmaps are ordered. But maybe this is no big deal as we cannot pass arguments by reference in our case. 🤔

Copy link
Contributor Author

@AlliBalliBaba AlliBalliBaba Jul 25, 2025

Choose a reason for hiding this comment

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

Yeah it potentially looses its order in the unpacked case, but would be cleaner and more performant otherwise. Also I could just do something like this 😄

return PHPArray(&Array{
	"frankenphp_version" : C.GoString(C.frankenphp_get_version().server_version),
	"thread_name":        thread.name(),
	"thread_index":       int(threadIndex),
	"is_worker_thread":   thread.handler.(*workerThread) != nil,
	"num_threads":        mainThread.numThreads,
	"max_threads":        mainThread.maxThreads,
})

Copy link
Contributor Author

@AlliBalliBaba AlliBalliBaba Jul 25, 2025

Choose a reason for hiding this comment

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

Otherwise, if keeping the order is completely necessary, the correct data type on the go side would be an ordered map for the 'unpacked' array (sadly no built in ordered map by go)

The issues with the current implementation are mainly that duplicate keys are allowed on the go side and keys can be a mix of ints and strings. But exactly mirroring PHP Arrays probably cannot be done anyways without it getting too messy. So either mapping an array to a slice (if you need order) or a map (if you need association) seems like it makes most sense IMO.

Not sure though what others think

6 changes: 6 additions & 0 deletions frankenphp_arginfo.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,16 @@ ZEND_END_ARG_INFO()

#define arginfo_apache_response_headers arginfo_frankenphp_response_headers

ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_frankenphp_info, 0, 0, IS_ARRAY,
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_info);

// clang-format off
static const zend_function_entry ext_functions[] = {
Expand All @@ -47,6 +52,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_info, arginfo_frankenphp_info)
ZEND_FE_END
};
// clang-format on
Loading