Esta página foi traduzida pela comunidade e pode não estar atualizada. Consulte a versão de referência.
Esta página foi traduzida pela comunidade e pode não estar atualizada. Consulte a versão de referência.
Esta página foi traduzida pela comunidade e pode não estar atualizada. Consulte a versão de referência.
Com o FrankenPHP, você pode escrever extensões PHP em Go, o que permite criar funções nativas de alto desempenho que podem ser chamadas diretamente do PHP. Suas aplicações podem aproveitar qualquer biblioteca Go existente ou nova, bem como o famoso modelo de concorrência de goroutines diretamente do seu código PHP.
Escrever extensões PHP normalmente é feito em C, mas também é possível escrevê-las em outras linguagens com um pouco de trabalho extra. As extensões PHP permitem que você aproveite o poder das linguagens de baixo nível para estender as funcionalidades do PHP, por exemplo, adicionando funções nativas ou otimizando operações específicas.
Graças aos módulos Caddy, você pode escrever extensões PHP em Go e integrá-las rapidamente ao FrankenPHP.
O FrankenPHP oferece duas maneiras de criar extensões PHP em Go:
Começaremos com a abordagem do gerador, pois é a maneira mais fácil de começar, e, em seguida, mostraremos a implementação manual para aqueles que precisam de controle total.
O FrankenPHP vem com uma ferramenta que permite criar uma extensão PHP usando apenas Go. Não é necessário escrever código C ou usar CGO diretamente: o FrankenPHP também inclui uma API de tipos pública para ajudar você a escrever suas extensões em Go sem ter que se preocupar com o malabarismo de tipos entre PHP/C e Go.
Tip
Se quiser entender como as extensões podem ser escritas em Go do zero, leia a seção de implementação manual abaixo, que demonstra como escrever uma extensão PHP em Go sem usar o gerador.
Lembre-se de que esta ferramenta não é um gerador de extensões completo. Ela foi criada para ajudar você a escrever extensões simples em Go, mas não oferece os recursos mais avançados das extensões PHP. Se precisar escrever uma extensão mais complexa e otimizada, talvez seja necessário escrever algum código em C ou usar CGO diretamente.
Conforme abordado na seção de implementação manual abaixo, você precisa obter o código-fonte do PHP e criar um novo módulo Go.
O primeiro passo para escrever uma extensão PHP em Go é criar um novo módulo Go. Você pode usar o seguinte comando para isso:
go mod init github.com/<minha-conta>/<meu-modulo>
O segundo passo é obter o código-fonte do PHP para os próximos passos. Depois de obtê-los, descompacte-os no diretório de sua escolha, não dentro do seu módulo Go:
tar xf php-*
Agora tudo está configurado para escrever sua função nativa em Go.
Crie um novo arquivo chamado stringext.go
.
Nossa primeira função receberá uma string como argumento, o número de vezes que
ela será repetida, um booleano para indicar se a string deve ser invertida e
retornará a string resultante.
Deve ficar assim:
import (
"C"
"github.com/dunglas/frankenphp"
"strings"
)
//export_php:function repeat_this(string $str, int $count, bool $reverse): string
func repeat_this(s *C.zend_string, count int64, reverse bool) unsafe.Pointer {
str := frankenphp.GoString(unsafe.Pointer(s))
result := strings.Repeat(str, int(count))
if reverse {
runes := []rune(result)
for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 {
runes[i], runes[j] = runes[j], runes[i]
}
result = string(runes)
}
return frankenphp.PHPString(result, false)
}
Há duas coisas importantes a serem observadas aqui:
//export_php:function
define a assinatura da
função no PHP.
É assim que o gerador sabe como gerar a função PHP com os parâmetros e o tipo
de retorno corretos;unsafe.Pointer
.
O FrankenPHP fornece uma API para ajudar você com o malabarismo de tipos entre
C e Go.Embora o primeiro ponto fale por si, o segundo pode ser mais difícil de entender. Vamos nos aprofundar no malabarismo de tipos na próxima seção.
Embora alguns tipos de variáveis tenham a mesma representação de memória entre C/PHP e Go, alguns tipos exigem mais lógica para serem usados diretamente. Esta talvez seja a parte mais difícil quando se trata de escrever extensões, pois requer a compreensão dos componentes internos da Zend Engine e de como as variáveis são armazenadas internamente no PHP. Esta tabela resume o que você precisa saber:
Tipo PHP | Tipo Go | Conversão direta | Auxiliar de C para Go | Auxiliar de Go para C | Suporte a métodos de classe |
---|---|---|---|---|---|
int | int64 | ✅ | - | - | ✅ |
?int | *int64 | ✅ | - | - | ✅ |
float | float64 | ✅ | - | - | ✅ |
?float | *float64 | ✅ | - | - | ✅ |
bool | bool | ✅ | - | - | ✅ |
?bool | *bool | ✅ | - | - | ✅ |
?bool | *bool | ✅ | - | - | ✅ |
string /?string | *C.zend_string | ❌ | frankenphp.GoString() | frankenphp.PHPString() | ✅ |
array | frankenphp.AssociativeArray | ❌ | frankenphp.GoAssociativeArray() | frankenphp.PHPAssociativeArray() | ✅ |
array | map[string]any | ❌ | frankenphp.GoMap() | frankenphp.PHPMap() | ✅ |
array | []any | ❌ | frankenphp.GoPackedArray() | frankenphp.PHPPackedArray() | ✅ |
mixed | any | ❌ | GoValue() | PHPValue() | ❌ |
object | struct | ❌ | Ainda não implementado | Ainda não implementado | ❌ |
Note
Esta tabela ainda não é exaustiva e será completada à medida que a API de tipos do FrankenPHP se tornar mais completa.
Tipos primitivos e arrays são suportados atualmente, especificamente para métodos de classe. Objetos ainda não podem ser usados como parâmetros de métodos ou tipos de retorno.
Se você consultar o trecho de código da seção anterior, poderá ver que os
auxiliares são usados para converter o primeiro parâmetro e o valor de retorno.
O segundo e o terceiro parâmetros da nossa função repeat_this()
não precisam
ser convertidos, pois a representação em memória dos tipos subjacentes é a mesma
para C e Go.
O FrankenPHP oferece suporte nativo para arrays PHP por meio de
frankenphp.AssociativeArray
ou conversão direta para um mapa ou slice.
AssociativeArray
representa um
hashmap composto por um campo
Map: map[string]any
e um campo opcional Order: []string
(ao contrário dos
arrays associativos do PHP, os mapas em Go não são ordenados).
Se a ordem ou a associação não forem necessárias, também é possível converter
diretamente para um slice []any
ou um mapa não ordenado map[string]any
.
Criando e manipulando arrays em Go:
//export_php:function process_data_ordered(array $input): array
func process_data_ordered_map(arr *C.zval) unsafe.Pointer {
// Converte um array associativo PHP para Go, mantendo a ordem
associativeArray := frankenphp.GoAssociativeArray(unsafe.Pointer(arr))
// percorre as entradas em ordem
for _, key := range associativeArray.Order {
value, _ = associativeArray.Map[key]
// faz algo com a chave e o valor
}
// retorna um array ordenado
// se 'Order' não estiver vazio, apenas os pares chave-valor em 'Order'
// serão respeitados
return frankenphp.PHPAssociativeArray(AssociativeArray{
Map: map[string]any{
"chave1": "valor1",
"chave2": "valor2",
},
Order: []string{"chave1", "chave2"},
})
}
//export_php:function process_data_unordered(array $input): array
func process_data_unordered_map(arr *C.zval) unsafe.Pointer {
// Converte um array associativo PHP em um mapa Go sem manter a ordem
// Ignorar a ordem terá melhor desempenho
goMap := frankenphp.GoMap(unsafe.Pointer(arr))
// percorre as entradas sem nenhuma ordem específica
for key, value := range goMap {
// faz algo com a chave e o valor
}
// retorna um array não ordenado
return frankenphp.PHPMap(map[string]any{
"chave1": "valor1",
"chave2": "valor2",
})
}
//export_php:function process_data_packed(array $input): array
func process_data_packed(arr *C.zval) unsafe.Pointer {
// Converte um array compactado PHP para Go
goSlice := frankenphp.GoPackedArray(unsafe.Pointer(arr), false)
// percorre o slice em ordem
for index, value := range goSlice {
// faz algo com a chave e o valor
}
// retorna um array compactado
return frankenphp.PHPackedArray([]any{"valor1", "valor2", "value3"})
}
Principais recursos da conversão de arrays:
int64
, float64
, string
, bool
, nil
,
AssociativeArray
, map[string]any
, []any
);null
no array PHP.frankenphp.PHPAssociativeArray(arr frankenphp.AssociativeArray) unsafe.Pointer
- Converte para um array PHP ordenado com pares chave-valor;frankenphp.PHPMap(arr map[string]any) unsafe.Pointer
- Converte um mapa em
um array PHP não ordenado com pares chave-valor;frankenphp.PHPPackedArray(slice []any) unsafe.Pointer
- Converte um slice
em um array PHP compactado apenas com valores indexados;frankenphp.GoAssociativeArray(arr unsafe.Pointer, ordered bool) frankenphp.AssociativeArray
- Converte um array PHP em um AssociativeArray
Go ordenado (mapa com ordem);frankenphp.GoMap(arr unsafe.Pointer) map[string]any
- Converte um array PHP
em um mapa Go não ordenado;frankenphp.GoPackedArray(arr unsafe.Pointer) []any
- Converte um array PHP
em um slice Go.O gerador suporta a declaração de classes opacas como estruturas Go, que
podem ser usadas para criar objetos PHP.
Você pode usar o comentário de diretiva //export_php:class
para definir uma
classe PHP.
Por exemplo:
//export_php:class User
type UserStruct struct {
Name string
Age int
}
Classes Opacas são classes cuja estrutura interna (propriedades) é ocultada do código PHP. Isso significa:
$user->name
não funcionará);Essa abordagem fornece melhor encapsulamento e evita que o código PHP corrompa acidentalmente o estado interno dos seus objetos Go. Todas as interações com o objeto devem passar pelos métodos que você definir explicitamente.
Como as propriedades não são diretamente acessíveis, você deve definir
métodos para interagir com suas classes opacas.
Use a diretiva //export_php:method
para definir o comportamento:
//export_php:class User
type UserStruct struct {
Name string
Age int
}
//export_php:method User::getName(): string
func (us *UserStruct) GetUserName() unsafe.Pointer {
return frankenphp.PHPString(us.Name, false)
}
//export_php:method User::setAge(int $age): void
func (us *UserStruct) SetUserAge(age int64) {
us.Age = int(age)
}
//export_php:method User::getAge(): int
func (us *UserStruct) GetUserAge() int64 {
return int64(us.Age)
}
//export_php:method User::setNamePrefix(string $prefix = "User"): void
func (us *UserStruct) SetNamePrefix(prefix *C.zend_string) {
us.Name = frankenphp.GoString(unsafe.Pointer(prefix)) + ": " + us.Name
}
O gerador suporta parâmetros anuláveis usando o prefixo ?
em assinaturas PHP.
Quando um parâmetro é anulável, ele se torna um ponteiro na sua função Go,
permitindo que você verifique se o valor era null
no PHP:
//export_php:method User::updateInfo(?string $name, ?int $age, ?bool $active): void
func (us *UserStruct) UpdateInfo(name *C.zend_string, age *int64, active *bool) {
// Verifica se o parâmetro name foi fornecido (não nulo)
if name != nil {
us.Name = frankenphp.GoString(unsafe.Pointer(name))
}
// Verifica se o parâmetro age foi fornecido (não nulo)
if age != nil {
us.Age = int(*age)
}
// Verifique se o parâmetro active foi fornecido (não nulo)
if active != nil {
us.Active = *active
}
}
Pontos-chave sobre parâmetros anuláveis:
?int
, ?float
, ?bool
) tornam-se ponteiros
(*int64
, *float64
, *bool
) em Go;?string
) permanecem como *C.zend_string
, mas podem
ser *nil
;nil
antes de dereferenciar valores de ponteiro;null
do PHP torna-se nil
do Go - quando o PHP passa null
, sua função
em Go recebe um ponteiro nil
.Warning
Atualmente, os métodos de classe têm as seguintes limitações. Objetos não são suportados como tipos de parâmetros ou tipos de retorno. Arrays são totalmente suportados tanto para parâmetros quanto para tipos de retorno. Tipos suportados:
string
,int
,float
,bool
,array
evoid
(para tipo de retorno). Tipos de parâmetros anuláveis são totalmente suportados para todos os tipos escalares (?string
,?int
,?float
,?bool
).
Após gerar a extensão, você poderá usar a classe e seus métodos no PHP. Observe que você não pode acessar propriedades diretamente:
<?php
$user = new User();
// ✅ Isso funciona - usando métodos
$user->setAge(25);
echo $user->getName(); // Saída: (vazio, valor padrão)
echo $user->getAge(); // Saída: 25
$user->setNamePrefix("Funcionária");
// ✅ Isso também funciona - parâmetros anuláveis
$user->updateInfo("João", 30, true); // Todos os parâmetros fornecidos
$user->updateInfo("Joana", null, false); // Age é nulo
$user->updateInfo(null, 25, null); // Name e active são nulos
// ❌ Isso NÃO funcionará - acesso direto à propriedade
// echo $user->name; // Error: Cannot access private property
// $user->age = 30; // Error: Cannot access private property
Este design garante que seu código Go tenha controle total sobre como o estado do objeto é acessado e modificado, proporcionando melhor encapsulamento e segurança de tipos.
O gerador suporta a exportação de constantes Go para PHP usando duas diretivas:
//export_php:const
para constantes globais e //export_php:classconstant
para
constantes de classe.
Isso permite que você compartilhe valores de configuração, códigos de status e
outras constantes entre código Go e PHP.
Use a diretiva //export_php:const
para criar constantes PHP globais:
//export_php:const
const MAX_CONNECTIONS = 100
//export_php:const
const API_VERSION = "1.2.3"
//export_php:const
const STATUS_OK = iota
//export_php:const
const STATUS_ERROR = iota
Use a diretiva //export_php:classconstant ClassName
para criar constantes que
pertencem a uma classe PHP específica:
//export_php:classconstant User
const STATUS_ACTIVE = 1
//export_php:classconstant User
const STATUS_INACTIVE = 0
//export_php:classconstant User
const ROLE_ADMIN = "admin"
//export_php:classconstant Order
const STATE_PENDING = iota
//export_php:classconstant Order
const STATE_PROCESSING = iota
//export_php:classconstant Order
const STATE_COMPLETED = iota
Constantes de classe são acessíveis usando o escopo do nome da classe no PHP:
<?php
// Constantes globais
echo MAX_CONNECTIONS; // 100
echo API_VERSION; // "1.2.3"
// Constantes de classe
echo User::STATUS_ACTIVE; // 1
echo User::ROLE_ADMIN; // "admin"
echo Order::STATE_PENDING; // 0
A diretiva suporta vários tipos de valor, incluindo strings, inteiros,
booleanos, floats e constantes iota
.
Ao usar iota
, o gerador atribui automaticamente valores sequenciais (0, 1, 2,
etc.).
As constantes globais ficam disponíveis no seu código PHP como constantes
globais, enquanto as constantes de classe são delimitadas para suas respectivas
classes usando a visibilidade pública.
Ao usar inteiros, diferentes notações possíveis (binária, hexadecimal, octal)
são suportadas e exibidas como estão no arquivo stub do PHP.
Você pode usar constantes da mesma forma que está acostumado no código Go.
Por exemplo, vamos pegar a função repeat_this()
que declaramos anteriormente e
alterar o último argumento para um inteiro:
import (
"C"
"github.com/dunglas/frankenphp"
"strings"
)
//export_php:const
const STR_REVERSE = iota
//export_php:const
const STR_NORMAL = iota
//export_php:classconstant StringProcessor
const MODE_LOWERCASE = 1
//export_php:classconstant StringProcessor
const MODE_UPPERCASE = 2
//export_php:function repeat_this(string $str, int $count, int $mode): string
func repeat_this(s *C.zend_string, count int64, mode int) unsafe.Pointer {
str := frankenphp.GoString(unsafe.Pointer(s))
result := strings.Repeat(str, int(count))
if mode == STR_REVERSE {
// inverte a string
}
if mode == STR_NORMAL {
// sem operação, apenas para mostrar a constante
}
return frankenphp.PHPString(result, false)
}
//export_php:class StringProcessor
type StringProcessorStruct struct {
// campos internos
}
//export_php:method StringProcessor::process(string $input, int $mode): string
func (sp *StringProcessorStruct) Process(input *C.zend_string, mode int64) unsafe.Pointer {
str := frankenphp.GoString(unsafe.Pointer(input))
switch mode {
case MODE_LOWERCASE:
str = strings.ToLower(str)
case MODE_UPPERCASE:
str = strings.ToUpper(str)
}
return frankenphp.PHPString(str, false)
}
O gerador suporta a organização das funções, classes e constantes da sua
extensão PHP em um namespace usando a diretiva //export_php:namespace
.
Isso ajuda a evitar conflitos de nomenclatura e proporciona uma melhor
organização para a API da sua extensão.
Use a diretiva //export_php:namespace
no topo do seu arquivo Go para colocar
todos os símbolos exportados em um namespace específico:
//export_php:namespace My\Extension
package main
import "C"
//export_php:function hello(): string
func hello() string {
return "Olá do namespace My\\Extension!"
}
//export_php:class User
type UserStruct struct {
// campos internos
}
//export_php:method User::getName(): string
func (u *UserStruct) GetName() unsafe.Pointer {
return frankenphp.PHPString("João Ninguém", false)
}
//export_php:const
const STATUS_ACTIVE = 1
Quando um namespace é declarado, todas as funções, classes e constantes são colocadas sob esse namespace no PHP:
<?php
echo My\Extension\hello(); // "Olá do namespace My\Extension!"
$user = new My\Extension\User();
echo $user->getName(); // "João Ninguém"
echo My\Extension\STATUS_ACTIVE; // 1
\
) como separadores;É aqui que a mágica acontece e sua extensão agora pode ser gerada. Você pode executar o gerador com o seguinte comando:
GEN_STUB_SCRIPT=php-src/build/gen_stub.php frankenphp extension-init my_extension.go
Note
Não se esqueça de definir a variável de ambiente
GEN_STUB_SCRIPT
para o caminho do arquivogen_stub.php
no código-fonte PHP que você baixou anteriormente. Este é o mesmo scriptgen_stub.php
mencionado na seção de implementação manual.
Se tudo correu bem, um novo diretório chamado build
deve ter sido criado.
Este diretório contém os arquivos gerados para sua extensão, incluindo o arquivo
my_extension.go
com os stubs de funções PHP gerados.
Nossa extensão agora está pronta para ser compilada e integrada ao FrankenPHP.
Para fazer isso, consulte a documentação de compilação do
FrankenPHP para aprender como compilar o FrankenPHP.
Adicione o módulo usando a flag --with
, apontando para o caminho do seu
módulo:
CGO_ENABLED=1 \
XCADDY_GO_BUILD_FLAGS="-ldflags='-w -s' -tags=nobadger,nomysql,nopgx" \
CGO_CFLAGS=$(php-config --includes) \
CGO_LDFLAGS="$(php-config --ldflags) $(php-config --libs)" \
xcaddy build \
--output frankenphp \
--with github.com/<minha-conta>/<meu-modulo>/build
Observe que você aponta para o subdiretório /build
que foi criado durante a
etapa de geração.
Entretanto, isso não é obrigatório: você também pode copiar os arquivos gerados
para o diretório do seu módulo e apontar diretamente para ele.
Você pode criar um arquivo PHP para testar as funções e classes que criou.
Por exemplo, crie um arquivo index.php
com o seguinte conteúdo:
<?php
// Usando constantes globais
var_dump(repeat_this('Olá mundo', 5, STR_REVERSE));
// Usando constantes de classe
$processor = new StringProcessor();
echo $processor->process('Olá mundo', StringProcessor::MODE_LOWERCASE); // "olá mundo"
echo $processor->process('Olá mundo', StringProcessor::MODE_UPPERCASE); // "OLÁ MUNDO"
Depois de integrar sua extensão ao FrankenPHP, como demonstrado na seção
anterior, você pode executar este arquivo de teste usando
./frankenphp php-server
e deverá ver sua extensão funcionando.
Se você quiser entender como as extensões funcionam ou precisar de controle total sobre elas, pode escrevê-las manualmente. Essa abordagem oferece controle total, mas requer mais código boilerplate.
Veremos como escrever uma extensão PHP simples em Go que define uma nova função nativa. Essa função será chamada do PHP e disparará uma goroutine que registra uma mensagem nos logs do Caddy. Essa função não recebe parâmetros e não retorna nada.
No seu módulo, você precisa definir uma nova função nativa que será chamada do
PHP.
Para fazer isso, crie um arquivo com o nome desejado, por exemplo,
extension.go
, e adicione o seguinte código:
package ext_go
//#include "extension.h"
import "C"
import (
"unsafe"
"github.com/caddyserver/caddy/v2"
"github.com/dunglas/frankenphp"
)
func init() {
frankenphp.RegisterExtension(unsafe.Pointer(&C.ext_module_entry))
}
//export go_print_something
func go_print_something() {
go func() {
caddy.Log().Info("Olá de uma goroutine!")
}()
}
A função frankenphp.RegisterExtension()
simplifica o processo de registro de
extensões, manipulando a lógica interna de registro do PHP.
A função go_print_something
usa a diretiva //export
para indicar que estará
acessível no código C que escreveremos, graças ao CGO.
Neste exemplo, nossa nova função disparará uma goroutine que registra uma mensagem nos logs do Caddy.
Para permitir que o PHP chame nossa função, precisamos definir uma função PHP
correspondente.
Para isso, criaremos um arquivo stub, por exemplo, extension.stub.php
, que
conterá o seguinte código:
<?php
/** @generate-class-entries */
function go_print(): void {}
Este arquivo define a assinatura da função go_print()
, que será chamada do
PHP.
A diretiva @generate-class-entries
permite que o PHP gere automaticamente
entradas de função para nossa extensão.
Isso não é feito manualmente, mas usando um script fornecido no código-fonte do
PHP (certifique-se de ajustar o caminho para o script gen_stub.php
com base em
onde o código-fonte do PHP está localizado):
php ../php-src/build/gen_stub.php extension.stub.php
Este script gerará um arquivo chamado extension_arginfo.h
que contém as
informações necessárias para que o PHP saiba como definir e chamar nossa função.
Agora, precisamos escrever a ponte entre Go e C.
Crie um arquivo chamado extension.h
no diretório do seu módulo com o seguinte
conteúdo:
#ifndef _EXTENSION_H
#define _EXTENSION_H
#include <php.h>
extern zend_module_entry ext_module_entry;
#endif
Em seguida, crie um arquivo chamado extension.c
que executará as seguintes
etapas:
go_print()
;Vamos começar incluindo os cabeçalhos necessários:
#include <php.h>
#include "extension.h"
#include "extension_arginfo.h"
// Contém símbolos exportados pelo Go
#include "_cgo_export.h"
Em seguida, definimos nossa função PHP como uma função nativa da linguagem:
PHP_FUNCTION(go_print)
{
ZEND_PARSE_PARAMETERS_NONE();
go_print_something();
}
zend_module_entry ext_module_entry = {
STANDARD_MODULE_HEADER,
"ext_go",
ext_functions, /* Funções */
NULL, /* MINIT */
NULL, /* MSHUTDOWN */
NULL, /* RINIT */
NULL, /* RSHUTDOWN */
NULL, /* MINFO */
"0.1.1",
STANDARD_MODULE_PROPERTIES
};
Neste caso, nossa função não recebe parâmetros e não retorna nada.
Ela simplesmente chama a função Go que definimos anteriormente, exportada usando
a diretiva //export
.
Finalmente, definimos os metadados da extensão em uma estrutura
zend_module_entry
, como seu nome, versão e propriedades.
Essas informações são necessárias para que o PHP reconheça e carregue nossa
extensão.
Observe que ext_functions
é um array de ponteiros para as funções PHP que
definimos e foi gerado automaticamente pelo script gen_stub.php
no arquivo
extension_arginfo.h
.
O registro da extensão é tratado automaticamente pela função
RegisterExtension()
do FrankenPHP, que chamamos em nosso código Go.
Agora que sabemos como criar uma extensão PHP básica em Go, vamos tornar nosso exemplo mais complexo. Agora, criaremos uma função PHP que recebe uma string como parâmetro e retorna sua versão em letras maiúsculas.
Para definir a nova função PHP, modificaremos nosso arquivo extension.stub.php
para incluir a nova assinatura da função:
<?php
/** @generate-class-entries */
/**
* Converte uma string para letras maiúsculas.
*
* @param string $string A string a ser convertida.
* @return string A versão em letras maiúsculas da string.
*/
function go_upper(string $string): string {}
Tip
Não negligencie a documentação das suas funções! Você provavelmente compartilhará os stubs da sua extensão com outras pessoas desenvolvedoras para documentar como usar a sua extensão e quais recursos estão disponíveis.
Ao gerar novamente o arquivo stub com o script gen_stub.php
, o arquivo
extension_arginfo.h
deverá ficar assim:
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_go_upper, 0, 1, IS_STRING, 0)
ZEND_ARG_TYPE_INFO(0, string, IS_STRING, 0)
ZEND_END_ARG_INFO()
ZEND_FUNCTION(go_upper);
static const zend_function_entry ext_functions[] = {
ZEND_FE(go_upper, arginfo_go_upper)
ZEND_FE_END
};
Podemos ver que a função go_upper
é definida com um parâmetro do tipo string
e um tipo de retorno string
.
Sua função Go não pode aceitar diretamente uma string PHP como parâmetro. Você precisa convertê-la para uma string Go. Felizmente, o FrankenPHP fornece funções auxiliares para lidar com a conversão entre strings PHP e strings Go, semelhante ao que vimos na abordagem do gerador.
O arquivo de cabeçalho permanece simples:
#ifndef _EXTENSION_H
#define _EXTENSION_H
#include <php.h>
extern zend_module_entry ext_module_entry;
#endif
Agora podemos escrever a ponte entre Go e C no nosso arquivo extension.c
.
Passaremos a string PHP diretamente para a nossa função Go:
PHP_FUNCTION(go_upper)
{
zend_string *str;
ZEND_PARSE_PARAMETERS_START(1, 1)
Z_PARAM_STR(str)
ZEND_PARSE_PARAMETERS_END();
zend_string *result = go_upper(str);
RETVAL_STR(result);
}
Você pode aprender mais sobre ZEND_PARSE_PARAMETERS_START
e análise de
parâmetros na página dedicada do
PHP Internals Book.
Aqui, informamos ao PHP que nossa função recebe um parâmetro obrigatório do tipo
string
como uma zend_string
.
Em seguida, passamos essa string diretamente para nossa função Go e retornamos o
resultado usando RETVAL_STR
.
Só resta uma coisa a fazer: implementar a função go_upper
em Go.
Nossa função Go receberá uma *C.zend_string
como parâmetro, a converterá em
uma string Go usando a função auxiliar do FrankenPHP, a processará e retornará o
resultado como uma nova *C.zend_string
.
As funções auxiliares cuidam de todo o gerenciamento de memória e da
complexidade da conversão para nós.
import "strings"
//export go_upper
func go_upper(s *C.zend_string) *C.zend_string {
str := frankenphp.GoString(unsafe.Pointer(s))
upper := strings.ToUpper(str)
return (*C.zend_string)(frankenphp.PHPString(upper, false))
}
Essa abordagem é muito mais limpa e segura do que o gerenciamento manual de
memória.
As funções auxiliares do FrankenPHP lidam automaticamente com a conversão entre
o formato zend_string
do PHP e strings em Go.
O parâmetro false
em PHPString()
indica que queremos criar uma nova string
não persistente (liberada ao final da requisição).
Tip
Neste exemplo, não realizamos nenhum tratamento de erro, mas você deve sempre verificar se os ponteiros não são
nil
e se os dados são válidos antes de usá-los em suas funções em Go.
Nossa extensão agora está pronta para ser compilada e integrada ao FrankenPHP.
Para isso, consulte a documentação de compilação do FrankenPHP
para aprender como compilar o FrankenPHP.
Adicione o módulo usando a flag --with
, apontando para o caminho do seu
módulo:
CGO_ENABLED=1 \
XCADDY_GO_BUILD_FLAGS="-ldflags='-w -s' -tags=nobadger,nomysql,nopgx" \
CGO_CFLAGS=$(php-config --includes) \
CGO_LDFLAGS="$(php-config --ldflags) $(php-config --libs)" \
xcaddy build \
--output frankenphp \
--with github.com/<minha-conta>/<meu-modulo>
Pronto! Sua extensão agora está integrada ao FrankenPHP e pode ser usada no seu código PHP.
Após integrar sua extensão ao FrankenPHP, você pode criar um arquivo index.php
com exemplos para as funções que você implementou:
<?php
// Testa a função básica
go_print();
// Testa a função avançada
echo go_upper("olá mundo") . "\n";
Agora você pode executar o FrankenPHP com este arquivo usando
./frankenphp php-server
e deverá ver sua extensão funcionando.