Skip to content

Commit 83c3fc4

Browse files
committed
feat: add helpers to create PHP extensions (#1644)
* feat: add helpers to create PHP extensions * cs * feat: GoString * test * add test for RegisterExtension * cs * optimize includes * fix
1 parent cfb9d9f commit 83c3fc4

12 files changed

Lines changed: 187 additions & 0 deletions

File tree

ext.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package frankenphp
2+
3+
//#include "frankenphp.h"
4+
import "C"
5+
import (
6+
"sync"
7+
"unsafe"
8+
)
9+
10+
var (
11+
extensions []*C.zend_module_entry
12+
registerOnce sync.Once
13+
)
14+
15+
// RegisterExtension registers a new PHP extension.
16+
func RegisterExtension(me unsafe.Pointer) {
17+
extensions = append(extensions, (*C.zend_module_entry)(me))
18+
}
19+
20+
func registerExtensions() {
21+
if len(extensions) == 0 {
22+
return
23+
}
24+
25+
registerOnce.Do(func() {
26+
C.register_extensions(extensions[0], C.int(len(extensions)))
27+
extensions = nil
28+
})
29+
}

frankenphp.c

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1182,3 +1182,34 @@ int frankenphp_reset_opcache(void) {
11821182
}
11831183

11841184
int frankenphp_get_current_memory_limit() { return PG(memory_limit); }
1185+
1186+
static zend_module_entry *modules = NULL;
1187+
static int modules_len = 0;
1188+
static int (*original_php_register_internal_extensions_func)(void) = NULL;
1189+
1190+
PHPAPI int register_internal_extensions(void) {
1191+
if (original_php_register_internal_extensions_func != NULL &&
1192+
original_php_register_internal_extensions_func() != SUCCESS) {
1193+
return FAILURE;
1194+
}
1195+
1196+
for (int i = 0; i < modules_len; i++) {
1197+
if (zend_register_internal_module(&modules[i]) == NULL) {
1198+
return FAILURE;
1199+
}
1200+
}
1201+
1202+
modules = NULL;
1203+
modules_len = 0;
1204+
1205+
return SUCCESS;
1206+
}
1207+
1208+
void register_extensions(zend_module_entry *m, int len) {
1209+
modules = m;
1210+
modules_len = len;
1211+
1212+
original_php_register_internal_extensions_func =
1213+
php_register_internal_extensions_func;
1214+
php_register_internal_extensions_func = register_internal_extensions;
1215+
}

frankenphp.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,8 @@ func Init(options ...Option) error {
226226
// Docker/Moby has a similar hack: https://github.com/moby/moby/blob/d828b032a87606ae34267e349bf7f7ccb1f6495a/cmd/dockerd/docker.go#L87-L90
227227
signal.Ignore(syscall.SIGPIPE)
228228

229+
registerExtensions()
230+
229231
opt := &opt{}
230232
for _, o := range options {
231233
if err := o(opt); err != nil {

frankenphp.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
#define _FRANKENPPHP_H
33

44
#include <Zend/zend_types.h>
5+
#include <Zend/zend_modules.h>
56
#include <stdbool.h>
67
#include <stdint.h>
78

@@ -92,4 +93,6 @@ void frankenphp_register_bulk(
9293
ht_key_value_pair auth_type, ht_key_value_pair remote_ident,
9394
ht_key_value_pair request_uri);
9495

96+
void register_extensions(zend_module_entry *m, int len);
97+
9598
#endif

internal/testext/ext_test.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package testext
2+
3+
import "testing"
4+
5+
func TestRegisterExtension(t *testing.T) {
6+
testRegisterExtension(t)
7+
}

internal/testext/extension.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
#ifndef _EXTENSIONS_H
2+
#define _EXTENSIONS_H
3+
4+
#include <php.h>
5+
6+
extern zend_module_entry module1_entry;
7+
extern zend_module_entry module2_entry;
8+
9+
#endif

internal/testext/extensions.c

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
#include <php.h>
2+
#include <zend_exceptions.h>
3+
4+
#include "_cgo_export.h"
5+
6+
zend_module_entry module1_entry = {STANDARD_MODULE_HEADER,
7+
"ext1",
8+
NULL, /* Functions */
9+
NULL, /* MINIT */
10+
NULL, /* MSHUTDOWN */
11+
NULL, /* RINIT */
12+
NULL, /* RSHUTDOWN */
13+
NULL, /* MINFO */
14+
"0.1.0",
15+
STANDARD_MODULE_PROPERTIES};
16+
17+
zend_module_entry module2_entry = {STANDARD_MODULE_HEADER,
18+
"ext2",
19+
NULL, /* Functions */
20+
NULL, /* MINIT */
21+
NULL, /* MSHUTDOWN */
22+
NULL, /* RINIT */
23+
NULL, /* RSHUTDOWN */
24+
NULL, /* MINFO */
25+
"0.1.0",
26+
STANDARD_MODULE_PROPERTIES};

internal/testext/exttest.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package testext
2+
3+
//#include "extension.h"
4+
import "C"
5+
import (
6+
"github.com/dunglas/frankenphp"
7+
"github.com/stretchr/testify/assert"
8+
"github.com/stretchr/testify/require"
9+
"io"
10+
"net/http/httptest"
11+
"testing"
12+
"unsafe"
13+
)
14+
15+
func testRegisterExtension(t *testing.T) {
16+
frankenphp.RegisterExtension(unsafe.Pointer(&C.module1_entry))
17+
frankenphp.RegisterExtension(unsafe.Pointer(&C.module2_entry))
18+
19+
err := frankenphp.Init()
20+
require.Nil(t, err)
21+
defer frankenphp.Shutdown()
22+
23+
req := httptest.NewRequest("GET", "http://example.com/index.php", nil)
24+
w := httptest.NewRecorder()
25+
26+
req, err = frankenphp.NewRequestWithContext(req, frankenphp.WithRequestDocumentRoot("./testdata", false))
27+
assert.NoError(t, err)
28+
29+
err = frankenphp.ServeHTTP(w, req)
30+
assert.NoError(t, err)
31+
32+
resp := w.Result()
33+
body, _ := io.ReadAll(resp.Body)
34+
assert.Contains(t, string(body), "ext1")
35+
assert.Contains(t, string(body), "ext2")
36+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<?php
2+
3+
print_r(get_loaded_extensions());

types.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package frankenphp
2+
3+
//#include <zend.h>
4+
import "C"
5+
import "unsafe"
6+
7+
// EXPERIMENTAL: GoString converts a zend_string to a Go string without copy.
8+
func GoString(s unsafe.Pointer) string {
9+
if s == nil {
10+
return ""
11+
}
12+
13+
zendStr := (*C.zend_string)(s)
14+
15+
return C.GoStringN((*C.char)(unsafe.Pointer(&zendStr.val)), C.int(zendStr.len))
16+
}

0 commit comments

Comments
 (0)