Skip to content
Closed
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
71 changes: 60 additions & 11 deletions docs/extensions.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,17 +81,18 @@ While the first point speaks for itself, the second may be harder to apprehend.

While some variable types have the same memory representation between C/PHP and Go, some types require more logic to be directly used. This is maybe the hardest part when it comes to writing extensions because it requires understanding internals of the Zend Engine and how variables are stored internally in PHP. This table summarizes what you need to know:

| PHP type | Go type | Direct conversion | C to Go helper | Go to C helper | Class Methods Support |
|--------------------|---------------------|-------------------|-----------------------|------------------------|-----------------------|
| `int` | `int64` | ✅ | - | - | ✅ |
| `?int` | `*int64` | ✅ | - | - | ✅ |
| `float` | `float64` | ✅ | - | - | ✅ |
| `?float` | `*float64` | ✅ | - | - | ✅ |
| `bool` | `bool` | ✅ | - | - | ✅ |
| `?bool` | `*bool` | ✅ | - | - | ✅ |
| `string`/`?string` | `*C.zend_string` | ❌ | frankenphp.GoString() | frankenphp.PHPString() | ✅ |
| `array` | `*frankenphp.Array` | ❌ | frankenphp.GoArray() | frankenphp.PHPArray() | ✅ |
| `object` | `struct` | ❌ | _Not yet implemented_ | _Not yet implemented_ | ❌ |
| PHP type | Go type | Direct conversion | C to Go helper | Go to C helper | Class Methods Support |
|--------------------|---------------------|-------------------|-----------------------|------------------------------|-----------------------|
| `int` | `int64` | ✅ | - | - | ✅ |
| `?int` | `*int64` | ✅ | - | - | ✅ |
| `float` | `float64` | ✅ | - | - | ✅ |
| `?float` | `*float64` | ✅ | - | - | ✅ |
| `bool` | `bool` | ✅ | - | - | ✅ |
| `?bool` | `*bool` | ✅ | - | - | ✅ |
| `string`/`?string` | `*C.zend_string` | ❌ | frankenphp.GoString() | frankenphp.PHPString() | ✅ |
| `array` | `*frankenphp.Array` | ❌ | frankenphp.GoArray() | frankenphp.PHPArray() | ✅ |
| `callable` | `*C.zval` | ❌ | - | frankenphp.CallPHPCallable() | ❌ |
| `object` | - | ❌ | _Not yet implemented_ | _Not yet implemented_ | ❌ |

> [!NOTE]
> This table is not exhaustive yet and will be completed as the FrankenPHP types API gets more complete.
Expand Down Expand Up @@ -151,6 +152,54 @@ func process_data(arr *C.zval) unsafe.Pointer {
* `At(index uint32) (PHPKey, interface{})` - Get key-value pair at index
* `frankenphp.PHPArray(arr *frankenphp.Array) unsafe.Pointer` - Convert to PHP array

### Working with Callables

FrankenPHP provides a way to work with PHP callables using the `frankenphp.CallPHPCallable` helper. This allows you to call PHP functions or methods from Go code.

To showcase this, let's create our own `array_map()` function that takes a callable and an array, applies the callable to each element of the array, and returns a new array with the results:

```go
// export_php:function my_array_map(array $data, callable $callback): array
func my_array_map(arr *C.zval, callback *C.zval) unsafe.Pointer {
goArr := frankenphp.GoArray(unsafe.Pointer(arr))
result := &frankenphp.Array{}

for i := uint32(0); i < goArr.Len(); i++ {
key, value := goArr.At(i)

callbackResult := frankenphp.CallPHPCallable(unsafe.Pointer(callback), []interface{}{value})

if key.Type == frankenphp.PHPIntKey {
result.SetInt(key.Int, callbackResult)
} else {
result.SetString(key.Str, callbackResult)
}
}

return frankenphp.PHPArray(result)
}
```

Notice how we use `frankenphp.CallPHPCallable()` to call the PHP callable passed as a parameter. This function takes a pointer to the callable and an array of arguments, and it returns the result of the callable execution. You can use the callable syntax you're used to:

```php
<?php

$strArray = ['a' => 'hello', 'b' => 'world', 'c' => 'php'];
$result = my_array_map($strArray, 'strtoupper'); // $result will be ['a' => 'HELLO', 'b' => 'WORLD', 'c' => 'PHP']

$arr = [1, 2, 3, 4, [5, 6]];
$result = my_array_map($arr, function($item) {
if (\is_array($item)) {
return my_array_map($item, function($subItem) {
return $subItem * 2;
});
}

return $item * 3;
}); // $result will be [3, 6, 9, 12, [10, 12]]
```

### Declaring a Native PHP Class

The generator supports declaring **opaque classes** as Go structs, which can be used to create PHP objects. You can use the `//export_php:class` directive comment to define a PHP class. For example:
Expand Down
48 changes: 48 additions & 0 deletions docs/fr/extensions.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,54 @@ func process_data(arr *C.zval) unsafe.Pointer {
* `At(index uint32) (PHPKey, interface{})` - Obtenir la paire clé-valeur à l'index
* `frankenphp.PHPArray(arr *frankenphp.Array) unsafe.Pointer` - Convertir vers un tableau PHP

### Travailler avec des Callables

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.

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 :

```go
// export_php:function my_array_map(array $data, callable $callback): array
func my_array_map(arr *C.zval, callback *C.zval) unsafe.Pointer {
goArr := frankenphp.GoArray(unsafe.Pointer(arr))
result := &frankenphp.Array{}

for i := uint32(0); i < goArr.Len(); i++ {
key, value := goArr.At(i)

callbackResult := frankenphp.CallPHPCallable(unsafe.Pointer(callback), []interface{}{value})

if key.Type == frankenphp.PHPIntKey {
result.SetInt(key.Int, callbackResult)
} else {
result.SetString(key.Str, callbackResult)
}
}

return frankenphp.PHPArray(result)
}
```

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_ :

```php
<?php

$strArray = ['a' => 'hello', 'b' => 'world', 'c' => 'php'];
$result = my_array_map($strArray, 'strtoupper'); // $result vaudra ['a' => 'HELLO', 'b' => 'WORLD', 'c' => 'PHP']

$arr = [1, 2, 3, 4, [5, 6]];
$result = my_array_map($arr, function($item) {
if (\is_array($item)) {
return my_array_map($item, function($subItem) {
return $subItem * 2;
});
}

return $item * 3;
}); // $result vaudra [3, 6, 9, 12, [10, 12]]
```

### Déclarer une Classe PHP Native

Le générateur prend en charge la déclaration de **classes opaques** comme structures Go, qui peuvent être utilisées pour créer des objets PHP. Vous pouvez utiliser la directive `//export_php:class` pour définir une classe PHP. Par exemple :
Expand Down
10 changes: 10 additions & 0 deletions internal/extgen/paramparser.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ func (pp *ParameterParser) generateSingleParamDeclaration(param phpParameter) []
}
case "array":
decls = append(decls, fmt.Sprintf("zval *%s = NULL;", param.Name))
case "callable":
decls = append(decls, fmt.Sprintf("zval *%s_callback;", param.Name))
}

return decls
Expand Down Expand Up @@ -119,6 +121,8 @@ func (pp *ParameterParser) generateParamParsingMacro(param phpParameter) string
return fmt.Sprintf("\n Z_PARAM_BOOL_OR_NULL(%s, %s_is_null)", param.Name, param.Name)
case "array":
return fmt.Sprintf("\n Z_PARAM_ARRAY_OR_NULL(%s)", param.Name)
case "callable":
return fmt.Sprintf("\n Z_PARAM_ZVAL_OR_NULL(%s_callback)", param.Name)
default:
return ""
}
Expand All @@ -134,6 +138,8 @@ func (pp *ParameterParser) generateParamParsingMacro(param phpParameter) string
return fmt.Sprintf("\n Z_PARAM_BOOL(%s)", param.Name)
case "array":
return fmt.Sprintf("\n Z_PARAM_ARRAY(%s)", param.Name)
case "callable":
return fmt.Sprintf("\n Z_PARAM_ZVAL(%s_callback)", param.Name)
default:
return ""
}
Expand Down Expand Up @@ -166,6 +172,8 @@ func (pp *ParameterParser) generateSingleGoCallParam(param phpParameter) string
return fmt.Sprintf("%s_is_null ? NULL : &%s", param.Name, param.Name)
case "array":
return param.Name
case "callable":
return fmt.Sprintf("%s_callback", param.Name)
default:
return param.Name
}
Expand All @@ -181,6 +189,8 @@ func (pp *ParameterParser) generateSingleGoCallParam(param phpParameter) string
return fmt.Sprintf("(int) %s", param.Name)
case "array":
return param.Name
case "callable":
return fmt.Sprintf("%s_callback", param.Name)
default:
return param.Name
}
Expand Down
76 changes: 76 additions & 0 deletions internal/extgen/paramparser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,29 @@ func TestParameterParser_GenerateParamDeclarations(t *testing.T) {
},
expected: " zend_string *name = NULL;\n zval *items = NULL;\n zend_long count = 5;",
},
{
name: "callable parameter",
params: []phpParameter{
{Name: "callback", PhpType: "callable", HasDefault: false},
},
expected: " zval *callback_callback;",
},
{
name: "nullable callable parameter",
params: []phpParameter{
{Name: "callback", PhpType: "callable", HasDefault: false, IsNullable: true},
},
expected: " zval *callback_callback;",
},
{
name: "mixed types with callable",
params: []phpParameter{
{Name: "data", PhpType: "array", HasDefault: false},
{Name: "callback", PhpType: "callable", HasDefault: false},
{Name: "options", PhpType: "int", HasDefault: true, DefaultValue: "0"},
},
expected: " zval *data = NULL;\n zval *callback_callback;\n zend_long options = 0;",
},
}

for _, tt := range tests {
Expand Down Expand Up @@ -278,6 +301,29 @@ func TestParameterParser_GenerateGoCallParams(t *testing.T) {
},
expected: "name, items, (long) count",
},
{
name: "callable parameter",
params: []phpParameter{
{Name: "callback", PhpType: "callable"},
},
expected: "callback_callback",
},
{
name: "nullable callable parameter",
params: []phpParameter{
{Name: "callback", PhpType: "callable", IsNullable: true},
},
expected: "callback_callback",
},
{
name: "mixed parameters with callable",
params: []phpParameter{
{Name: "data", PhpType: "array"},
{Name: "callback", PhpType: "callable"},
{Name: "limit", PhpType: "int"},
},
expected: "data, callback_callback, (long) limit",
},
}

for _, tt := range tests {
Expand Down Expand Up @@ -346,6 +392,16 @@ func TestParameterParser_GenerateParamParsingMacro(t *testing.T) {
param: phpParameter{Name: "items", PhpType: "array", IsNullable: true},
expected: "\n Z_PARAM_ARRAY_OR_NULL(items)",
},
{
name: "callable parameter",
param: phpParameter{Name: "callback", PhpType: "callable"},
expected: "\n Z_PARAM_ZVAL(callback_callback)",
},
{
name: "nullable callable parameter",
param: phpParameter{Name: "callback", PhpType: "callable", IsNullable: true},
expected: "\n Z_PARAM_ZVAL_OR_NULL(callback_callback)",
},
{
name: "unknown type",
param: phpParameter{Name: "unknown", PhpType: "unknown"},
Expand Down Expand Up @@ -456,6 +512,16 @@ func TestParameterParser_GenerateSingleGoCallParam(t *testing.T) {
param: phpParameter{Name: "items", PhpType: "array", IsNullable: true},
expected: "items",
},
{
name: "callable parameter",
param: phpParameter{Name: "callback", PhpType: "callable"},
expected: "callback_callback",
},
{
name: "nullable callable parameter",
param: phpParameter{Name: "callback", PhpType: "callable", IsNullable: true},
expected: "callback_callback",
},
{
name: "unknown type",
param: phpParameter{Name: "unknown", PhpType: "unknown"},
Expand Down Expand Up @@ -534,6 +600,16 @@ func TestParameterParser_GenerateSingleParamDeclaration(t *testing.T) {
param: phpParameter{Name: "items", PhpType: "array", HasDefault: false, IsNullable: true},
expected: []string{"zval *items = NULL;"},
},
{
name: "callable parameter",
param: phpParameter{Name: "callback", PhpType: "callable", HasDefault: false},
expected: []string{"zval *callback_callback;"},
},
{
name: "nullable callable parameter",
param: phpParameter{Name: "callback", PhpType: "callable", HasDefault: false, IsNullable: true},
expected: []string{"zval *callback_callback;"},
},
}

for _, tt := range tests {
Expand Down
Loading