Skip to content

Commit 10403dd

Browse files
add integration test
1 parent 09ee393 commit 10403dd

File tree

8 files changed

+217
-275
lines changed

8 files changed

+217
-275
lines changed

docs/extensions.md

Lines changed: 13 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -221,23 +221,19 @@ To showcase this, let's create our own `array_map()` function that takes a calla
221221

222222
```go
223223
// export_php:function my_array_map(array $data, callable $callback): array
224-
func my_array_map(arr *C.zval, callback *C.zval) unsafe.Pointer {
225-
goArr := frankenphp.GoArray(unsafe.Pointer(arr))
226-
result := &frankenphp.Array{}
227-
228-
for i := uint32(0); i < goArr.Len(); i++ {
229-
key, value := goArr.At(i)
224+
func my_array_map(arr *C.zend_array, callback *C.zval) unsafe.Pointer {
225+
goSlice, err := frankenphp.GoPackedArray[any](unsafe.Pointer(arr))
226+
if err != nil {
227+
panic(err)
228+
}
230229

231-
callbackResult := frankenphp.CallPHPCallable(unsafe.Pointer(callback), []interface{}{value})
230+
result := make([]any, len(goSlice))
232231

233-
if key.Type == frankenphp.PHPIntKey {
234-
result.SetInt(key.Int, callbackResult)
235-
} else {
236-
result.SetString(key.Str, callbackResult)
237-
}
232+
for index, value := range goSlice {
233+
result[index] = frankenphp.CallPHPCallable(unsafe.Pointer(callback), []interface{}{value})
238234
}
239235

240-
return frankenphp.PHPArray(result)
236+
return frankenphp.PHPPackedArray(result)
241237
}
242238
```
243239

@@ -246,19 +242,11 @@ Notice how we use `frankenphp.CallPHPCallable()` to call the PHP callable passed
246242
```php
247243
<?php
248244

249-
$strArray = ['a' => 'hello', 'b' => 'world', 'c' => 'php'];
250-
$result = my_array_map($strArray, 'strtoupper'); // $result will be ['a' => 'HELLO', 'b' => 'WORLD', 'c' => 'PHP']
251-
252-
$arr = [1, 2, 3, 4, [5, 6]];
253-
$result = my_array_map($arr, function($item) {
254-
if (\is_array($item)) {
255-
return my_array_map($item, function($subItem) {
256-
return $subItem * 2;
257-
});
258-
}
245+
$result = my_array_map([1, 2, 3], function($x) { return $x * 2; });
246+
// $result will be [2, 4, 6]
259247

260-
return $item * 3;
261-
}); // $result will be [3, 6, 9, 12, [10, 12]]
248+
$result = my_array_map(['hello', 'world'], 'strtoupper');
249+
// $result will be ['HELLO', 'WORLD']
262250
```
263251

264252
### Declaring a Native PHP Class

docs/fr/extensions.md

Lines changed: 15 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -212,50 +212,38 @@ func process_data_packed(arr *C.zval) unsafe.Pointer {
212212

213213
### Travailler avec des Callables
214214

215-
FrankenPHP propose un moyen de travailler avec les _callables_ PHP grâce au helper `frankenphp.CallPHPCallable()`. Cela permet dappeler des fonctions ou des méthodes PHP depuis du code Go.
215+
FrankenPHP propose un moyen de travailler avec les _callables_ PHP grâce au helper `frankenphp.CallPHPCallable()`. Cela permet d'appeler des fonctions ou des méthodes PHP depuis du code Go.
216216

217217
Pour illustrer cela, créons notre propre fonction `array_map()` qui prend un _callable_ et un tableau, applique le _callable_ à chaque élément du tableau, et retourne un nouveau tableau avec les résultats :
218218

219219
```go
220220
// export_php:function my_array_map(array $data, callable $callback): array
221-
func my_array_map(arr *C.zval, callback *C.zval) unsafe.Pointer {
222-
goArr := frankenphp.GoArray(unsafe.Pointer(arr))
223-
result := &frankenphp.Array{}
224-
225-
for i := uint32(0); i < goArr.Len(); i++ {
226-
key, value := goArr.At(i)
221+
func my_array_map(arr *C.zend_array, callback *C.zval) unsafe.Pointer {
222+
goSlice, err := frankenphp.GoPackedArray[any](unsafe.Pointer(arr))
223+
if err != nil {
224+
panic(err)
225+
}
227226

228-
callbackResult := frankenphp.CallPHPCallable(unsafe.Pointer(callback), []interface{}{value})
227+
result := make([]any, len(goSlice))
229228

230-
if key.Type == frankenphp.PHPIntKey {
231-
result.SetInt(key.Int, callbackResult)
232-
} else {
233-
result.SetString(key.Str, callbackResult)
234-
}
229+
for index, value := range goSlice {
230+
result[index] = frankenphp.CallPHPCallable(unsafe.Pointer(callback), []interface{}{value})
235231
}
236232

237-
return frankenphp.PHPArray(result)
233+
return frankenphp.PHPPackedArray(result)
238234
}
239235
```
240236

241-
Remarquez comment nous utilisons `frankenphp.CallPHPCallable()` pour appeler le _callable_ PHP passé en paramètre. Cette fonction prend un pointeur vers le _callable_ et un tableau darguments, et elle retourne le résultat de lexécution du _callable_. Vous pouvez utiliser la syntaxe habituelle des _callables_ :
237+
Remarquez comment nous utilisons `frankenphp.CallPHPCallable()` pour appeler le _callable_ PHP passé en paramètre. Cette fonction prend un pointeur vers le _callable_ et un tableau d'arguments, et elle retourne le résultat de l'exécution du _callable_. Vous pouvez utiliser la syntaxe habituelle des _callables_ :
242238

243239
```php
244240
<?php
245241

246-
$strArray = ['a' => 'hello', 'b' => 'world', 'c' => 'php'];
247-
$result = my_array_map($strArray, 'strtoupper'); // $result vaudra ['a' => 'HELLO', 'b' => 'WORLD', 'c' => 'PHP']
248-
249-
$arr = [1, 2, 3, 4, [5, 6]];
250-
$result = my_array_map($arr, function($item) {
251-
if (\is_array($item)) {
252-
return my_array_map($item, function($subItem) {
253-
return $subItem * 2;
254-
});
255-
}
242+
$result = my_array_map([1, 2, 3], function($x) { return $x * 2; });
243+
// $result vaudra [2, 4, 6]
256244

257-
return $item * 3;
258-
}); // $result vaudra [3, 6, 9, 12, [10, 12]]
245+
$result = my_array_map(['hello', 'world'], 'strtoupper');
246+
// $result vaudra ['HELLO', 'WORLD']
259247
```
260248

261249
### Déclarer une Classe PHP Native

internal/extgen/integration_test.go

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,30 @@ func (s *IntegrationTestSuite) runExtensionInit(sourceFile string) error {
118118
return nil
119119
}
120120

121+
// cleanupGeneratedFiles removes generated files from the original source directory
122+
func (s *IntegrationTestSuite) cleanupGeneratedFiles(originalSourceFile string) {
123+
s.t.Helper()
124+
125+
sourceDir := filepath.Dir(originalSourceFile)
126+
baseName := SanitizePackageName(strings.TrimSuffix(filepath.Base(originalSourceFile), ".go"))
127+
128+
generatedFiles := []string{
129+
baseName + ".stub.php",
130+
baseName + "_arginfo.h",
131+
baseName + ".h",
132+
baseName + ".c",
133+
baseName + ".go",
134+
"README.md",
135+
}
136+
137+
for _, file := range generatedFiles {
138+
fullPath := filepath.Join(sourceDir, file)
139+
if _, err := os.Stat(fullPath); err == nil {
140+
os.Remove(fullPath)
141+
}
142+
}
143+
}
144+
121145
// compileFrankenPHP compiles FrankenPHP with the generated extension
122146
func (s *IntegrationTestSuite) compileFrankenPHP(moduleDir string) (string, error) {
123147
s.t.Helper()
@@ -250,6 +274,7 @@ func TestBasicFunction(t *testing.T) {
250274
sourceFile := filepath.Join("..", "..", "testdata", "integration", "basic_function.go")
251275
sourceFile, err := filepath.Abs(sourceFile)
252276
require.NoError(t, err)
277+
defer suite.cleanupGeneratedFiles(sourceFile)
253278

254279
targetFile, err := suite.createGoModule(sourceFile)
255280
require.NoError(t, err)
@@ -326,6 +351,7 @@ func TestClassMethodsIntegration(t *testing.T) {
326351
sourceFile := filepath.Join("..", "..", "testdata", "integration", "class_methods.go")
327352
sourceFile, err := filepath.Abs(sourceFile)
328353
require.NoError(t, err)
354+
defer suite.cleanupGeneratedFiles(sourceFile)
329355

330356
targetFile, err := suite.createGoModule(sourceFile)
331357
require.NoError(t, err)
@@ -437,6 +463,7 @@ func TestConstants(t *testing.T) {
437463
sourceFile := filepath.Join("..", "..", "testdata", "integration", "constants.go")
438464
sourceFile, err := filepath.Abs(sourceFile)
439465
require.NoError(t, err)
466+
defer suite.cleanupGeneratedFiles(sourceFile)
440467

441468
targetFile, err := suite.createGoModule(sourceFile)
442469
require.NoError(t, err)
@@ -536,6 +563,7 @@ func TestNamespace(t *testing.T) {
536563
sourceFile := filepath.Join("..", "..", "testdata", "integration", "namespace.go")
537564
sourceFile, err := filepath.Abs(sourceFile)
538565
require.NoError(t, err)
566+
defer suite.cleanupGeneratedFiles(sourceFile)
539567

540568
targetFile, err := suite.createGoModule(sourceFile)
541569
require.NoError(t, err)
@@ -625,6 +653,7 @@ func TestInvalidSignature(t *testing.T) {
625653
sourceFile := filepath.Join("..", "..", "testdata", "integration", "invalid_signature.go")
626654
sourceFile, err := filepath.Abs(sourceFile)
627655
require.NoError(t, err)
656+
defer suite.cleanupGeneratedFiles(sourceFile)
628657

629658
targetFile, err := suite.createGoModule(sourceFile)
630659
require.NoError(t, err)
@@ -640,6 +669,7 @@ func TestTypeMismatch(t *testing.T) {
640669
sourceFile := filepath.Join("..", "..", "testdata", "integration", "type_mismatch.go")
641670
sourceFile, err := filepath.Abs(sourceFile)
642671
require.NoError(t, err)
672+
defer suite.cleanupGeneratedFiles(sourceFile)
643673

644674
targetFile, err := suite.createGoModule(sourceFile)
645675
require.NoError(t, err)
@@ -681,3 +711,83 @@ func dummy() {}
681711
assert.Error(t, err, "should fail when gen_stub.php is missing")
682712
assert.Contains(t, err.Error(), "gen_stub.php", "error should mention missing script")
683713
}
714+
715+
func TestCallable(t *testing.T) {
716+
suite := setupTest(t)
717+
718+
sourceFile := filepath.Join("..", "..", "testdata", "integration", "callable.go")
719+
sourceFile, err := filepath.Abs(sourceFile)
720+
require.NoError(t, err)
721+
defer suite.cleanupGeneratedFiles(sourceFile)
722+
723+
targetFile, err := suite.createGoModule(sourceFile)
724+
require.NoError(t, err)
725+
726+
err = suite.runExtensionInit(targetFile)
727+
require.NoError(t, err)
728+
729+
_, err = suite.compileFrankenPHP(filepath.Dir(targetFile))
730+
require.NoError(t, err)
731+
732+
err = suite.verifyPHPSymbols(
733+
[]string{"my_array_map", "my_filter"},
734+
[]string{"Processor"},
735+
[]string{},
736+
)
737+
require.NoError(t, err, "all functions and classes should be accessible from PHP")
738+
739+
err = suite.verifyFunctionBehavior(`<?php
740+
741+
$result = my_array_map([1, 2, 3], function($x) { return $x * 2; });
742+
if ($result !== [2, 4, 6]) {
743+
echo "FAIL: my_array_map with closure expected [2, 4, 6], got " . json_encode($result);
744+
exit(1);
745+
}
746+
747+
$result = my_array_map(['hello', 'world'], 'strtoupper');
748+
if ($result !== ['HELLO', 'WORLD']) {
749+
echo "FAIL: my_array_map with function name expected ['HELLO', 'WORLD'], got " . json_encode($result);
750+
exit(1);
751+
}
752+
753+
$result = my_array_map([], function($x) { return $x; });
754+
if ($result !== []) {
755+
echo "FAIL: my_array_map with empty array expected [], got " . json_encode($result);
756+
exit(1);
757+
}
758+
759+
$result = my_filter([1, 2, 3, 4, 5, 6], function($x) { return $x % 2 === 0; });
760+
if ($result !== [2, 4, 6]) {
761+
echo "FAIL: my_filter expected [2, 4, 6], got " . json_encode($result);
762+
exit(1);
763+
}
764+
765+
$result = my_filter([1, 2, 3, 4], null);
766+
if ($result !== [1, 2, 3, 4]) {
767+
echo "FAIL: my_filter with null callback expected [1, 2, 3, 4], got " . json_encode($result);
768+
exit(1);
769+
}
770+
771+
$processor = new Processor();
772+
$result = $processor->transform('hello', function($s) { return strtoupper($s); });
773+
if ($result !== 'HELLO') {
774+
echo "FAIL: Processor::transform with closure expected 'HELLO', got '$result'";
775+
exit(1);
776+
}
777+
778+
$result = $processor->transform('world', 'strtoupper');
779+
if ($result !== 'WORLD') {
780+
echo "FAIL: Processor::transform with function name expected 'WORLD', got '$result'";
781+
exit(1);
782+
}
783+
784+
$result = $processor->transform(' test ', 'trim');
785+
if ($result !== 'test') {
786+
echo "FAIL: Processor::transform with trim expected 'test', got '$result'";
787+
exit(1);
788+
}
789+
790+
echo "OK";
791+
`, "OK")
792+
require.NoError(t, err, "all callable tests should pass")
793+
}

internal/extgen/paramparser_test.go

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -145,14 +145,14 @@ func TestParameterParser_GenerateParamDeclarations(t *testing.T) {
145145
params: []phpParameter{
146146
{Name: "items", PhpType: phpArray, HasDefault: false},
147147
},
148-
expected: " zval *items = NULL;",
148+
expected: " zend_array *items = NULL;",
149149
},
150150
{
151151
name: "nullable array parameter",
152152
params: []phpParameter{
153153
{Name: "items", PhpType: phpArray, HasDefault: false, IsNullable: true},
154154
},
155-
expected: " zval *items = NULL;",
155+
expected: " zend_array *items = NULL;",
156156
},
157157
{
158158
name: "mixed types with array",
@@ -161,7 +161,7 @@ func TestParameterParser_GenerateParamDeclarations(t *testing.T) {
161161
{Name: "items", PhpType: phpArray, HasDefault: false},
162162
{Name: "count", PhpType: phpInt, HasDefault: true, DefaultValue: "5"},
163163
},
164-
expected: " zend_string *name = NULL;\n zval *items = NULL;\n zend_long count = 5;",
164+
expected: " zend_string *name = NULL;\n zend_array *items = NULL;\n zend_long count = 5;",
165165
},
166166
{
167167
name: "mixed parameter",
@@ -198,7 +198,7 @@ func TestParameterParser_GenerateParamDeclarations(t *testing.T) {
198198
{Name: "callback", PhpType: phpCallable, HasDefault: false},
199199
{Name: "options", PhpType: phpInt, HasDefault: true, DefaultValue: "0"},
200200
},
201-
expected: " zval *data = NULL;\n zval *callback_callback;\n zend_long options = 0;",
201+
expected: " zend_array *data = NULL;\n zval *callback_callback;\n zend_long options = 0;",
202202
},
203203
}
204204

@@ -399,12 +399,12 @@ func TestParameterParser_GenerateParamParsingMacro(t *testing.T) {
399399
{
400400
name: "array parameter",
401401
param: phpParameter{Name: "items", PhpType: phpArray},
402-
expected: "\n Z_PARAM_ARRAY(items)",
402+
expected: "\n Z_PARAM_ARRAY_HT(items)",
403403
},
404404
{
405405
name: "nullable array parameter",
406406
param: phpParameter{Name: "items", PhpType: phpArray, IsNullable: true},
407-
expected: "\n Z_PARAM_ARRAY_OR_NULL(items)",
407+
expected: "\n Z_PARAM_ARRAY_HT_OR_NULL(items)",
408408
},
409409
{
410410
name: "mixed parameter",
@@ -617,12 +617,12 @@ func TestParameterParser_GenerateSingleParamDeclaration(t *testing.T) {
617617
{
618618
name: "array parameter",
619619
param: phpParameter{Name: "items", PhpType: phpArray, HasDefault: false},
620-
expected: []string{"zval *items = NULL;"},
620+
expected: []string{"zend_array *items = NULL;"},
621621
},
622622
{
623623
name: "nullable array parameter",
624624
param: phpParameter{Name: "items", PhpType: phpArray, HasDefault: false, IsNullable: true},
625-
expected: []string{"zval *items = NULL;"},
625+
expected: []string{"zend_array *items = NULL;"},
626626
},
627627
{
628628
name: "callable parameter",

internal/extgen/phpfunc_test.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -107,8 +107,8 @@ func TestPHPFunctionGenerator_Generate(t *testing.T) {
107107
},
108108
contains: []string{
109109
"PHP_FUNCTION(process_array)",
110-
"zval *input = NULL;",
111-
"Z_PARAM_ARRAY(input)",
110+
"zend_array *input = NULL;",
111+
"Z_PARAM_ARRAY_HT(input)",
112112
"zend_array *result = process_array(input);",
113113
"RETURN_ARR(result)",
114114
},
@@ -126,10 +126,10 @@ func TestPHPFunctionGenerator_Generate(t *testing.T) {
126126
},
127127
contains: []string{
128128
"PHP_FUNCTION(filter_array)",
129-
"zval *data = NULL;",
129+
"zend_array *data = NULL;",
130130
"zend_string *key = NULL;",
131131
"zend_long limit = 10;",
132-
"Z_PARAM_ARRAY(data)",
132+
"Z_PARAM_ARRAY_HT(data)",
133133
"Z_PARAM_STR(key)",
134134
"Z_PARAM_LONG(limit)",
135135
"ZEND_PARSE_PARAMETERS_START(2, 3)",
@@ -201,7 +201,7 @@ func TestPHPFunctionGenerator_GenerateParamDeclarations(t *testing.T) {
201201
{Name: "items", PhpType: phpArray},
202202
},
203203
contains: []string{
204-
"zval *items = NULL;",
204+
"zend_array *items = NULL;",
205205
},
206206
},
207207
{
@@ -213,7 +213,7 @@ func TestPHPFunctionGenerator_GenerateParamDeclarations(t *testing.T) {
213213
},
214214
contains: []string{
215215
"zend_string *name = NULL;",
216-
"zval *data = NULL;",
216+
"zend_array *data = NULL;",
217217
"zend_long count = 0;",
218218
},
219219
},

0 commit comments

Comments
 (0)