From 340fe4202e18603d2d51cbe8a9693b69dccbe98d Mon Sep 17 00:00:00 2001 From: PhilDaiguille Date: Tue, 23 Dec 2025 21:39:48 +0100 Subject: [PATCH 1/5] feat: add Spanish translation --- docs/es/CONTRIBUTING.md | 220 ++++++++++ docs/es/README.md | 159 +++++++ docs/es/classic.md | 11 + docs/es/compile.md | 133 ++++++ docs/es/config.md | 347 +++++++++++++++ docs/es/docker.md | 209 +++++++++ docs/es/early-hints.md | 21 + docs/es/embed.md | 144 ++++++ docs/es/extensions.md | 893 ++++++++++++++++++++++++++++++++++++++ docs/es/github-actions.md | 31 ++ docs/es/known-issues.md | 147 +++++++ docs/es/laravel.md | 215 +++++++++ docs/es/mercure.md | 149 +++++++ docs/es/metrics.md | 17 + docs/es/performance.md | 187 ++++++++ docs/es/production.md | 144 ++++++ docs/es/static.md | 160 +++++++ docs/es/worker.md | 190 ++++++++ docs/es/x-sendfile.md | 71 +++ 19 files changed, 3448 insertions(+) create mode 100644 docs/es/CONTRIBUTING.md create mode 100644 docs/es/README.md create mode 100644 docs/es/classic.md create mode 100644 docs/es/compile.md create mode 100644 docs/es/config.md create mode 100644 docs/es/docker.md create mode 100644 docs/es/early-hints.md create mode 100644 docs/es/embed.md create mode 100644 docs/es/extensions.md create mode 100644 docs/es/github-actions.md create mode 100644 docs/es/known-issues.md create mode 100644 docs/es/laravel.md create mode 100644 docs/es/mercure.md create mode 100644 docs/es/metrics.md create mode 100644 docs/es/performance.md create mode 100644 docs/es/production.md create mode 100644 docs/es/static.md create mode 100644 docs/es/worker.md create mode 100644 docs/es/x-sendfile.md diff --git a/docs/es/CONTRIBUTING.md b/docs/es/CONTRIBUTING.md new file mode 100644 index 000000000..ab4456515 --- /dev/null +++ b/docs/es/CONTRIBUTING.md @@ -0,0 +1,220 @@ +# Contribuir + +## Compilar PHP + +### Con Docker (Linux) + +Construya la imagen Docker de desarrollo: + +```console +docker build -t frankenphp-dev -f dev.Dockerfile . +docker run --cap-add=SYS_PTRACE --security-opt seccomp=unconfined -p 8080:8080 -p 443:443 -p 443:443/udp -v $PWD:/go/src/app -it frankenphp-dev +``` + +La imagen contiene las herramientas de desarrollo habituales (Go, GDB, Valgrind, Neovim...) y utiliza las siguientes ubicaciones de configuración de PHP: + +- php.ini: `/etc/frankenphp/php.ini` Se proporciona un archivo php.ini con ajustes preestablecidos de desarrollo por defecto. +- archivos de configuración adicionales: `/etc/frankenphp/php.d/*.ini` +- extensiones php: `/usr/lib/frankenphp/modules/` + +Si su versión de Docker es inferior a 23.0, la construcción fallará debido a un [problema de patrón](https://github.com/moby/moby/pull/42676) en `.dockerignore`. Agregue los directorios a `.dockerignore`: + +```patch + !testdata/*.php + !testdata/*.txt ++!caddy ++!internal +``` + +### Sin Docker (Linux y macOS) + +[Siga las instrucciones para compilar desde las fuentes](compile.md) y pase la bandera de configuración `--debug`. + +## Ejecutar la suite de pruebas + +```console +go test -tags watcher -race -v ./... +``` + +## Módulo Caddy + +Construir Caddy con el módulo FrankenPHP: + +```console +cd caddy/frankenphp/ +go build -tags watcher,brotli,nobadger,nomysql,nopgx +cd ../../ +``` + +Ejecutar Caddy con el módulo FrankenPHP: + +```console +cd testdata/ +../caddy/frankenphp/frankenphp run +``` + +El servidor está configurado para escuchar en la dirección `127.0.0.1:80`: + +> [!NOTE] +> +> Si está usando Docker, deberá enlazar el puerto 80 del contenedor o ejecutar desde dentro del contenedor. + +```console +curl -vk http://127.0.0.1/phpinfo.php +``` + +## Servidor de prueba mínimo + +Construir el servidor de prueba mínimo: + +```console +cd internal/testserver/ +go build +cd ../../ +``` + +Iniciar el servidor de prueba: + +```console +cd testdata/ +../internal/testserver/testserver +``` + +El servidor está configurado para escuchar en la dirección `127.0.0.1:8080`: + +```console +curl -v http://127.0.0.1:8080/phpinfo.php +``` + +## Construir localmente las imágenes Docker + +Mostrar el plan de compilación: + +```console +docker buildx bake -f docker-bake.hcl --print +``` + +Construir localmente las imágenes FrankenPHP para amd64: + +```console +docker buildx bake -f docker-bake.hcl --pull --load --set "*.platform=linux/amd64" +``` + +Construir localmente las imágenes FrankenPHP para arm64: + +```console +docker buildx bake -f docker-bake.hcl --pull --load --set "*.platform=linux/arm64" +``` + +Construir desde cero las imágenes FrankenPHP para arm64 y amd64 y subirlas a Docker Hub: + +```console +docker buildx bake -f docker-bake.hcl --pull --no-cache --push +``` + +## Depurar errores de segmentación con las compilaciones estáticas + +1. Descargue la versión de depuración del binario FrankenPHP desde GitHub o cree su propia compilación estática incluyendo símbolos de depuración: + + ```console + docker buildx bake \ + --load \ + --set static-builder.args.DEBUG_SYMBOLS=1 \ + --set "static-builder.platform=linux/amd64" \ + static-builder + docker cp $(docker create --name static-builder-musl dunglas/frankenphp:static-builder-musl):/go/src/app/dist/frankenphp-linux-$(uname -m) frankenphp + ``` + +2. Reemplace su versión actual de `frankenphp` por el ejecutable de depuración de FrankenPHP. +3. Inicie FrankenPHP como de costumbre (alternativamente, puede iniciar FrankenPHP directamente con GDB: `gdb --args frankenphp run`). +4. Adjunte el proceso con GDB: + + ```console + gdb -p `pidof frankenphp` + ``` + +5. Si es necesario, escriba `continue` en el shell de GDB. +6. Haga que FrankenPHP falle. +7. Escriba `bt` en el shell de GDB. +8. Copie la salida. + +## Depurar errores de segmentación en GitHub Actions + +1. Abrir `.github/workflows/tests.yml` +2. Activar los símbolos de depuración de la biblioteca PHP: + + ```patch + - uses: shivammathur/setup-php@v2 + # ... + env: + phpts: ts + + debug: true + ``` + +3. Activar `tmate` para conectarse al contenedor: + + ```patch + - name: Set CGO flags + run: echo "CGO_CFLAGS=$(php-config --includes)" >> "$GITHUB_ENV" + + - run: | + + sudo apt install gdb + + mkdir -p /home/runner/.config/gdb/ + + printf "set auto-load safe-path /\nhandle SIG34 nostop noprint pass" > /home/runner/.config/gdb/gdbinit + + - uses: mxschmitt/action-tmate@v3 + ``` + +4. Conectarse al contenedor. +5. Abrir `frankenphp.go`. +6. Activar `cgosymbolizer`: + + ```patch + - //_ "github.com/ianlancetaylor/cgosymbolizer" + + _ "github.com/ianlancetaylor/cgosymbolizer" + ``` + +7. Descargar el módulo: `go get`. +8. Dentro del contenedor, puede usar GDB y similares: + + ```console + go test -tags watcher -c -ldflags=-w + gdb --args frankenphp.test -test.run ^MyTest$ + ``` + +9. Cuando el error esté corregido, revierta todos los cambios. + +## Recursos diversos para el desarrollo + +- [Integración de PHP en uWSGI](https://github.com/unbit/uwsgi/blob/master/plugins/php/php_plugin.c) +- [Integración de PHP en NGINX Unit](https://github.com/nginx/unit/blob/master/src/nxt_php_sapi.c) +- [Integración de PHP en Go (go-php)](https://github.com/deuill/go-php) +- [Integración de PHP en Go (GoEmPHP)](https://github.com/mikespook/goemphp) +- [Integración de PHP en C++](https://gist.github.com/paresy/3cbd4c6a469511ac7479aa0e7c42fea7) +- [Extending and Embedding PHP por Sara Golemon](https://books.google.fr/books?id=zMbGvK17_tYC&pg=PA254&lpg=PA254#v=onepage&q&f=false) +- [¿Qué es TSRMLS_CC, exactamente?](http://blog.golemon.com/2006/06/what-heck-is-tsrmlscc-anyway.html) +- [Integración de PHP en Mac](https://gist.github.com/jonnywang/61427ffc0e8dde74fff40f479d147db4) +- [Bindings SDL](https://pkg.go.dev/github.com/veandco/go-sdl2@v0.4.21/sdl#Main) + +## Recursos relacionados con Docker + +- [Definición del archivo Bake](https://docs.docker.com/build/customize/bake/file-definition/) +- [`docker buildx build`](https://docs.docker.com/engine/reference/commandline/buildx_build/) + +## Comando útil + +```console +apk add strace util-linux gdb +strace -e 'trace=!futex,epoll_ctl,epoll_pwait,tgkill,rt_sigreturn' -p 1 +``` + +## Traducir la documentación + +Para traducir la documentación y el sitio a un nuevo idioma, siga estos pasos: + +1. Cree un nuevo directorio con el código ISO de 2 caracteres del idioma en el directorio `docs/` de este repositorio. +2. Copie todos los archivos `.md` de la raíz del directorio `docs/` al nuevo directorio (siempre use la versión en inglés como fuente de traducción, ya que siempre está actualizada). +3. Copie los archivos `README.md` y `CONTRIBUTING.md` del directorio raíz al nuevo directorio. +4. Traduzca el contenido de los archivos, pero no cambie los nombres de los archivos, tampoco traduzca las cadenas que comiencen por `> [!` (es un marcado especial para GitHub). +5. Cree una Pull Request con las traducciones. +6. En el [repositorio del sitio](https://github.com/dunglas/frankenphp-website/tree/main), copie y traduzca los archivos de traducción en los directorios `content/`, `data/` y `i18n/`. +7. Traduzca los valores en el archivo YAML creado. +8. Abra una Pull Request en el repositorio del sitio. diff --git a/docs/es/README.md b/docs/es/README.md new file mode 100644 index 000000000..34826ce9d --- /dev/null +++ b/docs/es/README.md @@ -0,0 +1,159 @@ +# FrankenPHP: el servidor de aplicaciones PHP moderno, escrito en Go + +

FrankenPHP

+ +FrankenPHP es un servidor de aplicaciones moderno para PHP construido sobre el servidor web [Caddy](https://caddyserver.com/). + +FrankenPHP otorga superpoderes a tus aplicaciones PHP gracias a sus características de vanguardia: [_Early Hints_](early-hints.md), [modo worker](worker.md), [funcionalidades en tiempo real](mercure.md), HTTPS automático, soporte para HTTP/2 y HTTP/3... + +FrankenPHP funciona con cualquier aplicación PHP y hace que tus proyectos Laravel y Symfony sean más rápidos que nunca gracias a sus integraciones oficiales con el modo worker. + +FrankenPHP también puede usarse como una biblioteca Go autónoma que permite integrar PHP en cualquier aplicación usando `net/http`. + +Descubre más detalles sobre este servidor de aplicaciones en la grabación de esta conferencia dada en el Forum PHP 2022: + +Diapositivas + +## Para Comenzar + +En Windows, usa [WSL](https://learn.microsoft.com/es-es/windows/wsl/) para ejecutar FrankenPHP. + +### Script de instalación + +Puedes copiar esta línea en tu terminal para instalar automáticamente +una versión adaptada a tu plataforma: + +```console +curl https://frankenphp.dev/install.sh | sh +``` + +### Binario autónomo + +Proporcionamos binarios estáticos de FrankenPHP para desarrollo, para Linux y macOS, +conteniendo [PHP 8.4](https://www.php.net/releases/8.4/es.php) y la mayoría de las extensiones PHP populares. + +[Descargar FrankenPHP](https://github.com/php/frankenphp/releases) + +**Instalación de extensiones:** Las extensiones más comunes están incluidas. No es posible instalar más. + +### Paquetes rpm + +Nuestros mantenedores proponen paquetes rpm para todos los sistemas que usan `dnf`. Para instalar, ejecuta: + +```console +sudo dnf install https://rpm.henderkes.com/static-php-1-0.noarch.rpm +sudo dnf module enable php-zts:static-8.4 # 8.2-8.5 disponibles +sudo dnf install frankenphp +``` + +**Instalación de extensiones:** `sudo dnf install php-zts-` + +Para extensiones no disponibles por defecto, usa [PIE](https://github.com/php/pie): + +```console +sudo dnf install pie-zts +sudo pie-zts install asgrim/example-pie-extension +``` + +### Paquetes deb + +Nuestros mantenedores proponen paquetes deb para todos los sistemas que usan `apt`. Para instalar, ejecuta: + +```console +sudo curl -fsSL https://key.henderkes.com/static-php.gpg -o /usr/share/keyrings/static-php.gpg && \ +echo "deb [signed-by=/usr/share/keyrings/static-php.gpg] https://deb.henderkes.com/ stable main" | sudo tee /etc/apt/sources.list.d/static-php.list && \ +sudo apt update +sudo apt install frankenphp +``` + +**Instalación de extensiones:** `sudo apt install php-zts-` + +Para extensiones no disponibles por defecto, usa [PIE](https://github.com/php/pie): + +```console +sudo apt install pie-zts +sudo pie-zts install asgrim/example-pie-extension +``` + +### Docker + +Las [imágenes Docker](https://frankenphp.dev/docs/es/docker/) también están disponibles: + +```console +docker run -v .:/app/public \ + -p 80:80 -p 443:443 -p 443:443/udp \ + dunglas/frankenphp +``` + +Ve a `https://localhost`, ¡listo! + +> [!TIP] +> +> No intentes usar `https://127.0.0.1`. Usa `https://localhost` y acepta el certificado auto-firmado. +> Usa [la variable de entorno `SERVER_NAME`](config.md#variables-de-entorno) para cambiar el dominio a usar. + +### Homebrew + +FrankenPHP también está disponible como paquete [Homebrew](https://brew.sh) para macOS y Linux. + +Para instalarlo: + +```console +brew install dunglas/frankenphp/frankenphp +``` + +**Instalación de extensiones:** Usa [PIE](https://github.com/php/pie). + +### Uso + +Para servir el contenido del directorio actual, ejecuta: + +```console +frankenphp php-server +``` + +También puedes ejecutar scripts en línea de comandos con: + +```console +frankenphp php-cli /ruta/a/tu/script.php +``` + +Para los paquetes deb y rpm, también puedes iniciar el servicio systemd: + +```console +sudo systemctl start frankenphp +``` + +## Documentación + +- [El modo clásico](classic.md) +- [El modo worker](worker.md) +- [Soporte para Early Hints (código de estado HTTP 103)](early-hints.md) +- [Tiempo real](mercure.md) +- [Servir eficientemente archivos estáticos grandes](x-sendfile.md) +- [Configuración](config.md) +- [Escribir extensiones PHP en Go](extensions.md) +- [Imágenes Docker](docker.md) +- [Despliegue en producción](production.md) +- [Optimización del rendimiento](performance.md) +- [Crear aplicaciones PHP **autónomas**, auto-ejecutables](embed.md) +- [Crear una compilación estática](static.md) +- [Compilar desde las fuentes](compile.md) +- [Monitoreo de FrankenPHP](metrics.md) +- [Integración con Laravel](laravel.md) +- [Problemas conocidos](known-issues.md) +- [Aplicación de demostración (Symfony) y benchmarks](https://github.com/dunglas/frankenphp-demo) +- [Documentación de la biblioteca Go](https://pkg.go.dev/github.com/dunglas/frankenphp) +- [Contribuir y depurar](CONTRIBUTING.md) + +## Ejemplos y esqueletos + +- [Symfony](https://github.com/dunglas/symfony-docker) +- [API Platform](https://api-platform.com/docs/distribution/) +- [Laravel](laravel.md) +- [Sulu](https://sulu.io/blog/running-sulu-with-frankenphp) +- [WordPress](https://github.com/StephenMiracle/frankenwp) +- [Drupal](https://github.com/dunglas/frankenphp-drupal) +- [Joomla](https://github.com/alexandreelise/frankenphp-joomla) +- [TYPO3](https://github.com/ochorocho/franken-typo3) +- [Magento2](https://github.com/ekino/frankenphp-magento2) diff --git a/docs/es/classic.md b/docs/es/classic.md new file mode 100644 index 000000000..1176723ac --- /dev/null +++ b/docs/es/classic.md @@ -0,0 +1,11 @@ +# Usando el Modo Clásico + +Sin ninguna configuración adicional, FrankenPHP opera en modo clásico. En este modo, FrankenPHP funciona como un servidor PHP tradicional, sirviendo directamente archivos PHP. Esto lo convierte en un reemplazo directo para PHP-FPM o Apache con mod_php. + +Al igual que Caddy, FrankenPHP acepta un número ilimitado de conexiones y utiliza un [número fijo de hilos](config.md#caddyfile-config) para atenderlas. La cantidad de conexiones aceptadas y en cola está limitada únicamente por los recursos disponibles del sistema. +El *pool* de hilos de PHP opera con un número fijo de hilos inicializados al inicio, comparable al modo estático de PHP-FPM. También es posible permitir que los hilos [escale automáticamente en tiempo de ejecución](performance.md#max_threads), similar al modo dinámico de PHP-FPM. + +Las conexiones en cola esperarán indefinidamente hasta que un hilo de PHP esté disponible para atenderlas. Para evitar esto, puedes usar la configuración `max_wait_time` en la [configuración global de FrankenPHP](config.md#caddyfile-config) para limitar la duración que una petición puede esperar por un hilo de PHP libre antes de ser rechazada. +Adicionalmente, puedes establecer un [tiempo límite de escritura razonable en Caddy](https://caddyserver.com/docs/caddyfile/options#timeouts). + +Cada instancia de Caddy iniciará solo un *pool* de hilos de FrankenPHP, el cual será compartido entre todos los bloques `php_server`. diff --git a/docs/es/compile.md b/docs/es/compile.md new file mode 100644 index 000000000..269b0ff62 --- /dev/null +++ b/docs/es/compile.md @@ -0,0 +1,133 @@ +# Compilar desde fuentes + +Este documento explica cómo crear un binario de FrankenPHP que cargará PHP como una biblioteca dinámica. +Esta es la forma recomendada. + +Alternativamente, también se pueden crear [compilaciones estáticas y mayormente estáticas](static.md). + +## Instalar PHP + +FrankenPHP es compatible con PHP 8.2 y versiones superiores. + +### Con Homebrew (Linux y Mac) + +La forma más sencilla de instalar una versión de libphp compatible con FrankenPHP es usar los paquetes ZTS proporcionados por [Homebrew PHP](https://github.com/shivammathur/homebrew-php). + +Primero, si no lo ha hecho ya, instale [Homebrew](https://brew.sh). + +Luego, instale la variante ZTS de PHP, Brotli (opcional, para soporte de compresión) y watcher (opcional, para detección de cambios en archivos): + +```console +brew install shivammathur/php/php-zts brotli watcher +brew link --overwrite --force shivammathur/php/php-zts +``` + +### Compilando PHP + +Alternativamente, puede compilar PHP desde las fuentes con las opciones necesarias para FrankenPHP siguiendo estos pasos. + +Primero, [obtenga las fuentes de PHP](https://www.php.net/downloads.php) y extráigalas: + +```console +tar xf php-* +cd php-*/ +``` + +Luego, ejecute el script `configure` con las opciones necesarias para su plataforma. +Las siguientes banderas de `./configure` son obligatorias, pero puede agregar otras, por ejemplo, para compilar extensiones o características adicionales. + +#### Linux + +```console +./configure \ + --enable-embed \ + --enable-zts \ + --disable-zend-signals \ + --enable-zend-max-execution-timers +``` + +#### Mac + +Use el gestor de paquetes [Homebrew](https://brew.sh/) para instalar las dependencias requeridas y opcionales: + +```console +brew install libiconv bison brotli re2c pkg-config watcher +echo 'export PATH="/opt/homebrew/opt/bison/bin:$PATH"' >> ~/.zshrc +``` + +Luego ejecute el script de configuración: + +```console +./configure \ + --enable-embed \ + --enable-zts \ + --disable-zend-signals \ + --with-iconv=/opt/homebrew/opt/libiconv/ +``` + +#### Compilar PHP + +Finalmente, compile e instale PHP: + +```console +make -j"$(getconf _NPROCESSORS_ONLN)" +sudo make install +``` + +## Instalar dependencias opcionales + +Algunas características de FrankenPHP dependen de dependencias opcionales del sistema que deben instalarse. +Alternativamente, estas características pueden deshabilitarse pasando etiquetas de compilación al compilador Go. + +| Característica | Dependencia | Etiqueta de compilación para deshabilitarla | +| ----------------------------------- | ------------------------------------------------------------------------------------------------------------- | -------------------------------------------- | +| Compresión Brotli | [Brotli](https://github.com/google/brotli) | nobrotli | +| Reiniciar workers al cambiar archivos | [Watcher C](https://github.com/e-dant/watcher/tree/release/watcher-c) | nowatcher | +| [Mercure](mercure.md) | [Biblioteca Mercure Go](https://pkg.go.dev/github.com/dunglas/mercure) (instalada automáticamente, licencia AGPL) | nomercure | + +## Compilar la aplicación Go + +Ahora puede construir el binario final. + +### Usando xcaddy + +La forma recomendada es usar [xcaddy](https://github.com/caddyserver/xcaddy) para compilar FrankenPHP. +`xcaddy` también permite agregar fácilmente [módulos personalizados de Caddy](https://caddyserver.com/docs/modules/) y extensiones de FrankenPHP: + +```console +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/dunglas/frankenphp/caddy \ + --with github.com/dunglas/mercure/caddy \ + --with github.com/dunglas/vulcain/caddy \ + --with github.com/dunglas/caddy-cbrotli + # Agregue módulos adicionales de Caddy y extensiones de FrankenPHP aquí + # opcionalmente, si desea compilar desde sus fuentes de frankenphp: + # --with github.com/dunglas/frankenphp=$(pwd) \ + # --with github.com/dunglas/frankenphp/caddy=$(pwd)/caddy + +``` + +> [!TIP] +> +> Si está usando musl libc (predeterminado en Alpine Linux) y Symfony, +> es posible que deba aumentar el tamaño de pila predeterminado. +> De lo contrario, podría obtener errores como `PHP Fatal error: Maximum call stack size of 83360 bytes reached during compilation. Try splitting expression` +> +> Para hacerlo, cambie la variable de entorno `XCADDY_GO_BUILD_FLAGS` a algo como: +> `XCADDY_GO_BUILD_FLAGS=$'-ldflags "-w -s -extldflags \'-Wl,-z,stack-size=0x80000\'"'` +> (cambie el valor del tamaño de pila según las necesidades de su aplicación). + +### Sin xcaddy + +Alternativamente, es posible compilar FrankenPHP sin `xcaddy` usando directamente el comando `go`: + +```console +curl -L https://github.com/php/frankenphp/archive/refs/heads/main.tar.gz | tar xz +cd frankenphp-main/caddy/frankenphp +CGO_CFLAGS=$(php-config --includes) CGO_LDFLAGS="$(php-config --ldflags) $(php-config --libs)" go build -tags=nobadger,nomysql,nopgx +``` diff --git a/docs/es/config.md b/docs/es/config.md new file mode 100644 index 000000000..a1690f62f --- /dev/null +++ b/docs/es/config.md @@ -0,0 +1,347 @@ +# Configuración + +FrankenPHP, Caddy así como los módulos [Mercure](mercure.md) y [Vulcain](https://vulcain.rocks) pueden configurarse usando [los formatos soportados por Caddy](https://caddyserver.com/docs/getting-started#your-first-config). + +El formato más común es el `Caddyfile`, que es un formato de texto simple y legible. +Por defecto, FrankenPHP buscará un `Caddyfile` en el directorio actual. +Puede especificar una ruta personalizada con la opción `-c` o `--config`. + +Un `Caddyfile` mínimo para servir una aplicación PHP se muestra a continuación: + +```caddyfile +# El nombre de host al que responder +localhost + +# Opcionalmente, el directorio desde el que servir archivos, por defecto es el directorio actual +#root public/ +php_server +``` + +Un `Caddyfile` más avanzado que habilita más características y proporciona variables de entorno convenientes está disponible [en el repositorio de FrankenPHP](https://github.com/php/frankenphp/blob/main/caddy/frankenphp/Caddyfile), +y con las imágenes de Docker. + +PHP en sí puede configurarse [usando un archivo `php.ini`](https://www.php.net/manual/es/configuration.file.php). + +Dependiendo de su método de instalación, FrankenPHP y el intérprete de PHP buscarán archivos de configuración en las ubicaciones descritas a continuación. + +## Docker + +FrankenPHP: + +- `/etc/frankenphp/Caddyfile`: el archivo de configuración principal +- `/etc/frankenphp/Caddyfile.d/*.caddyfile`: archivos de configuración adicionales que se cargan automáticamente + +PHP: + +- `php.ini`: `/usr/local/etc/php/php.ini` (no se proporciona ningún `php.ini` por defecto) +- archivos de configuración adicionales: `/usr/local/etc/php/conf.d/*.ini` +- extensiones de PHP: `/usr/local/lib/php/extensions/no-debug-zts-/` +- Debe copiar una plantilla oficial proporcionada por el proyecto PHP: + +```dockerfile +FROM dunglas/frankenphp + +# Producción: +RUN cp $PHP_INI_DIR/php.ini-production $PHP_INI_DIR/php.ini + +# O desarrollo: +RUN cp $PHP_INI_DIR/php.ini-development $PHP_INI_DIR/php.ini +``` + +## Paquetes RPM y Debian + +FrankenPHP: + +- `/etc/frankenphp/Caddyfile`: el archivo de configuración principal +- `/etc/frankenphp/Caddyfile.d/*.caddyfile`: archivos de configuración adicionales que se cargan automáticamente + +PHP: + +- `php.ini`: `/etc/php-zts/php.ini` (se proporciona un archivo `php.ini` con ajustes de producción por defecto) +- archivos de configuración adicionales: `/etc/php-zts/conf.d/*.ini` + +## Binario estático + +FrankenPHP: + +- En el directorio de trabajo actual: `Caddyfile` + +PHP: + +- `php.ini`: El directorio en el que se ejecuta `frankenphp run` o `frankenphp php-server`, luego `/etc/frankenphp/php.ini` +- archivos de configuración adicionales: `/etc/frankenphp/php.d/*.ini` +- extensiones de PHP: no pueden cargarse, debe incluirlas en el binario mismo +- copie uno de `php.ini-production` o `php.ini-development` proporcionados [en las fuentes de PHP](https://github.com/php/php-src/). + +## Configuración de Caddyfile + +Las directivas `php_server` o `php` [de HTTP](https://caddyserver.com/docs/caddyfile/concepts#directives) pueden usarse dentro de los bloques de sitio para servir su aplicación PHP. + +Ejemplo mínimo: + +```caddyfile +localhost { + # Habilitar compresión (opcional) + encode zstd br gzip + # Ejecutar archivos PHP en el directorio actual y servir activos + php_server +} +``` + +También puede configurar explícitamente FrankenPHP usando la [opción global](https://caddyserver.com/docs/caddyfile/concepts#global-options) `frankenphp`: + +```caddyfile +{ + frankenphp { + num_threads # Establece el número de hilos de PHP para iniciar. Por defecto: 2x el número de CPUs disponibles. + max_threads # Limita el número de hilos de PHP adicionales que pueden iniciarse en tiempo de ejecución. Por defecto: num_threads. Puede establecerse como 'auto'. + max_wait_time # Establece el tiempo máximo que una solicitud puede esperar por un hilo de PHP libre antes de agotar el tiempo de espera. Por defecto: deshabilitado. + php_ini # Establece una directiva php.ini. Puede usarse varias veces para establecer múltiples directivas. + worker { + file # Establece la ruta al script del worker. + num # Establece el número de hilos de PHP para iniciar, por defecto es 2x el número de CPUs disponibles. + env # Establece una variable de entorno adicional con el valor dado. Puede especificarse más de una vez para múltiples variables de entorno. + watch # Establece la ruta para observar cambios en archivos. Puede especificarse más de una vez para múltiples rutas. + name # Establece el nombre del worker, usado en logs y métricas. Por defecto: ruta absoluta del archivo del worker + max_consecutive_failures # Establece el número máximo de fallos consecutivos antes de que el worker se considere no saludable, -1 significa que el worker siempre se reiniciará. Por defecto: 6. + } + } +} + +# ... +``` + +Alternativamente, puede usar la forma corta de una línea de la opción `worker`: + +```caddyfile +{ + frankenphp { + worker + } +} + +# ... +``` + +También puede definir múltiples workers si sirve múltiples aplicaciones en el mismo servidor: + +```caddyfile +app.example.com { + root /path/to/app/public + php_server { + root /path/to/app/public # permite un mejor almacenamiento en caché + worker index.php + } +} + +other.example.com { + root /path/to/other/public + php_server { + root /path/to/other/public + worker index.php + } +} + +# ... +``` + +Usar la directiva `php_server` es generalmente lo que necesita, +pero si necesita un control total, puede usar la directiva de bajo nivel `php`. +La directiva `php` pasa toda la entrada a PHP, en lugar de verificar primero si +es un archivo PHP o no. Lea más sobre esto en la [página de rendimiento](performance.md#try_files). + +Usar la directiva `php_server` es equivalente a esta configuración: + +```caddyfile +route { + # Agrega barra final para solicitudes de directorio + @canonicalPath { + file {path}/index.php + not path */ + } + redir @canonicalPath {path}/ 308 + # Si el archivo solicitado no existe, intenta archivos índice + @indexFiles file { + try_files {path} {path}/index.php index.php + split_path .php + } + rewrite @indexFiles {http.matchers.file.relative} + # ¡FrankenPHP! + @phpFiles path *.php + php @phpFiles + file_server +} +``` + +Las directivas `php_server` y `php` tienen las siguientes opciones: + +```caddyfile +php_server [] { + root # Establece la carpeta raíz del sitio. Por defecto: directiva `root`. + split_path # Establece las subcadenas para dividir la URI en dos partes. La primera subcadena coincidente se usará para dividir la "información de ruta" del path. La primera parte se sufija con la subcadena coincidente y se asumirá como el nombre del recurso real (script CGI). La segunda parte se establecerá como PATH_INFO para que el script la use. Por defecto: `.php` + resolve_root_symlink false # Desactiva la resolución del directorio `root` a su valor real evaluando un enlace simbólico, si existe (habilitado por defecto). + env # Establece una variable de entorno adicional con el valor dado. Puede especificarse más de una vez para múltiples variables de entorno. + file_server off # Desactiva la directiva incorporada file_server. + worker { # Crea un worker específico para este servidor. Puede especificarse más de una vez para múltiples workers. + file # Establece la ruta al script del worker, puede ser relativa a la raíz de php_server + num # Establece el número de hilos de PHP para iniciar, por defecto es 2x el número de CPUs disponibles. + name # Establece el nombre para el worker, usado en logs y métricas. Por defecto: ruta absoluta del archivo del worker. Siempre comienza con m# cuando se define en un bloque php_server. + watch # Establece la ruta para observar cambios en archivos. Puede especificarse más de una vez para múltiples rutas. + env # Establece una variable de entorno adicional con el valor dado. Puede especificarse más de una vez para múltiples variables de entorno. Las variables de entorno para este worker también se heredan del php_server padre, pero pueden sobrescribirse aquí. + match # hace coincidir el worker con un patrón de ruta. Anula try_files y solo puede usarse en la directiva php_server. + } + worker # También puede usar la forma corta como en el bloque global frankenphp. +} +``` + +### Observando cambios en archivos + +Dado que los workers solo inician su aplicación una vez y la mantienen en memoria, cualquier cambio +en sus archivos PHP no se reflejará inmediatamente. + +Los workers pueden reiniciarse al cambiar archivos mediante la directiva `watch`. +Esto es útil para entornos de desarrollo. + +```caddyfile +{ + frankenphp { + worker { + file /path/to/app/public/worker.php + watch + } + } +} +``` + +Si no se especifica el directorio `watch`, volverá a `./**/*.{php,yaml,yml,twig,env}`, +que observa todos los archivos `.php`, `.yaml`, `.yml`, `.twig` y `.env` en el directorio y subdirectorios +donde se inició el proceso de FrankenPHP. En su lugar, también puede especificar uno o más directorios mediante un +[patrón de nombre de archivo de shell](https://pkg.go.dev/path/filepath#Match): + +```caddyfile +{ + frankenphp { + worker { + file /path/to/app/public/worker.php + watch /path/to/app # observa todos los archivos en todos los subdirectorios de /path/to/app + watch /path/to/app/*.php # observa archivos que terminan en .php en /path/to/app + watch /path/to/app/**/*.php # observa archivos PHP en /path/to/app y subdirectorios + watch /path/to/app/**/*.{php,twig} # observa archivos PHP y Twig en /path/to/app y subdirectorios + } + } +} +``` + +- El patrón `**` significa observación recursiva +- Los directorios también pueden ser relativos (al lugar donde se inicia el proceso de FrankenPHP) +- Si tiene múltiples workers definidos, todos ellos se reiniciarán cuando cambie un archivo +- Tenga cuidado al observar archivos que se crean en tiempo de ejecución (como logs) ya que podrían causar reinicios no deseados de workers. + +El observador de archivos se basa en [e-dant/watcher](https://github.com/e-dant/watcher). + +## Coincidencia del worker con una ruta + +En aplicaciones PHP tradicionales, los scripts siempre se colocan en el directorio público. +Esto también es cierto para los scripts de workers, que se tratan como cualquier otro script PHP. +Si desea colocar el script del worker fuera del directorio público, puede hacerlo mediante la directiva `match`. + +La directiva `match` es una alternativa optimizada a `try_files` solo disponible dentro de `php_server` y `php`. +El siguiente ejemplo siempre servirá un archivo en el directorio público si está presente +y de lo contrario reenviará la solicitud al worker que coincida con el patrón de ruta. + +```caddyfile +{ + frankenphp { + php_server { + worker { + file /path/to/worker.php # el archivo puede estar fuera de la ruta pública + match /api/* # todas las solicitudes que comiencen con /api/ serán manejadas por este worker + } + } + } +} +``` + +## Variables de entorno + +Las siguientes variables de entorno pueden usarse para inyectar directivas de Caddy en el `Caddyfile` sin modificarlo: + +- `SERVER_NAME`: cambia [las direcciones en las que escuchar](https://caddyserver.com/docs/caddyfile/concepts#addresses), los nombres de host proporcionados también se usarán para el certificado TLS generado +- `SERVER_ROOT`: cambia el directorio raíz del sitio, por defecto es `public/` +- `CADDY_GLOBAL_OPTIONS`: inyecta [opciones globales](https://caddyserver.com/docs/caddyfile/options) +- `FRANKENPHP_CONFIG`: inyecta configuración bajo la directiva `frankenphp` + +Al igual que en FPM y SAPIs CLI, las variables de entorno se exponen por defecto en la superglobal `$_SERVER`. + +El valor `S` de [la directiva `variables_order` de PHP](https://www.php.net/manual/en/ini.core.php#ini.variables-order) siempre es equivalente a `ES` independientemente de la ubicación de `E` en otro lugar de esta directiva. + +## Configuración de PHP + +Para cargar [archivos de configuración adicionales de PHP](https://www.php.net/manual/en/configuration.file.php#configuration.file.scan), +puede usarse la variable de entorno `PHP_INI_SCAN_DIR`. +Cuando se establece, PHP cargará todos los archivos con la extensión `.ini` presentes en los directorios dados. + +También puede cambiar la configuración de PHP usando la directiva `php_ini` en el `Caddyfile`: + +```caddyfile +{ + frankenphp { + php_ini memory_limit 256M + + # o + + php_ini { + memory_limit 256M + max_execution_time 15 + } + } +} +``` + +### Deshabilitar HTTPS + +Por defecto, FrankenPHP habilitará automáticamente HTTPS para todos los nombres de host, incluyendo `localhost`. +Si desea deshabilitar HTTPS (por ejemplo en un entorno de desarrollo), puede establecer la variable de entorno `SERVER_NAME` a `http://` o `:80`: + +Alternativamente, puede usar todos los otros métodos descritos en la [documentación de Caddy](https://caddyserver.com/docs/automatic-https#activation). + +Si desea usar HTTPS con la dirección IP `127.0.0.1` en lugar del nombre de host `localhost`, lea la sección de [problemas conocidos](known-issues.md#using-https127001-with-docker). + +### Dúplex completo (HTTP/1) + +Al usar HTTP/1.x, puede ser deseable habilitar el modo dúplex completo para permitir escribir una respuesta antes de que se haya leído todo el cuerpo +(por ejemplo: [Mercure](mercure.md), WebSocket, Eventos enviados por el servidor, etc.). + +Esta es una configuración opcional que debe agregarse a las opciones globales en el `Caddyfile`: + +```caddyfile +{ + servers { + enable_full_duplex + } +} +``` + +> [!CAUTION] +> +> Habilitar esta opción puede causar que clientes HTTP/1.x antiguos que no soportan dúplex completo se bloqueen. +> Esto también puede configurarse usando la configuración de entorno `CADDY_GLOBAL_OPTIONS`: + +```sh +CADDY_GLOBAL_OPTIONS="servers { + enable_full_duplex +}" +``` + +Puede encontrar más información sobre esta configuración en la [documentación de Caddy](https://caddyserver.com/docs/caddyfile/options#enable-full-duplex). + +## Habilitar el modo de depuración + +Al usar la imagen de Docker, establezca la variable de entorno `CADDY_GLOBAL_OPTIONS` a `debug` para habilitar el modo de depuración: + +```console +docker run -v $PWD:/app/public \ + -e CADDY_GLOBAL_OPTIONS=debug \ + -p 80:80 -p 443:443 -p 443:443/udp \ + dunglas/frankenphp +``` diff --git a/docs/es/docker.md b/docs/es/docker.md new file mode 100644 index 000000000..08779b49c --- /dev/null +++ b/docs/es/docker.md @@ -0,0 +1,209 @@ +# Construir una imagen Docker personalizada + +Las [imágenes Docker de FrankenPHP](https://hub.docker.com/r/dunglas/frankenphp) están basadas en [imágenes oficiales de PHP](https://hub.docker.com/_/php/). +Se proporcionan variantes para Debian y Alpine Linux en arquitecturas populares. +Se recomiendan las variantes de Debian. + +Se proporcionan variantes para PHP 8.2, 8.3, 8.4 y 8.5. + +Las etiquetas siguen este patrón: `dunglas/frankenphp:-php-` + +- `` y `` son los números de versión de FrankenPHP y PHP respectivamente, que van desde versiones principales (ej. `1`), menores (ej. `1.2`) hasta versiones de parche (ej. `1.2.3`). +- `` es `trixie` (para Debian Trixie), `bookworm` (para Debian Bookworm) o `alpine` (para la última versión estable de Alpine). + +[Explorar etiquetas](https://hub.docker.com/r/dunglas/frankenphp/tags). + +## Cómo usar las imágenes + +Cree un archivo `Dockerfile` en su proyecto: + +```dockerfile +FROM dunglas/frankenphp + +COPY . /app/public +``` + +Luego, ejecute estos comandos para construir y ejecutar la imagen Docker: + +```console +docker build -t my-php-app . +docker run -it --rm --name my-running-app my-php-app +``` + +## Cómo ajustar la configuración + +Para mayor comodidad, se proporciona en la imagen un [archivo `Caddyfile` predeterminado](https://github.com/php/frankenphp/blob/main/caddy/frankenphp/Caddyfile) que contiene variables de entorno útiles. + +## Cómo instalar más extensiones de PHP + +El script [`docker-php-extension-installer`](https://github.com/mlocati/docker-php-extension-installer) está disponible en la imagen base. +Agregar extensiones adicionales de PHP es sencillo: + +```dockerfile +FROM dunglas/frankenphp + +# agregue extensiones adicionales aquí: +RUN install-php-extensions \ + pdo_mysql \ + gd \ + intl \ + zip \ + opcache +``` + +## Cómo instalar más módulos de Caddy + +FrankenPHP está construido sobre Caddy, y todos los [módulos de Caddy](https://caddyserver.com/docs/modules/) pueden usarse con FrankenPHP. + +La forma más fácil de instalar módulos personalizados de Caddy es usar [xcaddy](https://github.com/caddyserver/xcaddy): + +```dockerfile +FROM dunglas/frankenphp:builder AS builder + +# Copie xcaddy en la imagen del constructor +COPY --from=caddy:builder /usr/bin/xcaddy /usr/bin/xcaddy + +# CGO debe estar habilitado para construir FrankenPHP +RUN CGO_ENABLED=1 \ + XCADDY_SETCAP=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 /usr/local/bin/frankenphp \ + --with github.com/dunglas/frankenphp=./ \ + --with github.com/dunglas/frankenphp/caddy=./caddy/ \ + --with github.com/dunglas/caddy-cbrotli \ + # Mercure y Vulcain están incluidos en la compilación oficial, pero puede eliminarlos si lo desea + --with github.com/dunglas/mercure/caddy \ + --with github.com/dunglas/vulcain/caddy + # Agregue módulos adicionales de Caddy aquí + +FROM dunglas/frankenphp AS runner + +# Reemplace el binario oficial por el que contiene sus módulos personalizados +COPY --from=builder /usr/local/bin/frankenphp /usr/local/bin/frankenphp +``` + +La imagen `builder` proporcionada por FrankenPHP contiene una versión compilada de `libphp`. +Se proporcionan [imágenes de constructor](https://hub.docker.com/r/dunglas/frankenphp/tags?name=builder) para todas las versiones de FrankenPHP y PHP, tanto para Debian como para Alpine. + +> [!TIP] +> +> Si está usando Alpine Linux y Symfony, +> es posible que deba [aumentar el tamaño de pila predeterminado](compile.md#using-xcaddy). + +## Habilitar el modo Worker por defecto + +Establezca la variable de entorno `FRANKENPHP_CONFIG` para iniciar FrankenPHP con un script de worker: + +```dockerfile +FROM dunglas/frankenphp + +# ... + +ENV FRANKENPHP_CONFIG="worker ./public/index.php" +``` + +## Usar un volumen en desarrollo + +Para desarrollar fácilmente con FrankenPHP, monte el directorio de su host que contiene el código fuente de la aplicación como un volumen en el contenedor Docker: + +```console +docker run -v $PWD:/app/public -p 80:80 -p 443:443 -p 443:443/udp --tty my-php-app +``` + +> [!TIP] +> +> La opción `--tty` permite tener logs legibles en lugar de logs en formato JSON. + +Con Docker Compose: + +```yaml +# compose.yaml + +services: + php: + image: dunglas/frankenphp + # descomente la siguiente línea si desea usar un Dockerfile personalizado + #build: . + # descomente la siguiente línea si desea ejecutar esto en un entorno de producción + # restart: always + ports: + - "80:80" # HTTP + - "443:443" # HTTPS + - "443:443/udp" # HTTP/3 + volumes: + - ./:/app/public + - caddy_data:/data + - caddy_config:/config + # comente la siguiente línea en producción, permite tener logs legibles en desarrollo + tty: true + +# Volúmenes necesarios para los certificados y configuración de Caddy +volumes: + caddy_data: + caddy_config: +``` + +## Ejecutar como usuario no root + +FrankenPHP puede ejecutarse como usuario no root en Docker. + +Aquí hay un ejemplo de `Dockerfile` que hace esto: + +```dockerfile +FROM dunglas/frankenphp + +ARG USER=appuser + +RUN \ + # Use "adduser -D ${USER}" para distribuciones basadas en alpine + useradd ${USER}; \ + # Agregar capacidad adicional para enlazar a los puertos 80 y 443 + setcap CAP_NET_BIND_SERVICE=+eip /usr/local/bin/frankenphp; \ + # Dar acceso de escritura a /config/caddy y /data/caddy + chown -R ${USER}:${USER} /config/caddy /data/caddy + +USER ${USER} +``` + +### Ejecutar sin capacidades + +Incluso cuando se ejecuta sin root, FrankenPHP necesita la capacidad `CAP_NET_BIND_SERVICE` para enlazar el servidor web en puertos privilegiados (80 y 443). + +Si expone FrankenPHP en un puerto no privilegiado (1024 y superior), es posible ejecutar el servidor web como usuario no root, y sin necesidad de ninguna capacidad: + +```dockerfile +FROM dunglas/frankenphp + +ARG USER=appuser + +RUN \ + # Use "adduser -D ${USER}" para distribuciones basadas en alpine + useradd ${USER}; \ + # Eliminar la capacidad predeterminada + setcap -r /usr/local/bin/frankenphp; \ + # Dar acceso de escritura a /config/caddy y /data/caddy + chown -R ${USER}:${USER} /config/caddy /data/caddy + +USER ${USER} +``` + +Luego, establezca la variable de entorno `SERVER_NAME` para usar un puerto no privilegiado. +Ejemplo: `:8000` + +## Actualizaciones + +Las imágenes Docker se construyen: + +- Cuando se etiqueta una nueva versión +- Diariamente a las 4 am UTC, si hay nuevas versiones de las imágenes oficiales de PHP disponibles + +## Versiones de desarrollo + +Las versiones de desarrollo están disponibles en el [repositorio Docker `dunglas/frankenphp-dev`](https://hub.docker.com/repository/docker/dunglas/frankenphp-dev). +Se activa una nueva compilación cada vez que se envía un commit a la rama principal del repositorio de GitHub. + +Las etiquetas `latest*` apuntan a la cabeza de la rama `main`. +También están disponibles etiquetas de la forma `sha-`. diff --git a/docs/es/early-hints.md b/docs/es/early-hints.md new file mode 100644 index 000000000..9b929fcb2 --- /dev/null +++ b/docs/es/early-hints.md @@ -0,0 +1,21 @@ +# Early Hints (Pistas Tempranas) + +FrankenPHP soporta nativamente el [código de estado 103 Early Hints](https://developer.chrome.com/blog/early-hints/). +El uso de Early Hints puede mejorar el tiempo de carga de sus páginas web hasta en un 30%. + +```php +; rel=preload; as=style'); +headers_send(103); + +// sus algoritmos lentos y consultas SQL 🤪 + +echo <<<'HTML' + +Hola FrankenPHP + +HTML; +``` + +Early Hints están soportados tanto en el modo normal como en el modo [worker](worker.md). diff --git a/docs/es/embed.md b/docs/es/embed.md new file mode 100644 index 000000000..fa324609b --- /dev/null +++ b/docs/es/embed.md @@ -0,0 +1,144 @@ +# Aplicaciones PHP como Binarios Autónomos + +FrankenPHP tiene la capacidad de incrustar el código fuente y los activos de aplicaciones PHP en un binario estático y autónomo. + +Gracias a esta característica, las aplicaciones PHP pueden distribuirse como binarios autónomos que incluyen la aplicación en sí, el intérprete de PHP y Caddy, un servidor web de nivel de producción. + +Obtenga más información sobre esta característica [en la presentación realizada por Kévin en SymfonyCon 2023](https://dunglas.dev/2023/12/php-and-symfony-apps-as-standalone-binaries/). + +Para incrustar aplicaciones Laravel, [lea esta entrada específica de documentación](laravel.md#laravel-apps-as-standalone-binaries). + +## Preparando su Aplicación + +Antes de crear el binario autónomo, asegúrese de que su aplicación esté lista para ser incrustada. + +Por ejemplo, probablemente querrá: + +- Instalar las dependencias de producción de la aplicación +- Volcar el autoload +- Activar el modo de producción de su aplicación (si lo hay) +- Eliminar archivos innecesarios como `.git` o pruebas para reducir el tamaño de su binario final + +Por ejemplo, para una aplicación Symfony, puede usar los siguientes comandos: + +```console +# Exportar el proyecto para deshacerse de .git/, etc. +mkdir $TMPDIR/my-prepared-app +git archive HEAD | tar -x -C $TMPDIR/my-prepared-app +cd $TMPDIR/my-prepared-app + +# Establecer las variables de entorno adecuadas +echo APP_ENV=prod > .env.local +echo APP_DEBUG=0 >> .env.local + +# Eliminar las pruebas y otros archivos innecesarios para ahorrar espacio +# Alternativamente, agregue estos archivos con el atributo export-ignore en su archivo .gitattributes +rm -Rf tests/ + +# Instalar las dependencias +composer install --ignore-platform-reqs --no-dev -a + +# Optimizar .env +composer dump-env prod +``` + +### Personalizar la Configuración + +Para personalizar [la configuración](config.md), puede colocar un archivo `Caddyfile` así como un archivo `php.ini` +en el directorio principal de la aplicación a incrustar (`$TMPDIR/my-prepared-app` en el ejemplo anterior). + +## Crear un Binario para Linux + +La forma más fácil de crear un binario para Linux es usar el constructor basado en Docker que proporcionamos. + +1. Cree un archivo llamado `static-build.Dockerfile` en el repositorio de su aplicación: + + ```dockerfile + FROM --platform=linux/amd64 dunglas/frankenphp:static-builder-gnu + # Si tiene la intención de ejecutar el binario en sistemas musl-libc, use static-builder-musl en su lugar + + # Copie su aplicación + WORKDIR /go/src/app/dist/app + COPY . . + + # Construya el binario estático + WORKDIR /go/src/app/ + RUN EMBED=dist/app/ ./build-static.sh + ``` + + > [!CAUTION] + > + > Algunos archivos `.dockerignore` (por ejemplo, el [`.dockerignore` predeterminado de Symfony Docker](https://github.com/dunglas/symfony-docker/blob/main/.dockerignore)) + > ignorarán el directorio `vendor/` y los archivos `.env`. Asegúrese de ajustar o eliminar el archivo `.dockerignore` antes de la construcción. + +2. Construya: + + ```console + docker build -t static-app -f static-build.Dockerfile . + ``` + +3. Extraiga el binario: + + ```console + docker cp $(docker create --name static-app-tmp static-app):/go/src/app/dist/frankenphp-linux-x86_64 my-app ; docker rm static-app-tmp + ``` + +El binario resultante es el archivo llamado `my-app` en el directorio actual. + +## Crear un Binario para Otros Sistemas Operativos + +Si no desea usar Docker o desea construir un binario para macOS, use el script de shell que proporcionamos: + +```console +git clone https://github.com/php/frankenphp +cd frankenphp +EMBED=/path/to/your/app ./build-static.sh +``` + +El binario resultante es el archivo llamado `frankenphp--` en el directorio `dist/`. + +## Usar el Binario + +¡Listo! El archivo `my-app` (o `dist/frankenphp--` en otros sistemas operativos) contiene su aplicación autónoma. + +Para iniciar la aplicación web, ejecute: + +```console +./my-app php-server +``` + +Si su aplicación contiene un [script worker](worker.md), inicie el worker con algo como: + +```console +./my-app php-server --worker public/index.php +``` + +Para habilitar HTTPS (se crea automáticamente un certificado de Let's Encrypt), HTTP/2 y HTTP/3, especifique el nombre de dominio a usar: + +```console +./my-app php-server --domain localhost +``` + +También puede ejecutar los scripts CLI de PHP incrustados en su binario: + +```console +./my-app php-cli bin/console +``` + +## Extensiones de PHP + +Por defecto, el script construirá las extensiones requeridas por el archivo `composer.json` de su proyecto, si existe. +Si el archivo `composer.json` no existe, se construirán las extensiones predeterminadas, como se documenta en [la entrada de compilaciones estáticas](static.md). + +Para personalizar las extensiones, use la variable de entorno `PHP_EXTENSIONS`. + +## Personalizar la Compilación + +[Lea la documentación de compilación estática](static.md) para ver cómo personalizar el binario (extensiones, versión de PHP, etc.). + +## Distribuir el Binario + +En Linux, el binario creado se comprime usando [UPX](https://upx.github.io). + +En Mac, para reducir el tamaño del archivo antes de enviarlo, puede comprimirlo. +Recomendamos `xz`. diff --git a/docs/es/extensions.md b/docs/es/extensions.md new file mode 100644 index 000000000..da85623a2 --- /dev/null +++ b/docs/es/extensions.md @@ -0,0 +1,893 @@ +# Escribir Extensiones PHP en Go + +Con FrankenPHP, puedes **escribir extensiones PHP en Go**, lo que te permite crear **funciones nativas de alto rendimiento** que pueden ser llamadas directamente desde PHP. Tus aplicaciones pueden aprovechar cualquier biblioteca Go existente o nueva, así como el famoso modelo de concurrencia de **goroutines directamente desde tu código PHP**. + +Escribir extensiones PHP típicamente se hace en C, pero también es posible escribirlas en otros lenguajes con un poco de trabajo adicional. Las extensiones PHP te permiten aprovechar el poder de lenguajes de bajo nivel para extender las funcionalidades de PHP, por ejemplo, añadiendo funciones nativas o optimizando operaciones específicas. + +Gracias a los módulos de Caddy, puedes escribir extensiones PHP en Go e integrarlas muy rápidamente en FrankenPHP. + +## Dos Enfoques + +FrankenPHP proporciona dos formas de crear extensiones PHP en Go: + +1. **Usando el Generador de Extensiones** - El enfoque recomendado que genera todo el código repetitivo necesario para la mayoría de los casos de uso, permitiéndote enfocarte en escribir tu código Go. +2. **Implementación Manual** - Control total sobre la estructura de la extensión para casos de uso avanzados. + +Comenzaremos con el enfoque del generador ya que es la forma más fácil de empezar, luego mostraremos la implementación manual para aquellos que necesitan un control completo. + +## Usando el Generador de Extensiones + +FrankenPHP incluye una herramienta que te permite **crear una extensión PHP** usando solo Go. **No necesitas escribir código C** ni usar CGO directamente: FrankenPHP también incluye una **API de tipos públicos** para ayudarte a escribir tus extensiones en Go sin tener que preocuparte por **la manipulación de tipos entre PHP/C y Go**. + +> [!TIP] +> Si quieres entender cómo se pueden escribir extensiones en Go desde cero, puedes leer la sección de implementación manual a continuación que demuestra cómo escribir una extensión PHP en Go sin usar el generador. + +Ten en cuenta que esta herramienta **no es un generador de extensiones completo**. Está diseñada para ayudarte a escribir extensiones simples en Go, pero no proporciona las características más avanzadas de las extensiones PHP. Si necesitas escribir una extensión más **compleja y optimizada**, es posible que necesites escribir algo de código C o usar CGO directamente. + +### Requisitos Previos + +Como se cubre en la sección de implementación manual a continuación, necesitas [obtener las fuentes de PHP](https://www.php.net/downloads.php) y crear un nuevo módulo Go. + +#### Crear un Nuevo Módulo y Obtener las Fuentes de PHP + +El primer paso para escribir una extensión PHP en Go es crear un nuevo módulo Go. Puedes usar el siguiente comando para esto: + +```console +go mod init ejemplo.com/ejemplo +``` + +El segundo paso es [obtener las fuentes de PHP](https://www.php.net/downloads.php) para los siguientes pasos. Una vez que las tengas, descomprímelas en el directorio de tu elección, no dentro de tu módulo Go: + +```console +tar xf php-* +``` + +### Escribiendo la Extensión + +Todo está listo para escribir tu función nativa en Go. Crea un nuevo archivo llamado `stringext.go`. Nuestra primera función tomará una cadena como argumento, el número de veces para repetirla, un booleano para indicar si invertir la cadena, y devolverá la cadena resultante. Esto debería verse así: + +```go +package ejemplo + +// #include +import "C" +import ( + "strings" + "unsafe" + + "github.com/dunglas/frankenphp" +) + +//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) +} +``` + +Hay dos cosas importantes a tener en cuenta aquí: + +- Un comentario de directiva `//export_php:function` define la firma de la función en PHP. Así es como el generador sabe cómo generar la función PHP con los parámetros y tipo de retorno correctos; +- La función debe devolver un `unsafe.Pointer`. FrankenPHP proporciona una API para ayudarte con la manipulación de tipos entre C y Go. + +Mientras que el primer punto se explica por sí mismo, el segundo puede ser más difícil de entender. Profundicemos en la manipulación de tipos en la siguiente sección. + +### Manipulación de Tipos + +Aunque algunos tipos de variables tienen la misma representación en memoria entre C/PHP y Go, algunos tipos requieren más lógica para ser usados directamente. Esta es quizá la parte más difícil cuando se trata de escribir extensiones porque requiere entender los internos del motor Zend y cómo se almacenan las variables internamente en PHP. +Esta tabla resume lo que necesitas saber: + +| Tipo PHP | Tipo Go | Conversión directa | Helper de C a Go | Helper de Go a C | Soporte para Métodos de Clase | +|---------------------|--------------------------------|---------------------|---------------------------------------|----------------------------------------|-------------------------------| +| `int` | `int64` | ✅ | - | - | ✅ | +| `?int` | `*int64` | ✅ | - | - | ✅ | +| `float` | `float64` | ✅ | - | - | ✅ | +| `?float` | `*float64` | ✅ | - | - | ✅ | +| `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()` | ❌ | +| `callable` | `*C.zval` | ❌ | - | frankenphp.CallPHPCallable() | ❌ | +| `object` | `struct` | ❌ | _Aún no implementado_ | _Aún no implementado_ | ❌ | + +> [!NOTE] +> +> Esta tabla aún no es exhaustiva y se completará a medida que la API de tipos de FrankenPHP se vuelva más completa. +> +> Para métodos de clase específicamente, los tipos primitivos y los arrays están actualmente soportados. Los objetos aún no pueden usarse como parámetros de métodos o tipos de retorno. + +Si te refieres al fragmento de código de la sección anterior, puedes ver que se usan helpers para convertir el primer parámetro y el valor de retorno. El segundo y tercer parámetro de nuestra función `repeat_this()` no necesitan ser convertidos ya que la representación en memoria de los tipos subyacentes es la misma para C y Go. + +#### Trabajando con Arrays + +FrankenPHP proporciona soporte nativo para arrays PHP a través de `frankenphp.AssociativeArray` o conversión directa a un mapa o slice. + +`AssociativeArray` representa un [mapa hash](https://es.wikipedia.org/wiki/Tabla_hash) compuesto por un campo `Map: map[string]any` y un campo opcional `Order: []string` (a diferencia de los "arrays asociativos" de PHP, los mapas de Go no están ordenados). + +Si no se necesita orden o asociación, también es posible convertir directamente a un slice `[]any` o un mapa no ordenado `map[string]any`. + +**Creando y manipulando arrays en Go:** + +```go +package ejemplo + +// #include +import "C" +import ( + "unsafe" + + "github.com/dunglas/frankenphp" +) + +// export_php:function process_data_ordered(array $input): array +func process_data_ordered_map(arr *C.zend_array) unsafe.Pointer { + // Convertir array asociativo PHP a Go manteniendo el orden + associativeArray, err := frankenphp.GoAssociativeArray[any](unsafe.Pointer(arr)) + if err != nil { + // manejar error + } + + // iterar sobre las entradas en orden + for _, key := range associativeArray.Order { + value, _ = associativeArray.Map[key] + // hacer algo con key y value + } + + // devolver un array ordenado + // si 'Order' no está vacío, solo se respetarán los pares clave-valor en 'Order' + return frankenphp.PHPAssociativeArray[string](frankenphp.AssociativeArray[string]{ + Map: map[string]string{ + "clave1": "valor1", + "clave2": "valor2", + }, + Order: []string{"clave1", "clave2"}, + }) +} + +// export_php:function process_data_unordered(array $input): array +func process_data_unordered_map(arr *C.zend_array) unsafe.Pointer { + // Convertir array asociativo PHP a un mapa Go sin mantener el orden + // ignorar el orden será más eficiente + goMap, err := frankenphp.GoMap[any](unsafe.Pointer(arr)) + if err != nil { + // manejar error + } + + // iterar sobre las entradas sin un orden específico + for key, value := range goMap { + // hacer algo con key y value + } + + // devolver un array no ordenado + return frankenphp.PHPMap(map[string]string { + "clave1": "valor1", + "clave2": "valor2", + }) +} + +// export_php:function process_data_packed(array $input): array +func process_data_packed(arr *C.zend_array) unsafe.Pointer { + // Convertir array empaquetado PHP a Go + goSlice, err := frankenphp.GoPackedArray(unsafe.Pointer(arr)) + if err != nil { + // manejar error + } + + // iterar sobre el slice en orden + for index, value := range goSlice { + // hacer algo con index y value + } + + // devolver un array empaquetado + return frankenphp.PHPPackedArray([]string{"valor1", "valor2", "valor3"}) +} +``` + +**Características clave de la conversión de arrays:** + +- **Pares clave-valor ordenados** - Opción para mantener el orden del array asociativo +- **Optimizado para múltiples casos** - Opción para prescindir del orden para un mejor rendimiento o convertir directamente a un slice +- **Detección automática de listas** - Al convertir a PHP, detecta automáticamente si el array debe ser una lista empaquetada o un mapa hash +- **Arrays Anidados** - Los arrays pueden estar anidados y convertirán automáticamente todos los tipos soportados (`int64`, `float64`, `string`, `bool`, `nil`, `AssociativeArray`, `map[string]any`, `[]any`) +- **Objetos no soportados** - Actualmente, solo se pueden usar tipos escalares y arrays como valores. Proporcionar un objeto resultará en un valor `null` en el array PHP. + +##### Métodos Disponibles: Empaquetados y Asociativos + +- `frankenphp.PHPAssociativeArray(arr frankenphp.AssociativeArray) unsafe.Pointer` - Convertir a un array PHP ordenado con pares clave-valor +- `frankenphp.PHPMap(arr map[string]any) unsafe.Pointer` - Convertir un mapa a un array PHP no ordenado con pares clave-valor +- `frankenphp.PHPPackedArray(slice []any) unsafe.Pointer` - Convertir un slice a un array PHP empaquetado con solo valores indexados +- `frankenphp.GoAssociativeArray(arr unsafe.Pointer, ordered bool) frankenphp.AssociativeArray` - Convertir un array PHP a un `AssociativeArray` de Go ordenado (mapa con orden) +- `frankenphp.GoMap(arr unsafe.Pointer) map[string]any` - Convertir un array PHP a un mapa Go no ordenado +- `frankenphp.GoPackedArray(arr unsafe.Pointer) []any` - Convertir un array PHP a un slice Go +- `frankenphp.IsPacked(zval *C.zend_array) bool` - Verificar si un array PHP está empaquetado (solo indexado) o es asociativo (pares clave-valor) + +### Trabajando con Callables + +FrankenPHP proporciona una forma de trabajar con callables de PHP usando el helper `frankenphp.CallPHPCallable`. Esto te permite llamar a funciones o métodos de PHP desde código Go. + +Para mostrar esto, creemos nuestra propia función `array_map()` que toma un callable y un array, aplica el callable a cada elemento del array, y devuelve un nuevo array con los resultados: + +```go +// export_php:function my_array_map(array $data, callable $callback): array +func my_array_map(arr *C.zend_array, callback *C.zval) unsafe.Pointer { + goSlice, err := frankenphp.GoPackedArray[any](unsafe.Pointer(arr)) + if err != nil { + panic(err) + } + + result := make([]any, len(goSlice)) + + for index, value := range goSlice { + result[index] = frankenphp.CallPHPCallable(unsafe.Pointer(callback), []interface{}{value}) + } + + return frankenphp.PHPPackedArray(result) +} +``` + +Observa cómo usamos `frankenphp.CallPHPCallable()` para llamar al callable de PHP pasado como parámetro. Esta función toma un puntero al callable y un array de argumentos, y devuelve el resultado de la ejecución del callable. Puedes usar la sintaxis de callable a la que estás acostumbrado: + +```php +name` no funcionará) +- **Interfaz solo de métodos** - Todas las interacciones deben pasar a través de los métodos que defines +- **Mejor encapsulación** - La estructura de datos interna está completamente controlada por el código Go +- **Seguridad de tipos** - Sin riesgo de que el código PHP corrompa el estado interno con tipos incorrectos +- **API más limpia** - Obliga a diseñar una interfaz pública adecuada + +Este enfoque proporciona una mejor encapsulación y evita que el código PHP corrompa accidentalmente el estado interno de tus objetos Go. Todas las interacciones con el objeto deben pasar a través de los métodos que defines explícitamente. + +#### Añadiendo Métodos a las Clases + +Dado que las propiedades no son directamente accesibles, **debes definir métodos** para interactuar con tus clases opacas. Usa la directiva `//export_php:method` para definir el comportamiento: + +```go +package ejemplo + +// #include +import "C" +import ( + "unsafe" + + "github.com/dunglas/frankenphp" +) + +//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 +} +``` + +#### Parámetros Nulos + +El generador soporta parámetros nulos usando el prefijo `?` en las firmas de PHP. Cuando un parámetro es nulo, se convierte en un puntero en tu función Go, permitiéndote verificar si el valor era `null` en PHP: + +```go +package ejemplo + +// #include +import "C" +import ( + "unsafe" + + "github.com/dunglas/frankenphp" +) + +//export_php:method User::updateInfo(?string $name, ?int $age, ?bool $active): void +func (us *UserStruct) UpdateInfo(name *C.zend_string, age *int64, active *bool) { + // Verificar si se proporcionó name (no es null) + if name != nil { + us.Name = frankenphp.GoString(unsafe.Pointer(name)) + } + + // Verificar si se proporcionó age (no es null) + if age != nil { + us.Age = int(*age) + } + + // Verificar si se proporcionó active (no es null) + if active != nil { + us.Active = *active + } +} +``` + +**Puntos clave sobre parámetros nulos:** + +- **Tipos primitivos nulos** (`?int`, `?float`, `?bool`) se convierten en punteros (`*int64`, `*float64`, `*bool`) en Go +- **Strings nulos** (`?string`) permanecen como `*C.zend_string` pero pueden ser `nil` +- **Verificar `nil`** antes de desreferenciar valores de puntero +- **`null` de PHP se convierte en `nil` de Go** - cuando PHP pasa `null`, tu función Go recibe un puntero `nil` + +> [!CAUTION] +> +> Actualmente, los métodos de clase tienen las siguientes limitaciones. **Los objetos no están soportados** como tipos de parámetro o tipos de retorno. **Los arrays están completamente soportados** para ambos parámetros y tipos de retorno. Tipos soportados: `string`, `int`, `float`, `bool`, `array`, y `void` (para tipo de retorno). **Los tipos de parámetros nulos están completamente soportados** para todos los tipos escalares (`?string`, `?int`, `?float`, `?bool`). + +Después de generar la extensión, podrás usar la clase y sus métodos en PHP. Ten en cuenta que **no puedes acceder a las propiedades directamente**: + +```php +setAge(25); +echo $user->getName(); // Salida: (vacío, valor por defecto) +echo $user->getAge(); // Salida: 25 +$user->setNamePrefix("Empleado"); + +// ✅ Esto también funciona - parámetros nulos +$user->updateInfo("John", 30, true); // Todos los parámetros proporcionados +$user->updateInfo("Jane", null, false); // Age es null +$user->updateInfo(null, 25, null); // Name y active son null + +// ❌ Esto NO funcionará - acceso directo a propiedades +// echo $user->name; // Error: No se puede acceder a la propiedad privada +// $user->age = 30; // Error: No se puede acceder a la propiedad privada +``` + +Este diseño asegura que tu código Go tenga control completo sobre cómo se accede y modifica el estado del objeto, proporcionando una mejor encapsulación y seguridad de tipos. + +### Declarando Constantes + +El generador soporta exportar constantes Go a PHP usando dos directivas: `//export_php:const` para constantes globales y `//export_php:classconst` para constantes de clase. Esto te permite compartir valores de configuración, códigos de estado y otras constantes entre código Go y PHP. + +#### Constantes Globales + +Usa la directiva `//export_php:const` para crear constantes globales de PHP: + +```go +package ejemplo + +//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 +``` + +#### Constantes de Clase + +Usa la directiva `//export_php:classconst ClassName` para crear constantes que pertenecen a una clase PHP específica: + +```go +package ejemplo + +//export_php:classconst User +const STATUS_ACTIVE = 1 + +//export_php:classconst User +const STATUS_INACTIVE = 0 + +//export_php:classconst User +const ROLE_ADMIN = "admin" + +//export_php:classconst Order +const STATE_PENDING = iota + +//export_php:classconst Order +const STATE_PROCESSING = iota + +//export_php:classconst Order +const STATE_COMPLETED = iota +``` + +Las constantes de clase son accesibles usando el ámbito del nombre de clase en PHP: + +```php + +import "C" +import ( + "strings" + "unsafe" + + "github.com/dunglas/frankenphp" +) + +//export_php:const +const STR_REVERSE = iota + +//export_php:const +const STR_NORMAL = iota + +//export_php:classconst StringProcessor +const MODE_LOWERCASE = 1 + +//export_php:classconst 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 { + // invertir la cadena + } + + if mode == STR_NORMAL { + // no hacer nada, solo para mostrar la 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) +} +``` + +### Usando Espacios de Nombres + +El generador soporta organizar las funciones, clases y constantes de tu extensión PHP bajo un espacio de nombres usando la directiva `//export_php:namespace`. Esto ayuda a evitar conflictos de nombres y proporciona una mejor organización para la API de tu extensión. + +#### Declarando un Espacio de Nombres + +Usa la directiva `//export_php:namespace` al inicio de tu archivo Go para colocar todos los símbolos exportados bajo un espacio de nombres específico: + +```go +//export_php:namespace Mi\Extensión +package ejemplo + +import ( + "unsafe" + + "github.com/dunglas/frankenphp" +) + +//export_php:function hello(): string +func hello() string { + return "Hola desde el espacio de nombres Mi\\Extensión!" +} + +//export_php:class User +type UserStruct struct { + // campos internos +} + +//export_php:method User::getName(): string +func (u *UserStruct) GetName() unsafe.Pointer { + return frankenphp.PHPString("John Doe", false) +} + +//export_php:const +const STATUS_ACTIVE = 1 +``` + +#### Usando la Extensión con Espacio de Nombres en PHP + +Cuando se declara un espacio de nombres, todas las funciones, clases y constantes se colocan bajo ese espacio de nombres en PHP: + +```php +getName(); // "John Doe" + +echo Mi\Extensión\STATUS_ACTIVE; // 1 +``` + +#### Notas Importantes + +- Solo se permite **una** directiva de espacio de nombres por archivo. Si se encuentran múltiples directivas de espacio de nombres, el generador devolverá un error. +- El espacio de nombres se aplica a **todos** los símbolos exportados en el archivo: funciones, clases, métodos y constantes. +- Los nombres de espacios de nombres siguen las convenciones de espacios de nombres de PHP usando barras invertidas (`\`) como separadores. +- Si no se declara un espacio de nombres, los símbolos se exportan al espacio de nombres global como de costumbre. + +### Generando la Extensión + +Aquí es donde ocurre la magia, y tu extensión ahora puede ser generada. Puedes ejecutar el generador con el siguiente comando: + +```console +GEN_STUB_SCRIPT=php-src/build/gen_stub.php frankenphp extension-init mi_extensión.go +``` + +> [!NOTE] +> No olvides establecer la variable de entorno `GEN_STUB_SCRIPT` a la ruta del archivo `gen_stub.php` en las fuentes de PHP que descargaste anteriormente. Este es el mismo script `gen_stub.php` mencionado en la sección de implementación manual. + +Si todo salió bien, se debería haber creado un nuevo directorio llamado `build`. Este directorio contiene los archivos generados para tu extensión, incluyendo el archivo `mi_extensión.go` con los stubs de funciones PHP generadas. + +### Integrando la Extensión Generada en FrankenPHP + +Nuestra extensión ahora está lista para ser compilada e integrada en FrankenPHP. Para hacerlo, consulta la documentación de [compilación de FrankenPHP](compile.md) para aprender cómo compilar FrankenPHP. Agrega el módulo usando la bandera `--with`, apuntando a la ruta de tu módulo: + +```console +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/mi-cuenta/mi-módulo/build +``` + +Ten en cuenta que apuntas al subdirectorio `/build` que se creó durante el paso de generación. Sin embargo, esto no es obligatorio: también puedes copiar los archivos generados a tu directorio de módulo y apuntar a él directamente. + +### Probando tu Extensión Generada + +Puedes crear un archivo PHP para probar las funciones y clases que has creado. Por ejemplo, crea un archivo `index.php` con el siguiente contenido: + +```php +process('Hola Mundo', StringProcessor::MODE_LOWERCASE); // "hola mundo" +echo $processor->process('Hola Mundo', StringProcessor::MODE_UPPERCASE); // "HOLA MUNDO" +``` + +Una vez que hayas integrado tu extensión en FrankenPHP como se demostró en la sección anterior, puedes ejecutar este archivo de prueba usando `./frankenphp php-server`, y deberías ver tu extensión funcionando. + +## Implementación Manual + +Si quieres entender cómo funcionan las extensiones o necesitas un control total sobre tu extensión, puedes escribirlas manualmente. Este enfoque te da control completo pero requiere más código repetitivo. + +### Función Básica + +Veremos cómo escribir una extensión PHP simple en Go que define una nueva función nativa. Esta función será llamada desde PHP y desencadenará una goroutine que registra un mensaje en los logs de Caddy. Esta función no toma ningún parámetro y no devuelve nada. + +#### Definir la Función Go + +En tu módulo, necesitas definir una nueva función nativa que será llamada desde PHP. Para esto, crea un archivo con el nombre que desees, por ejemplo, `extension.go`, y agrega el siguiente código: + +```go +package ejemplo + +// #include "extension.h" +import "C" +import ( + "log/slog" + "unsafe" + + "github.com/dunglas/frankenphp" +) + +func init() { + frankenphp.RegisterExtension(unsafe.Pointer(&C.ext_module_entry)) +} + +//export go_print_something +func go_print_something() { + go func() { + slog.Info("¡Hola desde una goroutine!") + }() +} +``` + +La función `frankenphp.RegisterExtension()` simplifica el proceso de registro de la extensión manejando la lógica interna de registro de PHP. La función `go_print_something` usa la directiva `//export` para indicar que será accesible en el código C que escribiremos, gracias a CGO. + +En este ejemplo, nuestra nueva función desencadenará una goroutine que registra un mensaje en los logs de Caddy. + +#### Definir la Función PHP + +Para permitir que PHP llame a nuestra función, necesitamos definir una función PHP correspondiente. Para esto, crearemos un archivo stub, por ejemplo, `extension.stub.php`, que contendrá el siguiente código: + +```php + + +extern zend_module_entry ext_module_entry; + +#endif +``` + +A continuación, crea un archivo llamado `extension.c` que realizará los siguientes pasos: + +- Incluir los encabezados de PHP; +- Declarar nuestra nueva función nativa de PHP `go_print()`; +- Declarar los metadatos de la extensión. + +Comencemos incluyendo los encabezados requeridos: + +```c +#include +#include "extension.h" +#include "extension_arginfo.h" + +// Contiene símbolos exportados por Go +#include "_cgo_export.h" +``` + +Luego definimos nuestra función PHP como una función de lenguaje nativo: + +```c +PHP_FUNCTION(go_print) +{ + ZEND_PARSE_PARAMETERS_NONE(); + + go_print_something(); +} + +zend_module_entry ext_module_entry = { + STANDARD_MODULE_HEADER, + "ext_go", + ext_functions, /* Funciones */ + NULL, /* MINIT */ + NULL, /* MSHUTDOWN */ + NULL, /* RINIT */ + NULL, /* RSHUTDOWN */ + NULL, /* MINFO */ + "0.1.1", + STANDARD_MODULE_PROPERTIES +}; +``` + +En este caso, nuestra función no toma parámetros y no devuelve nada. Simplemente llama a la función Go que definimos anteriormente, exportada usando la directiva `//export`. + +Finalmente, definimos los metadatos de la extensión en una estructura `zend_module_entry`, como su nombre, versión y propiedades. Esta información es necesaria para que PHP reconozca y cargue nuestra extensión. Ten en cuenta que `ext_functions` es un array de punteros a las funciones PHP que definimos, y fue generado automáticamente por el script `gen_stub.php` en el archivo `extension_arginfo.h`. + +El registro de la extensión es manejado automáticamente por la función `RegisterExtension()` de FrankenPHP que llamamos en nuestro código Go. + +### Uso Avanzado + +Ahora que sabemos cómo crear una extensión PHP básica en Go, compliquemos nuestro ejemplo. Ahora crearemos una función PHP que tome una cadena como parámetro y devuelva su versión en mayúsculas. + +#### Definir el Stub de la Función PHP + +Para definir la nueva función PHP, modificaremos nuestro archivo `extension.stub.php` para incluir la nueva firma de la función: + +```php + [!TIP] +> ¡No descuides la documentación de tus funciones! Es probable que compartas tus stubs de extensión con otros desarrolladores para documentar cómo usar tu extensión y qué características están disponibles. + +Al regenerar el archivo stub con el script `gen_stub.php`, el archivo `extension_arginfo.h` debería verse así: + +```c +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 la función `go_upper` está definida con un parámetro de tipo `string` y un tipo de retorno `string`. + +#### Manipulación de Tipos entre Go y PHP/C + +Tu función Go no puede aceptar directamente una cadena PHP como parámetro. Necesitas convertirla a una cadena Go. Afortunadamente, FrankenPHP proporciona funciones helper para manejar la conversión entre cadenas PHP y cadenas Go, similar a lo que vimos en el enfoque del generador. + +El archivo de encabezado sigue siendo simple: + +```c +#ifndef _EXTENSION_H +#define _EXTENSION_H + +#include + +extern zend_module_entry ext_module_entry; + +#endif +``` + +Ahora podemos escribir el puente entre Go y C en nuestro archivo `extension.c`. Pasaremos la cadena PHP directamente a nuestra función Go: + +```c +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); +} +``` + +Puedes aprender más sobre `ZEND_PARSE_PARAMETERS_START` y el análisis de parámetros en la página dedicada del [Libro de Internals de PHP](https://www.phpinternalsbook.com/php7/extensions_design/php_functions.html#parsing-parameters-zend-parse-parameters). Aquí, le decimos a PHP que nuestra función toma un parámetro obligatorio de tipo `string` como `zend_string`. Luego pasamos esta cadena directamente a nuestra función Go y devolvemos el resultado usando `RETVAL_STR`. + +Solo queda una cosa por hacer: implementar la función `go_upper` en Go. + +#### Implementar la Función Go + +Nuestra función Go tomará un `*C.zend_string` como parámetro, lo convertirá a una cadena Go usando la función helper de FrankenPHP, lo procesará y devolverá el resultado como un nuevo `*C.zend_string`. Las funciones helper manejan toda la complejidad de gestión de memoria y conversión por nosotros. + +```go +package ejemplo + +// #include +import "C" +import ( + "unsafe" + "strings" + + "github.com/dunglas/frankenphp" +) + +//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)) +} +``` + +Este enfoque es mucho más limpio y seguro que la gestión manual de memoria. +Las funciones helper de FrankenPHP manejan la conversión entre el formato `zend_string` de PHP y las cadenas Go automáticamente. +El parámetro `false` en `PHPString()` indica que queremos crear una nueva cadena no persistente (liberada al final de la solicitud). + +> [!TIP] +> +> En este ejemplo, no realizamos ningún manejo de errores, pero siempre debes verificar que los punteros no sean `nil` y que los datos sean válidos antes de usarlos en tus funciones Go. + +### Integrando la Extensión en FrankenPHP + +Nuestra extensión ahora está lista para ser compilada e integrada en FrankenPHP. Para hacerlo, consulta la documentación de [compilación de FrankenPHP](compile.md) para aprender cómo compilar FrankenPHP. Agrega el módulo usando la bandera `--with`, apuntando a la ruta de tu módulo: + +```console +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/mi-cuenta/mi-módulo +``` + +¡Eso es todo! Tu extensión ahora está integrada en FrankenPHP y puede ser usada en tu código PHP. + +### Probando tu Extensión + +Después de integrar tu extensión en FrankenPHP, puedes crear un archivo `index.php` con ejemplos para las funciones que has implementado: + +```php + [!CAUTION] +> +> Asegúrate de reemplazar `172.17.0.3` con la IP que se asignará a tu contenedor. + +Ahora deberías poder acceder a `https://127.0.0.1` desde la máquina host. + +Si no es así, inicia FrankenPHP en modo depuración para intentar identificar el problema: + +```console +docker run \ + -e CADDY_GLOBAL_OPTIONS="debug" \ + -e SERVER_NAME="127.0.0.1" \ + -v $PWD:/app/public \ + -p 80:80 -p 443:443 -p 443:443/udp \ + dunglas/frankenphp +``` + +## Scripts de Composer que Referencian `@php` + +Los [scripts de Composer](https://getcomposer.org/doc/articles/scripts.md) pueden querer ejecutar un binario PHP para algunas tareas, por ejemplo, en [un proyecto Laravel](laravel.md) para ejecutar `@php artisan package:discover --ansi`. Esto [actualmente falla](https://github.com/php/frankenphp/issues/483#issuecomment-1899890915) por dos razones: + +- Composer no sabe cómo llamar al binario de FrankenPHP; +- Composer puede agregar configuraciones de PHP usando la bandera `-d` en el comando, que FrankenPHP aún no soporta. + +Como solución alternativa, podemos crear un script de shell en `/usr/local/bin/php` que elimine los parámetros no soportados y luego llame a FrankenPHP: + +```bash +#!/usr/bin/env bash +args=("$@") +index=0 +for i in "$@" +do + if [ "$i" == "-d" ]; then + unset 'args[$index]' + unset 'args[$index+1]' + fi + index=$((index+1)) +done + +/usr/local/bin/frankenphp php-cli ${args[@]} +``` + +Luego, establece la variable de entorno `PHP_BINARY` a la ruta de nuestro script `php` y ejecuta Composer: + +```console +export PHP_BINARY=/usr/local/bin/php +composer install +``` + +## Solución de Problemas de TLS/SSL con Binarios Estáticos + +Al usar los binarios estáticos, puedes encontrar los siguientes errores relacionados con TLS, por ejemplo, al enviar correos electrónicos usando STARTTLS: + +```text +No se puede conectar con STARTTLS: stream_socket_enable_crypto(): La operación SSL falló con el código 5. Mensajes de error de OpenSSL: +error:80000002:librería del sistema::No existe el archivo o el directorio +error:80000002:librería del sistema::No existe el archivo o el directorio +error:80000002:librería del sistema::No existe el archivo o el directorio +error:0A000086:rutinas de SSL::falló la verificación del certificado +``` + +Dado que el binario estático no incluye certificados TLS, necesitas indicar a OpenSSL la ubicación de tu instalación local de certificados CA. + +Inspecciona la salida de [`openssl_get_cert_locations()`](https://www.php.net/manual/es/function.openssl-get-cert-locations.php), +para encontrar dónde deben instalarse los certificados CA y guárdalos en esa ubicación. + +> [!CAUTION] +> +> Los contextos web y CLI pueden tener configuraciones diferentes. +> Asegúrate de ejecutar `openssl_get_cert_locations()` en el contexto adecuado. + +[Los certificados CA extraídos de Mozilla pueden descargarse del sitio de cURL](https://curl.se/docs/caextract.html). + +Alternativamente, muchas distribuciones, incluyendo Debian, Ubuntu y Alpine, proporcionan paquetes llamados `ca-certificates` que contienen estos certificados. + +También es posible usar `SSL_CERT_FILE` y `SSL_CERT_DIR` para indicar a OpenSSL dónde buscar los certificados CA: + +```console +# Establecer variables de entorno de certificados TLS +export SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt +export SSL_CERT_DIR=/etc/ssl/certs +``` diff --git a/docs/es/laravel.md b/docs/es/laravel.md new file mode 100644 index 000000000..03742b2ed --- /dev/null +++ b/docs/es/laravel.md @@ -0,0 +1,215 @@ +# Laravel + +## Docker + +Servir una aplicación web [Laravel](https://laravel.com) con FrankenPHP es tan fácil como montar el proyecto en el directorio `/app` de la imagen Docker oficial. + +Ejecuta este comando desde el directorio principal de tu aplicación Laravel: + +```console +docker run -p 80:80 -p 443:443 -p 443:443/udp -v $PWD:/app dunglas/frankenphp +``` + +¡Y listo! + +## Instalación Local + +Alternativamente, puedes ejecutar tus proyectos Laravel con FrankenPHP desde tu máquina local: + +1. [Descarga el binario correspondiente a tu sistema](../#binario-autónomo) +2. Agrega la siguiente configuración a un archivo llamado `Caddyfile` en el directorio raíz de tu proyecto Laravel: + + ```caddyfile + { + frankenphp + } + + # El nombre de dominio de tu servidor + localhost { + # Establece el directorio web raíz en public/ + root public/ + # Habilita la compresión (opcional) + encode zstd br gzip + # Ejecuta archivos PHP desde el directorio public/ y sirve los assets + php_server { + try_files {path} index.php + } + } + ``` + +3. Inicia FrankenPHP desde el directorio raíz de tu proyecto Laravel: `frankenphp run` + +## Laravel Octane + +Octane se puede instalar a través del gestor de paquetes Composer: + +```console +composer require laravel/octane +``` + +Después de instalar Octane, puedes ejecutar el comando Artisan `octane:install`, que instalará el archivo de configuración de Octane en tu aplicación: + +```console +php artisan octane:install --server=frankenphp +``` + +El servidor Octane se puede iniciar mediante el comando Artisan `octane:frankenphp`. + +```console +php artisan octane:frankenphp +``` + +El comando `octane:frankenphp` puede tomar las siguientes opciones: + +- `--host`: La dirección IP a la que el servidor debe enlazarse (por defecto: `127.0.0.1`) +- `--port`: El puerto en el que el servidor debe estar disponible (por defecto: `8000`) +- `--admin-port`: El puerto en el que el servidor de administración debe estar disponible (por defecto: `2019`) +- `--workers`: El número de workers que deben estar disponibles para manejar solicitudes (por defecto: `auto`) +- `--max-requests`: El número de solicitudes a procesar antes de recargar el servidor (por defecto: `500`) +- `--caddyfile`: La ruta al archivo `Caddyfile` de FrankenPHP (por defecto: [Caddyfile de plantilla en Laravel Octane](https://github.com/laravel/octane/blob/2.x/src/Commands/stubs/Caddyfile)) +- `--https`: Habilita HTTPS, HTTP/2 y HTTP/3, y genera y renueva certificados automáticamente +- `--http-redirect`: Habilita la redirección de HTTP a HTTPS (solo se habilita si se pasa --https) +- `--watch`: Recarga automáticamente el servidor cuando se modifica la aplicación +- `--poll`: Usa la sondea del sistema de archivos mientras se observa para vigilar archivos a través de una red +- `--log-level`: Registra mensajes en o por encima del nivel de registro especificado, usando el registrador nativo de Caddy + +> [!TIP] +> Para obtener registros JSON estructurados (útil al usar soluciones de análisis de registros), pasa explícitamente la opción `--log-level`. + +Consulta también [cómo usar Mercure con Octane](#soporte-para-mercure). + +Aprende más sobre [Laravel Octane en su documentación oficial](https://laravel.com/docs/octane). + +## Aplicaciones Laravel como Binarios Autónomos + +Usando [la característica de incrustación de aplicaciones de FrankenPHP](embed.md), es posible distribuir aplicaciones Laravel +como binarios autónomos. + +Sigue estos pasos para empaquetar tu aplicación Laravel como un binario autónomo para Linux: + +1. Crea un archivo llamado `static-build.Dockerfile` en el repositorio de tu aplicación: + + ```dockerfile + FROM --platform=linux/amd64 dunglas/frankenphp:static-builder-gnu + # Si tienes intención de ejecutar el binario en sistemas musl-libc, usa static-builder-musl en su lugar + + # Copia tu aplicación + WORKDIR /go/src/app/dist/app + COPY . . + + # Elimina las pruebas y otros archivos innecesarios para ahorrar espacio + # Alternativamente, agrega estos archivos a un archivo .dockerignore + RUN rm -Rf tests/ + + # Copia el archivo .env + RUN cp .env.example .env + # Cambia APP_ENV y APP_DEBUG para que estén listos para producción + RUN sed -i'' -e 's/^APP_ENV=.*/APP_ENV=production/' -e 's/^APP_DEBUG=.*/APP_DEBUG=false/' .env + + # Realiza otros cambios en tu archivo .env si es necesario + + # Instala las dependencias + RUN composer install --ignore-platform-reqs --no-dev -a + + # Compila el binario estático + WORKDIR /go/src/app/ + RUN EMBED=dist/app/ ./build-static.sh + ``` + + > [!CAUTION] + > + > Algunos archivos `.dockerignore` + > ignorarán el directorio `vendor/` y los archivos `.env`. Asegúrate de ajustar o eliminar el archivo `.dockerignore` antes de la compilación. + +2. Compila: + + ```console + docker build -t static-laravel-app -f static-build.Dockerfile . + ``` + +3. Extrae el binario: + + ```console + docker cp $(docker create --name static-laravel-app-tmp static-laravel-app):/go/src/app/dist/frankenphp-linux-x86_64 frankenphp ; docker rm static-laravel-app-tmp + ``` + +4. Rellena las cachés: + + ```console + frankenphp php-cli artisan optimize + ``` + +5. Ejecuta las migraciones de la base de datos (si las hay): + + ```console + frankenphp php-cli artisan migrate + ``` + +6. Genera la clave secreta de la aplicación: + + ```console + frankenphp php-cli artisan key:generate + ``` + +7. Inicia el servidor: + + ```console + frankenphp php-server + ``` + +¡Tu aplicación ya está lista! + +Aprende más sobre las opciones disponibles y cómo compilar binarios para otros sistemas operativos en la documentación de [incrustación de aplicaciones](embed.md). + +### Cambiar la Ruta de Almacenamiento + +Por defecto, Laravel almacena los archivos subidos, cachés, registros, etc. en el directorio `storage/` de la aplicación. +Esto no es adecuado para aplicaciones incrustadas, ya que cada nueva versión se extraerá en un directorio temporal diferente. + +Establece la variable de entorno `LARAVEL_STORAGE_PATH` (por ejemplo, en tu archivo `.env`) o llama al método `Illuminate\Foundation\Application::useStoragePath()` para usar un directorio fuera del directorio temporal. + +### Soporte para Mercure + +[Mercure](https://mercure.rocks) es una excelente manera de agregar capacidades en tiempo real a tus aplicaciones Laravel. +FrankenPHP incluye [soporte para Mercure integrado](mercure.md). + +Si no estás usando [Octane](#laravel-octane), consulta [la entrada de documentación de Mercure](mercure.md). + +Si estás usando Octane, puedes habilitar el soporte para Mercure agregando las siguientes líneas a tu archivo `config/octane.php`: + +```php +// ... + +return [ + // ... + + 'mercure' => [ + 'anonymous' => true, + 'publisher_jwt' => '!CambiaEstaClaveSecretaJWTDelHubMercure!', + 'subscriber_jwt' => '!CambiaEstaClaveSecretaJWTDelHubMercure!', + ], +]; +``` + +Puedes usar [todas las directivas soportadas por Mercure](https://mercure.rocks/docs/hub/config#directives) en este array. + +Para publicar y suscribirte a actualizaciones, recomendamos usar la biblioteca [Laravel Mercure Broadcaster](https://github.com/mvanduijker/laravel-mercure-broadcaster). +Alternativamente, consulta [la documentación de Mercure](mercure.md) para hacerlo en PHP y JavaScript puros. + +### Ejecutar Octane con Binarios Autónomos + +¡Incluso es posible empaquetar aplicaciones Laravel Octane como binarios autónomos! + +Para hacerlo, [instala Octane correctamente](#laravel-octane) y sigue los pasos descritos en [la sección anterior](#laravel-apps-as-standalone-binaries). + +Luego, para iniciar FrankenPHP en modo worker a través de Octane, ejecuta: + +```console +PATH="$PWD:$PATH" frankenphp php-cli artisan octane:frankenphp +``` + +> [!CAUTION] +> +> Para que el comando funcione, el binario autónomo **debe** llamarse `frankenphp` +> porque Octane necesita un programa llamado `frankenphp` disponible en la ruta. +``` diff --git a/docs/es/mercure.md b/docs/es/mercure.md new file mode 100644 index 000000000..117c7eafa --- /dev/null +++ b/docs/es/mercure.md @@ -0,0 +1,149 @@ +# Tiempo Real + +¡FrankenPHP incluye un hub [Mercure](https://mercure.rocks) integrado! +Mercure te permite enviar eventos en tiempo real a todos los dispositivos conectados: recibirán un evento JavaScript al instante. + +¡Es una alternativa conveniente a WebSockets que es simple de usar y es soportada nativamente por todos los navegadores web modernos! + +![Mercure](../mercure-hub.png) + +## Habilitando Mercure + +El soporte para Mercure está deshabilitado por defecto. +Aquí tienes un ejemplo mínimo de un `Caddyfile` que habilita tanto FrankenPHP como el hub Mercure: + +```caddyfile +# El nombre de host al que responder +localhost + +mercure { + # La clave secreta usada para firmar los tokens JWT para los publicadores + publisher_jwt !CambiaEstaClaveSecretaJWTDelHubMercure! + # Permite suscriptores anónimos (sin JWT) + anonymous +} + +root public/ +php_server +``` + +> [!TIP] +> +> El [`Caddyfile` de ejemplo](https://github.com/php/frankenphp/blob/main/caddy/frankenphp/Caddyfile) +> proporcionado por [las imágenes Docker](docker.md) ya incluye una configuración comentada de Mercure +> con variables de entorno convenientes para configurarlo. +> +> Descomenta la sección Mercure en `/etc/frankenphp/Caddyfile` para habilitarla. + +## Suscribiéndose a Actualizaciones + +Por defecto, el hub Mercure está disponible en la ruta `/.well-known/mercure` de tu servidor FrankenPHP. +Para suscribirte a actualizaciones, usa la clase nativa [`EventSource`](https://developer.mozilla.org/es/docs/Web/API/EventSource) de JavaScript: + +```html + + +Ejemplo Mercure + +``` + +## Publicando Actualizaciones + +### Usando `mercure_publish()` + +FrankenPHP proporciona una función conveniente `mercure_publish()` para publicar actualizaciones en el hub Mercure integrado: + +```php + 'valor'])); + +// Escribir en los registros de FrankenPHP +error_log("actualización $updateID publicada", 4); +``` + +La firma completa de la función es: + +```php +/** + * @param string|string[] $topics + */ +function mercure_publish(string|array $topics, string $data = '', bool $private = false, ?string $id = null, ?string $type = null, ?int $retry = null): string {} +``` + +### Usando `file_get_contents()` + +Para enviar una actualización a los suscriptores conectados, envía una solicitud POST autenticada al hub Mercure con los parámetros `topic` y `data`: + +```php + [ + 'method' => 'POST', + 'header' => "Content-type: application/x-www-form-urlencoded\r\nAuthorization: Bearer " . JWT, + 'content' => http_build_query([ + 'topic' => 'mi-tema', + 'data' => json_encode(['clave' => 'valor']), + ]), +]])); + +// Escribir en los registros de FrankenPHP +error_log("actualización $updateID publicada", 4); +``` + +La clave pasada como parámetro de la opción `mercure.publisher_jwt` en el `Caddyfile` debe usarse para firmar el token JWT usado en el encabezado `Authorization`. + +El JWT debe incluir un reclamo `mercure` con un permiso `publish` para los temas a los que deseas publicar. +Consulta [la documentación de Mercure](https://mercure.rocks/spec#publishers) sobre autorización. + +Para generar tus propios tokens, puedes usar [este enlace de jwt.io](https://www.jwt.io/#token=eyJhbGciOiJIUzI1NiJ9.eyJtZXJjdXJlIjp7InB1Ymxpc2giOlsiKiJdfX0.PXwpfIGng6KObfZlcOXvcnWCJOWTFLtswGI5DZuWSK4), +pero para aplicaciones en producción, se recomienda usar tokens de corta duración generados dinámicamente usando una biblioteca [JWT](https://www.jwt.io/libraries?programming_language=php) confiable. + +### Usando Symfony Mercure + +Alternativamente, puedes usar el [Componente Symfony Mercure](https://symfony.com/components/Mercure), una biblioteca PHP independiente. + +Esta biblioteca maneja la generación de JWT, la publicación de actualizaciones así como la autorización basada en cookies para los suscriptores. + +Primero, instala la biblioteca usando Composer: + +```console +composer require symfony/mercure lcobucci/jwt +``` + +Luego, puedes usarla de la siguiente manera: + +```php +publish(new \Symfony\Component\Mercure\Update('mi-tema', json_encode(['clave' => 'valor']))); + +// Escribir en los registros de FrankenPHP +error_log("actualización $updateID publicada", 4); +``` + +Mercure también es soportado nativamente por: + +- [Laravel](laravel.md#soporte-para-mercure) +- [Symfony](https://symfony.com/doc/current/mercure.html) +- [API Platform](https://api-platform.com/docs/core/mercure/) diff --git a/docs/es/metrics.md b/docs/es/metrics.md new file mode 100644 index 000000000..5208b64b3 --- /dev/null +++ b/docs/es/metrics.md @@ -0,0 +1,17 @@ +# Métricas + +Cuando las [métricas de Caddy](https://caddyserver.com/docs/metrics) están habilitadas, FrankenPHP expone las siguientes métricas: + +- `frankenphp_total_threads`: El número total de hilos PHP. +- `frankenphp_busy_threads`: El número de hilos PHP procesando actualmente una solicitud (los workers en ejecución siempre consumen un hilo). +- `frankenphp_queue_depth`: El número de solicitudes regulares en cola +- `frankenphp_total_workers{worker="[nombre_worker]"}`: El número total de workers. +- `frankenphp_busy_workers{worker="[nombre_worker]"}`: El número de workers procesando actualmente una solicitud. +- `frankenphp_worker_request_time{worker="[nombre_worker]"}`: El tiempo dedicado al procesamiento de solicitudes por todos los workers. +- `frankenphp_worker_request_count{worker="[nombre_worker]"}`: El número de solicitudes procesadas por todos los workers. +- `frankenphp_ready_workers{worker="[nombre_worker]"}`: El número de workers que han llamado a `frankenphp_handle_request` al menos una vez. +- `frankenphp_worker_crashes{worker="[nombre_worker]"}`: El número de veces que un worker ha terminado inesperadamente. +- `frankenphp_worker_restarts{worker="[nombre_worker]"}`: El número de veces que un worker ha sido reiniciado deliberadamente. +- `frankenphp_worker_queue_depth{worker="[nombre_worker]"}`: El número de solicitudes en cola. + +Para las métricas de los workers, el marcador de posición `[nombre_worker]` es reemplazado por el nombre del worker en el Caddyfile; de lo contrario, se usará la ruta absoluta del archivo del worker. diff --git a/docs/es/performance.md b/docs/es/performance.md new file mode 100644 index 000000000..dc46ab05f --- /dev/null +++ b/docs/es/performance.md @@ -0,0 +1,187 @@ +# Rendimiento + +Por defecto, FrankenPHP intenta ofrecer un buen compromiso entre rendimiento y facilidad de uso. +Sin embargo, es posible mejorar sustancialmente el rendimiento usando una configuración adecuada. + +## Número de Hilos y Workers + +Por defecto, FrankenPHP inicia 2 veces más hilos y workers (en modo worker) que el número de CPUs disponibles. + +Los valores apropiados dependen en gran medida de cómo está escrita tu aplicación, qué hace y tu hardware. +Recomendamos encarecidamente cambiar estos valores. Para una mejor estabilidad del sistema, se recomienda que `num_threads` x `memory_limit` < `memoria_disponible`. + +Para encontrar los valores correctos, es mejor ejecutar pruebas de carga que simulen tráfico real. +[k6](https://k6.io) y [Gatling](https://gatling.io) son buenas herramientas para esto. + +Para configurar el número de hilos, usa la opción `num_threads` de las directivas `php_server` y `php`. +Para cambiar el número de workers, usa la opción `num` de la sección `worker` de la directiva `frankenphp`. + +### `max_threads` + +Aunque siempre es mejor saber exactamente cómo será tu tráfico, las aplicaciones reales tienden a ser más impredecibles. +La configuración `max_threads` [configuración](config.md#caddyfile-config) permite a FrankenPHP generar automáticamente hilos adicionales en tiempo de ejecución hasta el límite especificado. +`max_threads` puede ayudarte a determinar cuántos hilos necesitas para manejar tu tráfico y puede hacer que el servidor sea más resiliente a picos de latencia. +Si se establece en `auto`, el límite se estimará en función del `memory_limit` en tu `php.ini`. Si no puede hacerlo, +`auto` se establecerá por defecto en 2x `num_threads`. Ten en cuenta que `auto` puede subestimar fuertemente el número de hilos necesarios. +`max_threads` es similar a [pm.max_children](https://www.php.net/manual/es/install.fpm.configuration.php#pm.max-children) de PHP FPM. La principal diferencia es que FrankenPHP usa hilos en lugar de procesos y los delega automáticamente entre diferentes scripts de worker y el 'modo clásico' según sea necesario. + +## Modo Worker + +Habilitar [el modo worker](worker.md) mejora drásticamente el rendimiento, +pero tu aplicación debe adaptarse para ser compatible con este modo: +debes crear un script de worker y asegurarte de que la aplicación no tenga fugas de memoria. + +## No Usar musl + +La variante Alpine Linux de las imágenes Docker oficiales y los binarios predeterminados que proporcionamos usan [la libc musl](https://musl.libc.org). + +Se sabe que PHP es [más lento](https://gitlab.alpinelinux.org/alpine/aports/-/issues/14381) cuando usa esta biblioteca C alternativa en lugar de la biblioteca GNU tradicional, +especialmente cuando se compila en modo ZTS (thread-safe), que es requerido para FrankenPHP. La diferencia puede ser significativa en un entorno con muchos hilos. + +Además, [algunos errores solo ocurren cuando se usa musl](https://github.com/php/php-src/issues?q=sort%3Aupdated-desc+is%3Aissue+is%3Aopen+label%3ABug+musl). + +En entornos de producción, recomendamos usar FrankenPHP vinculado a glibc, compilado con un nivel de optimización adecuado. + +Esto se puede lograr usando las imágenes Docker de Debian, usando los paquetes de nuestros mantenedores [.deb](https://debs.henderkes.com) o [.rpm](https://rpms.henderkes.com), o [compilando FrankenPHP desde las fuentes](compile.md). + +## Configuración del Runtime de Go + +FrankenPHP está escrito en Go. + +En general, el runtime de Go no requiere ninguna configuración especial, pero en ciertas circunstancias, +una configuración específica mejora el rendimiento. + +Probablemente quieras establecer la variable de entorno `GODEBUG` en `cgocheck=0` (el valor predeterminado en las imágenes Docker de FrankenPHP). + +Si ejecutas FrankenPHP en contenedores (Docker, Kubernetes, LXC...) y limitas la memoria disponible para los contenedores, +establece la variable de entorno `GOMEMLIMIT` en la cantidad de memoria disponible. + +Para más detalles, [la página de documentación de Go dedicada a este tema](https://pkg.go.dev/runtime#hdr-Environment_Variables) es una lectura obligada para aprovechar al máximo el runtime. + +## `file_server` + +Por defecto, la directiva `php_server` configura automáticamente un servidor de archivos para +servir archivos estáticos (assets) almacenados en el directorio raíz. + +Esta característica es conveniente, pero tiene un costo. +Para deshabilitarla, usa la siguiente configuración: + +```caddyfile +php_server { + file_server off +} +``` + +## `try_files` + +Además de los archivos estáticos y los archivos PHP, `php_server` también intentará servir los archivos de índice de tu aplicación +y los índices de directorio (`/ruta/` -> `/ruta/index.php`). Si no necesitas índices de directorio, +puedes deshabilitarlos definiendo explícitamente `try_files` de esta manera: + +```caddyfile +php_server { + try_files {path} index.php + root /ruta/a/tu/app # agregar explícitamente la raíz aquí permite un mejor almacenamiento en caché +} +``` + +Esto puede reducir significativamente el número de operaciones de archivo innecesarias. + +Un enfoque alternativo con 0 operaciones innecesarias de sistema de archivos sería usar en su lugar la directiva `php` y separar +los archivos de PHP por ruta. Este enfoque funciona bien si toda tu aplicación es servida por un solo archivo de entrada. +Un ejemplo de [configuración](config.md#caddyfile-config) que sirve archivos estáticos detrás de una carpeta `/assets` podría verse así: + +```caddyfile +route { + @assets { + path /assets/* + } + + # todo lo que está detrás de /assets es manejado por el servidor de archivos + file_server @assets { + root /ruta/a/tu/app + } + + # todo lo que no está en /assets es manejado por tu archivo index o worker PHP + rewrite index.php + php { + root /ruta/a/tu/app # agregar explícitamente la raíz aquí permite un mejor almacenamiento en caché + } +} +``` + +## Marcadores de Posición (Placeholders) + +Puedes usar [marcadores de posición](https://caddyserver.com/docs/conventions#placeholders) en las directivas `root` y `env`. +Sin embargo, esto evita el almacenamiento en caché de estos valores y conlleva un costo significativo de rendimiento. + +Si es posible, evita los marcadores de posición en estas directivas. + +## `resolve_root_symlink` + +Por defecto, si la raíz del documento es un enlace simbólico, se resuelve automáticamente por FrankenPHP (esto es necesario para que PHP funcione correctamente). +Si la raíz del documento no es un enlace simbólico, puedes deshabilitar esta característica. + +```caddyfile +php_server { + resolve_root_symlink false +} +``` + +Esto mejorará el rendimiento si la directiva `root` contiene [marcadores de posición](https://caddyserver.com/docs/conventions#placeholders). +La ganancia será negligible en otros casos. + +## Registros (Logs) + +El registro es obviamente muy útil, pero, por definición, +requiere operaciones de E/S y asignaciones de memoria, lo que reduce considerablemente el rendimiento. +Asegúrate de [establecer el nivel de registro](https://caddyserver.com/docs/caddyfile/options#log) correctamente, +y registra solo lo necesario. + +## Rendimiento de PHP + +FrankenPHP usa el intérprete oficial de PHP. +Todas las optimizaciones de rendimiento habituales relacionadas con PHP se aplican con FrankenPHP. + +En particular: + +- verifica que [OPcache](https://www.php.net/manual/es/book.opcache.php) esté instalado, habilitado y correctamente configurado +- habilita [optimizaciones del autoload de Composer](https://getcomposer.org/doc/articles/autoloader-optimization.md) +- asegúrate de que la caché `realpath` sea lo suficientemente grande para las necesidades de tu aplicación +- usa [preloading](https://www.php.net/manual/es/opcache.preloading.php) + +Para más detalles, lee [la entrada de documentación dedicada de Symfony](https://symfony.com/doc/current/performance.html) +(la mayoría de los consejos son útiles incluso si no usas Symfony). + +## Dividiendo el Pool de Hilos + +Es común que las aplicaciones interactúen con servicios externos lentos, como una +API que tiende a ser poco confiable bajo alta carga o que consistentemente tarda 10+ segundos en responder. +En tales casos, puede ser beneficioso dividir el pool de hilos para tener pools "lentos" dedicados. +Esto evita que los endpoints lentos consuman todos los recursos/hilos del servidor y +limita la concurrencia de solicitudes hacia el endpoint lento, similar a un +pool de conexiones. + +```caddyfile +{ + frankenphp { + max_threads 100 # máximo 100 hilos compartidos por todos los workers + } +} + +ejemplo.com { + php_server { + root /app/public # la raíz de tu aplicación + worker index.php { + match /endpoint-lento/* # todas las solicitudes con la ruta /endpoint-lento/* son manejadas por este pool de hilos + num 10 # mínimo 10 hilos para solicitudes que coincidan con /endpoint-lento/* + } + worker index.php { + match * # todas las demás solicitudes son manejadas por separado + num 20 # mínimo 20 hilos para otras solicitudes, incluso si los endpoints lentos comienzan a colgarse + } + } +} +``` + +En general, también es aconsejable manejar endpoints muy lentos de manera asíncrona, utilizando mecanismos relevantes como colas de mensajes. diff --git a/docs/es/production.md b/docs/es/production.md new file mode 100644 index 000000000..464403199 --- /dev/null +++ b/docs/es/production.md @@ -0,0 +1,144 @@ +# Despliegue en Producción + +En este tutorial, aprenderemos cómo desplegar una aplicación PHP en un único servidor usando Docker Compose. + +Si estás usando Symfony, consulta la documentación "[Despliegue en producción](https://github.com/dunglas/symfony-docker/blob/main/docs/production.md)" del proyecto Symfony Docker (que usa FrankenPHP). + +Si estás usando API Platform (que también usa FrankenPHP), consulta [la documentación de despliegue del framework](https://api-platform.com/docs/deployment/). + +## Preparando tu Aplicación + +Primero, crea un archivo `Dockerfile` en el directorio raíz de tu proyecto PHP: + +```dockerfile +FROM dunglas/frankenphp + +# Asegúrate de reemplazar "tu-dominio.ejemplo.com" por tu nombre de dominio +ENV SERVER_NAME=tu-dominio.ejemplo.com +# Si quieres deshabilitar HTTPS, usa este valor en su lugar: +#ENV SERVER_NAME=:80 + +# Si tu proyecto no usa el directorio "public" como raíz web, puedes establecerlo aquí: +# ENV SERVER_ROOT=web/ + +# Habilitar configuración de producción de PHP +RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini" + +# Copiar los archivos PHP de tu proyecto en el directorio público +COPY . /app/public +# Si usas Symfony o Laravel, necesitas copiar todo el proyecto en su lugar: +#COPY . /app +``` + +Consulta "[Construyendo una Imagen Docker Personalizada](docker.md)" para más detalles y opciones, +y para aprender cómo personalizar la configuración, instalar extensiones PHP y módulos de Caddy. + +Si tu proyecto usa Composer, +asegúrate de incluirlo en la imagen Docker e instalar tus dependencias. + +Luego, agrega un archivo `compose.yaml`: + +```yaml +services: + php: + image: dunglas/frankenphp + restart: always + ports: + - "80:80" # HTTP + - "443:443" # HTTPS + - "443:443/udp" # HTTP/3 + volumes: + - caddy_data:/data + - caddy_config:/config + +# Volúmenes necesarios para los certificados y configuración de Caddy +volumes: + caddy_data: + caddy_config: +``` + +> [!NOTE] +> +> Los ejemplos anteriores están destinados a uso en producción. +> En desarrollo, es posible que desees usar un volumen, una configuración PHP diferente y un valor diferente para la variable de entorno `SERVER_NAME`. +> +> Consulta el proyecto [Symfony Docker](https://github.com/dunglas/symfony-docker) +> (que usa FrankenPHP) para un ejemplo más avanzado usando imágenes multi-etapa, +> Composer, extensiones PHP adicionales, etc. + +Finalmente, si usas Git, haz commit de estos archivos y haz push. + +## Preparando un Servidor + +Para desplegar tu aplicación en producción, necesitas un servidor. +En este tutorial, usaremos una máquina virtual proporcionada por DigitalOcean, pero cualquier servidor Linux puede funcionar. +Si ya tienes un servidor Linux con Docker instalado, puedes saltar directamente a [la siguiente sección](#configurando-un-nombre-de-dominio). + +De lo contrario, usa [este enlace de afiliado](https://m.do.co/c/5d8aabe3ab80) para obtener $200 de crédito gratuito, crea una cuenta y luego haz clic en "Crear un Droplet". +Luego, haz clic en la pestaña "Marketplace" bajo la sección "Elegir una imagen" y busca la aplicación llamada "Docker". +Esto aprovisionará un servidor Ubuntu con las últimas versiones de Docker y Docker Compose ya instaladas. + +Para fines de prueba, los planes más económicos serán suficientes. +Para un uso real en producción, probablemente querrás elegir un plan en la sección "uso general" que se adapte a tus necesidades. + +![Desplegando FrankenPHP en DigitalOcean con Docker](digitalocean-droplet.png) + +Puedes mantener los valores predeterminados para otras configuraciones o ajustarlos según tus necesidades. +No olvides agregar tu clave SSH o crear una contraseña y luego presionar el botón "Finalizar y crear". + +Luego, espera unos segundos mientras se aprovisiona tu Droplet. +Cuando tu Droplet esté listo, usa SSH para conectarte: + +```console +ssh root@ +``` + +## Configurando un Nombre de Dominio + +En la mayoría de los casos, querrás asociar un nombre de dominio a tu sitio. +Si aún no tienes un nombre de dominio, deberás comprar uno a través de un registrador. + +Luego, crea un registro DNS de tipo `A` para tu nombre de dominio que apunte a la dirección IP de tu servidor: + +```dns +tu-dominio.ejemplo.com. IN A 207.154.233.113 +``` + +Ejemplo con el servicio de Dominios de DigitalOcean ("Redes" > "Dominios"): + +![Configurando DNS en DigitalOcean](../digitalocean-dns.png) + +> [!NOTE] +> +> Let's Encrypt, el servicio utilizado por defecto por FrankenPHP para generar automáticamente un certificado TLS, no soporta el uso de direcciones IP puras. Usar un nombre de dominio es obligatorio para usar Let's Encrypt. + +## Despliegue + +Copia tu proyecto en el servidor usando `git clone`, `scp` o cualquier otra herramienta que se ajuste a tus necesidades. +Si usas GitHub, es posible que desees usar [una clave de despliegue](https://docs.github.com/es/free-pro-team@latest/developers/overview/managing-deploy-keys#deploy-keys). +Las claves de despliegue también son [soportadas por GitLab](https://docs.gitlab.com/ee/user/project/deploy_keys/). + +Ejemplo con Git: + +```console +git clone git@github.com:/.git +``` + +Ve al directorio que contiene tu proyecto (``) e inicia la aplicación en modo producción: + +```console +docker compose up --wait +``` + +Tu servidor está en funcionamiento y se ha generado automáticamente un certificado HTTPS para ti. +Ve a `https://tu-dominio.ejemplo.com` y ¡disfruta! + +> [!CAUTION] +> +> Docker puede tener una capa de caché, asegúrate de tener la compilación correcta para cada despliegue o vuelve a compilar tu proyecto con la opción `--no-cache` para evitar problemas de caché. + +## Despliegue en Múltiples Nodos + +Si deseas desplegar tu aplicación en un clúster de máquinas, puedes usar [Docker Swarm](https://docs.docker.com/engine/swarm/stack-deploy/), +que es compatible con los archivos Compose proporcionados. +Para desplegar en Kubernetes, consulta [el gráfico Helm proporcionado con API Platform](https://api-platform.com/docs/deployment/kubernetes/), que usa FrankenPHP. diff --git a/docs/es/static.md b/docs/es/static.md new file mode 100644 index 000000000..d23e9d7ad --- /dev/null +++ b/docs/es/static.md @@ -0,0 +1,160 @@ +# Crear una Compilación Estática + +En lugar de usar una instalación local de la biblioteca PHP, +es posible crear una compilación estática o mayormente estática de FrankenPHP gracias al excelente [proyecto static-php-cli](https://github.com/crazywhalecc/static-php-cli) (a pesar de su nombre, este proyecto soporta todas las SAPI, no solo CLI). + +Con este método, un único binario portátil contendrá el intérprete de PHP, el servidor web Caddy y FrankenPHP. + +Los ejecutables nativos completamente estáticos no requieren dependencias y pueden ejecutarse incluso en la imagen Docker [`scratch`](https://docs.docker.com/build/building/base-images/#create-a-minimal-base-image-using-scratch). +Sin embargo, no pueden cargar extensiones PHP dinámicas (como Xdebug) y tienen algunas limitaciones porque usan la libc musl. + +Los binarios mayormente estáticos solo requieren `glibc` y pueden cargar extensiones dinámicas. + +Cuando sea posible, recomendamos usar compilaciones mayormente estáticas basadas en glibc. + +FrankenPHP también soporta [incrustar la aplicación PHP en el binario estático](embed.md). + +## Linux + +Proporcionamos imágenes Docker para compilar binarios Linux estáticos: + +### Compilación Completamente Estática Basada en musl + +Para un binario completamente estático que se ejecuta en cualquier distribución Linux sin dependencias pero que no soporta carga dinámica de extensiones: + +```console +docker buildx bake --load static-builder-musl +docker cp $(docker create --name static-builder-musl dunglas/frankenphp:static-builder-musl):/go/src/app/dist/frankenphp-linux-$(uname -m) frankenphp ; docker rm static-builder-musl +``` + +Para un mejor rendimiento en escenarios altamente concurrentes, considera usar el asignador [mimalloc](https://github.com/microsoft/mimalloc). + +```console +docker buildx bake --load --set static-builder-musl.args.MIMALLOC=1 static-builder-musl +``` + +### Compilación Mayormente Estática Basada en glibc (Con Soporte para Extensiones Dinámicas) + +Para un binario que soporta la carga dinámica de extensiones PHP mientras tiene las extensiones seleccionadas compiladas estáticamente: + +```console +docker buildx bake --load static-builder-gnu +docker cp $(docker create --name static-builder-gnu dunglas/frankenphp:static-builder-gnu):/go/src/app/dist/frankenphp-linux-$(uname -m) frankenphp ; docker rm static-builder-gnu +``` + +Este binario soporta todas las versiones de glibc 2.17 y superiores, pero no se ejecuta en sistemas basados en musl (como Alpine Linux). + +El binario resultante (mayormente estático excepto por `glibc`) se llama `frankenphp` y está disponible en el directorio actual. + +Si deseas compilar el binario estático sin Docker, consulta las instrucciones para macOS, que también funcionan para Linux. + +### Extensiones Personalizadas + +Por omisión, se compilan las extensiones PHP más populares. + +Para reducir el tamaño del binario y disminuir la superficie de ataque, puedes elegir la lista de extensiones a compilar usando el ARG de Docker `PHP_EXTENSIONS`. + +Por ejemplo, ejecuta el siguiente comando para compilar solo la extensión `opcache`: + +```console +docker buildx bake --load --set static-builder-musl.args.PHP_EXTENSIONS=opcache,pdo_sqlite static-builder-musl +# ... +``` + +Para agregar bibliotecas que habiliten funcionalidades adicionales a las extensiones que has habilitado, puedes pasar el ARG de Docker `PHP_EXTENSION_LIBS`: + +```console +docker buildx bake \ + --load \ + --set static-builder-musl.args.PHP_EXTENSIONS=gd \ + --set static-builder-musl.args.PHP_EXTENSION_LIBS=libjpeg,libwebp \ + static-builder-musl +``` + +### Módulos Adicionales de Caddy + +Para agregar módulos adicionales de Caddy o pasar otros argumentos a [xcaddy](https://github.com/caddyserver/xcaddy), usa el ARG de Docker `XCADDY_ARGS`: + +```console +docker buildx bake \ + --load \ + --set static-builder-musl.args.XCADDY_ARGS="--with github.com/darkweak/souin/plugins/caddy --with github.com/dunglas/caddy-cbrotli --with github.com/dunglas/mercure/caddy --with github.com/dunglas/vulcain/caddy" \ + static-builder-musl +``` + +En este ejemplo, agregamos el módulo de caché HTTP [Souin](https://souin.io) para Caddy, así como los módulos [cbrotli](https://github.com/dunglas/caddy-cbrotli), [Mercure](https://mercure.rocks) y [Vulcain](https://vulcain.rocks). + +> [!TIP] +> +> Los módulos cbrotli, Mercure y Vulcain están incluidos por omisión si `XCADDY_ARGS` está vacío o no está configurado. +> Si personalizas el valor de `XCADDY_ARGS`, debes incluirlos explícitamente si deseas que estén incluidos. + +Consulta también cómo [personalizar la compilación](#personalizando-la-compilación). + +### Token de GitHub + +Si alcanzas el límite de tasa de la API de GitHub, establece un Token de Acceso Personal de GitHub en una variable de entorno llamada `GITHUB_TOKEN`: + +```console +GITHUB_TOKEN="xxx" docker --load buildx bake static-builder-musl +# ... +``` + +## macOS + +Ejecuta el siguiente script para crear un binario estático para macOS (debes tener [Homebrew](https://brew.sh/) instalado): + +```console +git clone https://github.com/php/frankenphp +cd frankenphp +./build-static.sh +``` + +Nota: este script también funciona en Linux (y probablemente en otros Unix) y es usado internamente por las imágenes Docker que proporcionamos. + +## Personalizando la Compilación + +Las siguientes variables de entorno pueden pasarse a `docker build` y al script `build-static.sh` para personalizar la compilación estática: + +- `FRANKENPHP_VERSION`: la versión de FrankenPHP a usar +- `PHP_VERSION`: la versión de PHP a usar +- `PHP_EXTENSIONS`: las extensiones PHP a compilar ([lista de extensiones soportadas](https://static-php.dev/en/guide/extensions.html)) +- `PHP_EXTENSION_LIBS`: bibliotecas adicionales a compilar que añaden funcionalidades a las extensiones +- `XCADDY_ARGS`: argumentos a pasar a [xcaddy](https://github.com/caddyserver/xcaddy), por ejemplo para agregar módulos adicionales de Caddy +- `EMBED`: ruta de la aplicación PHP a incrustar en el binario +- `CLEAN`: cuando está establecido, libphp y todas sus dependencias se compilan desde cero (sin caché) +- `NO_COMPRESS`: no comprimir el binario resultante usando UPX +- `DEBUG_SYMBOLS`: cuando está establecido, los símbolos de depuración no se eliminarán y se añadirán al binario +- `MIMALLOC`: (experimental, solo Linux) reemplaza mallocng de musl por [mimalloc](https://github.com/microsoft/mimalloc) para mejorar el rendimiento. Solo recomendamos usar esto para compilaciones orientadas a musl; para glibc, preferimos deshabilitar esta opción y usar [`LD_PRELOAD`](https://microsoft.github.io/mimalloc/overrides.html) cuando ejecutes tu binario. +- `RELEASE`: (solo para mantenedores) cuando está establecido, el binario resultante se subirá a GitHub + +## Extensiones + +Con los binarios basados en glibc o macOS, puedes cargar extensiones PHP dinámicamente. Sin embargo, estas extensiones deberán ser compiladas con soporte ZTS. +Dado que la mayoría de los gestores de paquetes no ofrecen actualmente versiones ZTS de sus extensiones, tendrás que compilarlas tú mismo. + +Para esto, puedes compilar y ejecutar el contenedor Docker `static-builder-gnu`, acceder a él y compilar las extensiones con `./configure --with-php-config=/go/src/app/dist/static-php-cli/buildroot/bin/php-config`. + +Pasos de ejemplo para [la extensión Xdebug](https://xdebug.org): + +```console +docker build -t gnu-ext -f static-builder-gnu.Dockerfile --build-arg FRANKENPHP_VERSION=1.0 . +docker create --name static-builder-gnu -it gnu-ext /bin/sh +docker start static-builder-gnu +docker exec -it static-builder-gnu /bin/sh +cd /go/src/app/dist/static-php-cli/buildroot/bin +git clone https://github.com/xdebug/xdebug.git && cd xdebug +source scl_source enable devtoolset-10 +../phpize +./configure --with-php-config=/go/src/app/dist/static-php-cli/buildroot/bin/php-config +make +exit +docker cp static-builder-gnu:/go/src/app/dist/static-php-cli/buildroot/bin/xdebug/modules/xdebug.so xdebug-zts.so +docker cp static-builder-gnu:/go/src/app/dist/frankenphp-linux-$(uname -m) ./frankenphp +docker stop static-builder-gnu +docker rm static-builder-gnu +docker rmi gnu-ext +``` + +Esto creará `frankenphp` y `xdebug-zts.so` en el directorio actual. +Si mueves `xdebug-zts.so` a tu directorio de extensiones, agrega `zend_extension=xdebug-zts.so` a tu php.ini y ejecuta FrankenPHP, cargará Xdebug. diff --git a/docs/es/worker.md b/docs/es/worker.md new file mode 100644 index 000000000..13d011039 --- /dev/null +++ b/docs/es/worker.md @@ -0,0 +1,190 @@ +# Usando los Workers de FrankenPHP + +Inicia tu aplicación una vez y manténla en memoria. +FrankenPHP gestionará las peticiones entrantes en unos pocos milisegundos. + +## Iniciando Scripts de Worker + +### Docker + +Establece el valor de la variable de entorno `FRANKENPHP_CONFIG` a `worker /ruta/a/tu/script/worker.php`: + +```console +docker run \ + -e FRANKENPHP_CONFIG="worker /app/ruta/a/tu/script/worker.php" \ + -v $PWD:/app \ + -p 80:80 -p 443:443 -p 443:443/udp \ + dunglas/frankenphp +``` + +### Binario Autónomo + +Usa la opción `--worker` del comando `php-server` para servir el contenido del directorio actual usando un worker: + +```console +frankenphp php-server --worker /ruta/a/tu/script/worker.php +``` + +Si tu aplicación PHP está [incrustada en el binario](embed.md), puedes agregar un `Caddyfile` personalizado en el directorio raíz de la aplicación. +Será usado automáticamente. + +También es posible [reiniciar el worker al detectar cambios en archivos](config.md#watching-for-file-changes) con la opción `--watch`. +El siguiente comando activará un reinicio si algún archivo que termine en `.php` en el directorio `/ruta/a/tu/app/` o sus subdirectorios es modificado: + +```console +frankenphp php-server --worker /ruta/a/tu/script/worker.php --watch="/ruta/a/tu/app/**/*.php" +``` + +## Symfony Runtime + +> [!TIP] +> La siguiente sección es necesaria solo para versiones anteriores a Symfony 7.4, donde se introdujo soporte nativo para el modo worker de FrankenPHP. + +El modo worker de FrankenPHP es soportado por el [Componente Runtime de Symfony](https://symfony.com/doc/current/components/runtime.html). +Para iniciar cualquier aplicación Symfony en un worker, instala el paquete FrankenPHP de [PHP Runtime](https://github.com/php-runtime/runtime): + +```console +composer require runtime/frankenphp-symfony +``` + +Inicia tu servidor de aplicación definiendo la variable de entorno `APP_RUNTIME` para usar el Runtime de FrankenPHP Symfony: + +```console +docker run \ + -e FRANKENPHP_CONFIG="worker ./public/index.php" \ + -e APP_RUNTIME=Runtime\\FrankenPhpSymfony\\Runtime \ + -v $PWD:/app \ + -p 80:80 -p 443:443 -p 443:443/udp \ + dunglas/frankenphp +``` + +## Laravel Octane + +Consulta [la documentación dedicada](laravel.md#laravel-octane). + +## Aplicaciones Personalizadas + +El siguiente ejemplo muestra cómo crear tu propio script de worker sin depender de una biblioteca de terceros: + +```php +boot(); + +// Manejador fuera del bucle para mejor rendimiento (menos trabajo) +$handler = static function () use ($myApp) { + try { + // Llamado cuando se recibe una petición, + // las superglobales, php://input y similares se reinician + echo $myApp->handle($_GET, $_POST, $_COOKIE, $_FILES, $_SERVER); + } catch (\Throwable $exception) { + // `set_exception_handler` se llama solo cuando el script de worker termina, + // lo cual puede no ser lo que esperas, así que captura y maneja excepciones aquí + (new \MyCustomExceptionHandler)->handleException($exception); + } +}; + +$maxRequests = (int)($_SERVER['MAX_REQUESTS'] ?? 0); +for ($nbRequests = 0; !$maxRequests || $nbRequests < $maxRequests; ++$nbRequests) { + $keepRunning = \frankenphp_handle_request($handler); + + // Haz algo después de enviar la respuesta HTTP + $myApp->terminate(); + + // Llama al recolector de basura para reducir las posibilidades de que se active en medio de la generación de una página + gc_collect_cycles(); + + if (!$keepRunning) break; +} + +// Limpieza +$myApp->shutdown(); +``` + +Luego, inicia tu aplicación y usa la variable de entorno `FRANKENPHP_CONFIG` para configurar tu worker: + +```console +docker run \ + -e FRANKENPHP_CONFIG="worker ./public/index.php" \ + -v $PWD:/app \ + -p 80:80 -p 443:443 -p 443:443/udp \ + dunglas/frankenphp +``` + +Por omisión, se inician 2 workers por CPU. +También puedes configurar el número de workers a iniciar: + +```console +docker run \ + -e FRANKENPHP_CONFIG="worker ./public/index.php 42" \ + -v $PWD:/app \ + -p 80:80 -p 443:443 -p 443:443/udp \ + dunglas/frankenphp +``` + +### Reiniciar el Worker Después de un Número Determinado de Peticiones + +Como PHP no fue diseñado originalmente para procesos de larga duración, aún hay muchas bibliotecas y códigos heredados que generan fugas de memoria. +Una solución para usar este tipo de código en modo worker es reiniciar el script de worker después de procesar un cierto número de peticiones: + +El fragmento de worker anterior permite configurar un número máximo de peticiones a manejar estableciendo una variable de entorno llamada `MAX_REQUESTS`. + +### Reiniciar Workers Manualmente + +Aunque es posible reiniciar workers [al detectar cambios en archivos](config.md#watching-for-file-changes), también es posible reiniciar todos los workers +de manera controlada a través de la [API de administración de Caddy](https://caddyserver.com/docs/api). Si el admin está habilitado en tu +[Caddyfile](config.md#caddyfile-config), puedes activar el endpoint de reinicio con una simple petición POST como esta: + +```console +curl -X POST http://localhost:2019/frankenphp/workers/restart +``` + +### Fallos en Workers + +Si un script de worker falla con un código de salida distinto de cero, FrankenPHP lo reiniciará con una estrategia de retroceso exponencial. +Si el script de worker permanece activo más tiempo que el último retroceso * 2, +no penalizará al script de worker y lo reiniciará nuevamente. +Sin embargo, si el script de worker continúa fallando con un código de salida distinto de cero en un corto período de tiempo +(por ejemplo, tener un error tipográfico en un script), FrankenPHP fallará con el error: `too many consecutive failures`. + +El número de fallos consecutivos puede configurarse en tu [Caddyfile](config.md#caddyfile-config) con la opción `max_consecutive_failures`: + +```caddyfile +frankenphp { + worker { + # ... + max_consecutive_failures 10 + } +} +``` + +## Comportamiento de las Superglobales + +Las [superglobales de PHP](https://www.php.net/manual/es/language.variables.superglobals.php) (`$_SERVER`, `$_ENV`, `$_GET`...) +se comportan de la siguiente manera: + +- antes de la primera llamada a `frankenphp_handle_request()`, las superglobales contienen valores vinculados al script de worker en sí +- durante y después de la llamada a `frankenphp_handle_request()`, las superglobales contienen valores generados a partir de la petición HTTP procesada; cada llamada a `frankenphp_handle_request()` cambia los valores de las superglobales + +Para acceder a las superglobales del script de worker dentro de la retrollamada, debes copiarlas e importar la copia en el ámbito de la retrollamada: + +```php + Date: Tue, 23 Dec 2025 21:39:48 +0100 Subject: [PATCH 2/5] fix: build --- docs/es/laravel.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/es/laravel.md b/docs/es/laravel.md index 03742b2ed..0c88ea91f 100644 --- a/docs/es/laravel.md +++ b/docs/es/laravel.md @@ -212,4 +212,3 @@ PATH="$PWD:$PATH" frankenphp php-cli artisan octane:frankenphp > > Para que el comando funcione, el binario autónomo **debe** llamarse `frankenphp` > porque Octane necesita un programa llamado `frankenphp` disponible en la ruta. -``` From 28eadc3ee68a124d056bcb12ddd11ac9bd6d4aae Mon Sep 17 00:00:00 2001 From: PhilDaiguille Date: Tue, 23 Dec 2025 22:15:08 +0100 Subject: [PATCH 3/5] fix: build lint --- docs/es/laravel.md | 2 +- docs/es/mercure.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/es/laravel.md b/docs/es/laravel.md index 0c88ea91f..3dc4e1fa8 100644 --- a/docs/es/laravel.md +++ b/docs/es/laravel.md @@ -200,7 +200,7 @@ Alternativamente, consulta [la documentación de Mercure](mercure.md) para hacer ¡Incluso es posible empaquetar aplicaciones Laravel Octane como binarios autónomos! -Para hacerlo, [instala Octane correctamente](#laravel-octane) y sigue los pasos descritos en [la sección anterior](#laravel-apps-as-standalone-binaries). +Para hacerlo, [instala Octane correctamente](#laravel-octane) y sigue los pasos descritos en [la sección anterior](#aplicaciones-laravel-como-binarios-autónomos). Luego, para iniciar FrankenPHP en modo worker a través de Octane, ejecuta: diff --git a/docs/es/mercure.md b/docs/es/mercure.md index 117c7eafa..0cfad7bd0 100644 --- a/docs/es/mercure.md +++ b/docs/es/mercure.md @@ -85,7 +85,7 @@ Para enviar una actualización a los suscriptores conectados, envía una solicit [ 'method' => 'POST', From 6c25fc8afee0bf145cfdb830fe11e7771e2b3931 Mon Sep 17 00:00:00 2001 From: PhilDaiguille Date: Thu, 25 Dec 2025 12:42:52 +0100 Subject: [PATCH 4/5] feat(docs): add new docs translation es --- docs/es/README.md | 3 + docs/es/config.md | 10 +-- docs/es/hot-reload.md | 139 ++++++++++++++++++++++++++++++++++++++++++ docs/es/logging.md | 71 +++++++++++++++++++++ docs/es/wordpress.md | 57 +++++++++++++++++ docs/es/worker.md | 2 + 6 files changed, 278 insertions(+), 4 deletions(-) create mode 100644 docs/es/hot-reload.md create mode 100644 docs/es/logging.md create mode 100644 docs/es/wordpress.md diff --git a/docs/es/README.md b/docs/es/README.md index 34826ce9d..633ebd4fe 100644 --- a/docs/es/README.md +++ b/docs/es/README.md @@ -130,6 +130,8 @@ sudo systemctl start frankenphp - [El modo worker](worker.md) - [Soporte para Early Hints (código de estado HTTP 103)](early-hints.md) - [Tiempo real](mercure.md) +- [Hot reloading](https://frankenphp.dev/docs/hot-reload/) +- [Registro de actividad](https://frankenphp.dev/docs/logging/) - [Servir eficientemente archivos estáticos grandes](x-sendfile.md) - [Configuración](config.md) - [Escribir extensiones PHP en Go](extensions.md) @@ -140,6 +142,7 @@ sudo systemctl start frankenphp - [Crear una compilación estática](static.md) - [Compilar desde las fuentes](compile.md) - [Monitoreo de FrankenPHP](metrics.md) +- [Integración con WordPress](https://frankenphp.dev/docs/wordpress/) - [Integración con Laravel](laravel.md) - [Problemas conocidos](known-issues.md) - [Aplicación de demostración (Symfony) y benchmarks](https://github.com/dunglas/frankenphp-demo) diff --git a/docs/es/config.md b/docs/es/config.md index a1690f62f..3954f82b0 100644 --- a/docs/es/config.md +++ b/docs/es/config.md @@ -213,10 +213,12 @@ Esto es útil para entornos de desarrollo. } ``` -Si no se especifica el directorio `watch`, volverá a `./**/*.{php,yaml,yml,twig,env}`, -que observa todos los archivos `.php`, `.yaml`, `.yml`, `.twig` y `.env` en el directorio y subdirectorios -donde se inició el proceso de FrankenPHP. En su lugar, también puede especificar uno o más directorios mediante un -[patrón de nombre de archivo de shell](https://pkg.go.dev/path/filepath#Match): +Esta función se utiliza frecuentemente en combinación con [hot reload](hot-reload.md). + +Si el directorio `watch` no está especificado, retrocederá a `./**/*.{env,php,twig,yaml,yml}`, +lo cual vigila todos los archivos `.env`, `.php`, `.twig`, `.yaml` y `.yml` en el directorio y subdirectorios +donde se inició el proceso de FrankenPHP. También puede especificar uno o más directorios mediante un +[patrón de nombres de ficheros de shell](https://pkg.go.dev/path/filepath#Match): ```caddyfile { diff --git a/docs/es/hot-reload.md b/docs/es/hot-reload.md new file mode 100644 index 000000000..67a7472a3 --- /dev/null +++ b/docs/es/hot-reload.md @@ -0,0 +1,139 @@ +# Hot reload + +FrankenPHP incluye una función de **hot reload** integrada diseñada para mejorar significativamente la experiencia del desarrollador. + +![Mercure](../hot-reload.png) + +Esta función proporciona un flujo de trabajo similar a **Hot Module Replacement (HMR)** encontrado en herramientas modernas de JavaScript (como Vite o webpack). +En lugar de actualizar manualmente el navegador después de cada cambio de archivo (código PHP, plantillas, archivos JavaScript y CSS...), +FrankenPHP actualiza el contenido en tiempo real. + +La Hot Reload funciona de forma nativa con WordPress, Laravel, Symfony y cualquier otra aplicación o framework PHP. + +Cuando está activada, FrankenPHP vigila el directorio de trabajo actual en busca de cambios en el sistema de archivos. +Cuando se modifica un archivo, envía una actualización [Mercure](mercure.md) al navegador. + +Dependiendo de la configuración, el navegador: + +- **Transformará el DOM** (preservando la posición de desplazamiento y el estado de los inputs) si [Idiomorph](https://github.com/bigskysoftware/idiomorph) está cargado. +- **Recargará la página** (recarga en vivo estándar) si Idiomorph no está presente. + +## Configuración + +Para habilitar la Hot Reload, active Mercure y luego agregue la subdirectiva `hot_reload` a la directiva `php_server` en su `Caddyfile`. + +> [!WARNING] +> Esta función está destinada **únicamente a entornos de desarrollo**. +> No active `hot_reload` en producción, ya que vigilar el sistema de archivos implica una sobrecarga de rendimiento y expone endpoints internos. + +```caddyfile +localhost + +mercure { + anonymous +} + +root public/ +php_server { + hot_reload +} +``` + +Por omisión, FrankenPHP vigilará todos los archivos en el directorio de trabajo actual que coincidan con este patrón glob: `./**/*.{css,env,gif,htm,html,jpg,jpeg,js,mjs,php,png,svg,twig,webp,xml,yaml,yml}` + +Es posible establecer explícitamente los archivos a vigilar usando la sintaxis glob: + +```caddyfile +localhost + +mercure { + anonymous +} + +root public/ +php_server { + hot_reload src/**/*{.php,.js} config/**/*.yaml +} +``` + +Use la forma larga para especificar el tema de Mercure a utilizar, así como qué directorios o archivos vigilar, proporcionando rutas a la opción `hot_reload`: + +```caddyfile +localhost + +mercure { + anonymous +} + +root public/ +php_server { + hot_reload { + topic hot-reload-topic + watch src/**/*.php + watch assets/**/*.{ts,json} + watch templates/ + watch public/css/ + } +} +``` + +## Integración Lado-Cliente + +Mientras el servidor detecta los cambios, el navegador necesita suscribirse a estos eventos para actualizar la página. +FrankenPHP expone la URL del Mercure Hub a utilizar para suscribirse a los cambios de archivos a través de la variable de entorno `$_SERVER['FRANKENPHP_HOT_RELOAD']`. + +Una biblioteca JavaScript de conveniencia, [frankenphp-hot-reload](https://www.npmjs.com/package/frankenphp-hot-reload), también está disponible para manejar la lógica lado-cliente. +Para usarla, agregue lo siguiente a su diseño principal: + +```php + +FrankenPHP Hot Reload + + + + + +``` + +La biblioteca se suscribirá automáticamente al hub de Mercure, obtendrá la URL actual en segundo plano cuando se detecte un cambio en un archivo y transformará el DOM. +Está disponible como un paquete [npm](https://www.npmjs.com/package/frankenphp-hot-reload) y en [GitHub](https://github.com/dunglas/frankenphp-hot-reload). + +Alternativamente, puede implementar su propia lógica lado-cliente suscribiéndose directamente al hub de Mercure usando la clase nativa de JavaScript `EventSource`. + +### Modo Worker + +Si está ejecutando su aplicación en [Modo Worker](https://frankenphp.dev/docs/worker/), el script de su aplicación permanece en memoria. +Esto significa que los cambios en su código PHP no se reflejarán inmediatamente, incluso si el navegador se recarga. + +Para la mejor experiencia de desarrollador, debe combinar `hot_reload` con [la subdirectiva `watch` en la directiva `worker`](config.md#watching-for-file-changes). + +- `hot_reload`: actualiza el **navegador** cuando los archivos cambian +- `worker.watch`: reinicia el worker cuando los archivos cambian + +```caddy +localhost + +mercure { + anonymous +} + +root public/ +php_server { + hot_reload + worker { + file /path/to/my_worker.php + watch + } +} +``` + +### Funcionamiento + +1. **Vigilancia**: FrankenPHP monitorea el sistema de archivos en busca de modificaciones usando [la biblioteca `e-dant/watcher`](https://github.com/e-dant/watcher) internamente (contribuimos con el binding de Go). +2. **Reinicio (Modo Worker)**: si `watch` está habilitado en la configuración del worker, el worker de PHP se reinicia para cargar el nuevo código. +3. **Envío**: se envía una carga útil JSON que contiene la lista de archivos modificados al [hub de Mercure](https://mercure.rocks) integrado. +4. **Recepción**: El navegador, escuchando a través de la biblioteca JavaScript, recibe el evento de Mercure. +5. **Actualización**: + +- Si se detecta **Idiomorph**, obtiene el contenido actualizado y transforma el HTML actual para que coincida con el nuevo estado, aplicando los cambios al instante sin perder el estado. +- De lo contrario, se llama a `window.location.reload()` para recargar la página. diff --git a/docs/es/logging.md b/docs/es/logging.md new file mode 100644 index 000000000..ae1866c5d --- /dev/null +++ b/docs/es/logging.md @@ -0,0 +1,71 @@ +# Registro de actividad + +FrankenPHP se integra perfectamente con [el sistema de registro de Caddy](https://caddyserver.com/docs/logging). +Puede registrar mensajes usando funciones estándar de PHP o aprovechar la función dedicada `frankenphp_log()` para capacidades avanzadas de registro estructurado. + +## `frankenphp_log()` + +La función `frankenphp_log()` le permite emitir registros estructurados directamente desde su aplicación PHP, +facilitando la ingesta en plataformas como Datadog, Grafana Loki o Elastic, así como el soporte para OpenTelemetry. + +Internamente, `frankenphp_log()` envuelve [el paquete `log/slog` de Go](https://pkg.go.dev/log/slog) para proporcionar funciones avanzadas de registro. + +Estos registros incluyen el nivel de gravedad y datos de contexto opcionales. + +```php +function frankenphp_log(string $message, int $level = FRANKENPHP_LOG_LEVEL_INFO, array $context = []): void +``` + +### Parámetros + +- **`message`**: El string del mensaje de registro. +- **`level`**: El nivel de gravedad del registro. Puede ser cualquier entero arbitrario. Se proporcionan constantes de conveniencia para niveles comunes: `FRANKENPHP_LOG_LEVEL_DEBUG` (`-4`), `FRANKENPHP_LOG_LEVEL_INFO` (`0`), `FRANKENPHP_LOG_LEVEL_WARN` (`4`) y `FRANKENPHP_LOG_LEVEL_ERROR` (`8`)). Por omisión es `FRANKENPHP_LOG_LEVEL_INFO`. +- **`context`**: Un array asociativo de datos adicionales para incluir en la entrada del registro. + +### Ejemplo + +```php + memory_get_usage(), + 'uso_pico' => memory_get_peak_usage(), + ], +); + +``` + +Al ver los registros (por ejemplo, mediante `docker compose logs`), la salida aparecerá como JSON estructurado: + +```json +{"level":"info","ts":1704067200,"logger":"frankenphp","msg":"¡Hola desde FrankenPHP!"} +{"level":"warn","ts":1704067200,"logger":"frankenphp","msg":"Uso de memoria alto","uso_actual":10485760,"uso_pico":12582912} +``` + +## `error_log()` + +FrankenPHP también permite el registro mediante la función estándar `error_log()`. Si el parámetro `$message_type` es `4` (SAPI), +estos mensajes se redirigen al registrador de Caddy. + +Por omisión, los mensajes enviados a través de `error_log()` se tratan como texto no estructurado. +Son útiles para la compatibilidad con aplicaciones o bibliotecas existentes que dependen de la biblioteca estándar de PHP. + +### Ejemplo + +```php +error_log("Fallo en la conexión a la base de datos", 4); +``` + +Esto aparecerá en los registros de Caddy, a menudo con un prefijo que indica que se originó desde PHP. + +> [!TIP] +> Para una mejor observabilidad en entornos de producción, prefiera `frankenphp_log()` +> ya que permite filtrar registros por nivel (Depuración, Error, etc.) +> y consultar campos específicos en su infraestructura de registro. diff --git a/docs/es/wordpress.md b/docs/es/wordpress.md new file mode 100644 index 000000000..856efef41 --- /dev/null +++ b/docs/es/wordpress.md @@ -0,0 +1,57 @@ +# WordPress + +Ejecute [WordPress](https://wordpress.org/) con FrankenPHP para disfrutar de una pila moderna y de alto rendimiento con HTTPS automático, HTTP/3 y compresión Zstandard. + +## Instalación Mínima + +1. [Descargue WordPress](https://wordpress.org/download/) +2. Extraiga el archivo ZIP y abra una terminal en el directorio extraído +3. Ejecute: + ```console + frankenphp php-server + ``` +4. Vaya a `http://localhost/wp-admin/` y siga las instrucciones de instalación +5. ¡Listo! + +Para una configuración lista para producción, prefiera usar `frankenphp run` con un `Caddyfile` como este: + +```caddyfile +example.com + +php_server +encode zstd br gzip +log +``` + +## Hot Reload + +Para usar la función de [Hot reload](hot-reload.md) con WordPress, active [Mercure](mercure.md) y agregue la subdirectiva `hot_reload` a la directiva `php_server` en su `Caddyfile`: + +```caddyfile +localhost + +mercure { + anonymous +} + +php_server { + hot_reload +} +``` + +Luego, agregue el código necesario para cargar las bibliotecas JavaScript en el archivo `functions.php` de su tema de WordPress: + +```php +function hot_reload() { + ?> + + + + + + [!TIP] From c4bba105fc7b6db567ed9dfd1e964d396f2f4e05 Mon Sep 17 00:00:00 2001 From: PhilDaiguille Date: Thu, 25 Dec 2025 13:03:25 +0100 Subject: [PATCH 5/5] fix(docs): fix build --- docs/es/logging.md | 2 +- docs/es/wordpress.md | 2 ++ docs/logging.md | 2 +- docs/wordpress.md | 2 ++ 4 files changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/es/logging.md b/docs/es/logging.md index ae1866c5d..c4302dbbd 100644 --- a/docs/es/logging.md +++ b/docs/es/logging.md @@ -57,7 +57,7 @@ estos mensajes se redirigen al registrador de Caddy. Por omisión, los mensajes enviados a través de `error_log()` se tratan como texto no estructurado. Son útiles para la compatibilidad con aplicaciones o bibliotecas existentes que dependen de la biblioteca estándar de PHP. -### Ejemplo +### Uso ```php error_log("Fallo en la conexión a la base de datos", 4); diff --git a/docs/es/wordpress.md b/docs/es/wordpress.md index 856efef41..27e678ae6 100644 --- a/docs/es/wordpress.md +++ b/docs/es/wordpress.md @@ -7,9 +7,11 @@ Ejecute [WordPress](https://wordpress.org/) con FrankenPHP para disfrutar de una 1. [Descargue WordPress](https://wordpress.org/download/) 2. Extraiga el archivo ZIP y abra una terminal en el directorio extraído 3. Ejecute: + ```console frankenphp php-server ``` + 4. Vaya a `http://localhost/wp-admin/` y siga las instrucciones de instalación 5. ¡Listo! diff --git a/docs/logging.md b/docs/logging.md index 372779d60..4b21c71d7 100644 --- a/docs/logging.md +++ b/docs/logging.md @@ -59,7 +59,7 @@ these messages are routed to the Caddy logger. By default, messages sent via `error_log()` are treated as unstructured text. They are useful for compatibility with existing applications or libraries that rely on the standard PHP library. -### Example +### Usage ```php error_log("Database connection failed", 4); diff --git a/docs/wordpress.md b/docs/wordpress.md index 3bca903d2..bc9f2756f 100644 --- a/docs/wordpress.md +++ b/docs/wordpress.md @@ -7,9 +7,11 @@ Run [WordPress](https://wordpress.org/) with FrankenPHP to enjoy a modern, high- 1. [Download WordPress](https://wordpress.org/download/) 2. Extract the ZIP archive and open a terminal in the extracted directory 3. Run: + ```console frankenphp php-server ``` + 4. Go to `http://localhost/wp-admin/` and follow the installation instructions 5. Enjoy!