Learn/flight_vs_laravel
Flight vs Laravel
¿Qué es Laravel?
Laravel es un framework completo con todas las campanas y silbatos y un ecosistema enfocado en el desarrollador impresionante, pero a costa de rendimiento y complejidad. El objetivo de Laravel es que el desarrollador tenga el nivel más alto de productividad y hacer que las tareas comunes sean fáciles. Laravel es una gran elección para desarrolladores que buscan construir una aplicación web empresarial completa. Eso viene con algunos compromisos, específicamente en términos de rendimiento y complejidad. Aprender los inicios de Laravel puede ser fácil, pero ganar proficiency en el framework puede tomar algo de tiempo.
También hay tantos módulos de Laravel que los desarrolladores a menudo sienten que la única manera de resolver problemas es a través de estos módulos, cuando en realidad podrías simplemente usar otra biblioteca o escribir tu propio código.
Pros comparado con Flight
- Laravel tiene un enorme ecosistema de desarrolladores y módulos que se pueden usar para resolver problemas comunes.
- Laravel tiene un ORM completo que se puede usar para interactuar con tu base de datos.
- Laravel tiene una insana cantidad de documentación y tutoriales que se pueden usar para aprender el framework. Eso puede ser bueno para profundizar en los detalles o malo porque hay tanto por revisar.
- Laravel tiene un sistema de autenticación integrado que se puede usar para asegurar tu aplicación.
- Laravel tiene podcasts, conferencias, reuniones, videos y otros recursos que se pueden usar para aprender el framework.
- Laravel está orientado a un desarrollador experimentado que busca construir una aplicación web empresarial completa.
Cons comparado con Flight
- Laravel tiene mucho más ocurriendo bajo el capó que Flight. Esto viene con un costo dramático en términos de rendimiento. Ver los benchmarks de TechEmpower para más información.
- Flight está orientado a un desarrollador que busca construir una aplicación web ligera, rápida y fácil de usar.
- Flight está orientado a la simplicidad y facilidad de uso.
- Una de las características principales de Flight es que hace lo mejor para mantener la compatibilidad hacia atrás. Laravel causa mucha frustración entre versiones mayores.
- Flight está destinado a desarrolladores que se aventuran por primera vez en el mundo de los frameworks.
- Flight no tiene dependencias, mientras que Laravel tiene una cantidad atroz de dependencias
- Flight también puede hacer aplicaciones de nivel empresarial, pero no tiene tanto código boilerplate como Laravel. También requerirá más disciplina por parte del desarrollador para mantener las cosas organizadas y bien estructuradas.
- Flight le da al desarrollador más control sobre la aplicación, mientras que Laravel tiene montones de magia detrás de escena que puede ser frustrante.
Learn/migrating_to_v3
Migrando a v3
La compatibilidad hacia atrás se ha mantenido en su mayor parte, pero hay algunos cambios de los que debes estar al tanto al migrar de v2 a v3. Hay algunos cambios que conflictuaban demasiado con los patrones de diseño, por lo que se tuvieron que hacer algunos ajustes.
Comportamiento de Buffering de Salida
v3.5.0
Output buffering es el proceso en el que la salida generada por un script PHP se almacena en un búfer (interno de PHP) antes de ser enviada al cliente. Esto te permite modificar la salida antes de que se envíe al cliente.
En una aplicación MVC, el Controlador es el "gestor" y gestiona lo que hace la vista. Tener salida generada fuera del controlador (o en el caso de Flight, a veces una función anónima) rompe el patrón MVC. Este cambio se realiza para alinearse más con el patrón MVC y hacer que el framework sea más predecible y fácil de usar.
En v2, el buffering de salida se manejaba de una manera en la que no cerraba consistentemente su propio búfer de salida, lo que hacía más difícil el unit testing y el streaming. Para la mayoría de los usuarios, este cambio puede no afectarte realmente. Sin embargo, si estás haciendo eco de contenido fuera de callables y controladores (por ejemplo, en un hook), es probable que te encuentres con problemas. Hacer eco de contenido en hooks, y antes de que el framework se ejecute realmente, puede haber funcionado en el pasado, pero no funcionará hacia adelante.
Dónde podrías tener problemas
// index.php
require 'vendor/autoload.php';
// solo un ejemplo
define('START_TIME', microtime(true));
function hello() {
echo 'Hello World';
}
Flight::map('hello', 'hello');
Flight::after('hello', function(){
// esto en realidad estará bien
echo '<p>This Hello World phrase was brought to you by the letter "H"</p>';
});
Flight::before('start', function(){
// cosas como esta causarán un error
echo '<html><head><title>My Page</title></head><body>';
});
Flight::route('/', function(){
// esto en realidad está bien
echo 'Hello World';
// Esto también debería estar bien
Flight::hello();
});
Flight::after('start', function(){
// esto causará un error
echo '<div>Your page loaded in '.(microtime(true) - START_TIME).' seconds</div></body></html>';
});
Activando el Comportamiento de Renderizado de v2
¿Puedes mantener tu código antiguo tal como está sin hacer una reescritura para que funcione con v3? ¡Sí, puedes! Puedes activar el comportamiento de renderizado de v2 estableciendo la opción de configuración flight.v2.output_buffering
en true
. Esto te permitirá continuar usando el comportamiento de renderizado antiguo, pero se recomienda corregirlo hacia adelante. En v4 del framework, esto se eliminará.
// index.php
require 'vendor/autoload.php';
Flight::set('flight.v2.output_buffering', true);
Flight::before('start', function(){
// Ahora esto estará bien
echo '<html><head><title>My Page</title></head><body>';
});
// más código
Cambios en el Dispatcher
v3.7.0
Si has estado llamando directamente métodos estáticos para Dispatcher
como Dispatcher::invokeMethod()
, Dispatcher::execute()
, etc., necesitarás actualizar tu código para no llamar directamente a estos métodos. Dispatcher
se ha convertido en más orientado a objetos para que los Contenedores de Inyección de Dependencias puedan usarse de una manera más fácil. Si necesitas invocar un método similar a como lo hacía Dispatcher, puedes usar manualmente algo como $result = $class->$method(...$params);
o call_user_func_array()
en su lugar.
Cambios en halt()
stop()
redirect()
y error()
v3.10.0
El comportamiento predeterminado antes de 3.10.0 era limpiar tanto los encabezados como el cuerpo de la respuesta. Esto se cambió para limpiar solo el cuerpo de la respuesta. Si necesitas limpiar también los encabezados, puedes usar Flight::response()->clear()
.
Learn/configuration
Configuración
Resumen
Flight proporciona una forma sencilla de configurar varios aspectos del framework para adaptarlos a las necesidades de su aplicación. Algunos se establecen por defecto, pero puede anularlos según sea necesario. También puede establecer sus propias variables para usarlas en toda su aplicación.
Comprensión
Puede personalizar ciertos comportamientos de Flight estableciendo valores de configuración a través del método set
.
Flight::set('flight.log_errors', true);
En el archivo app/config/config.php
, puede ver todas las variables de configuración predeterminadas disponibles para usted.
Uso Básico
Opciones de Configuración de Flight
A continuación se presenta una lista de todas las configuraciones disponibles:
- flight.base_url
?string
- Anula la URL base de la solicitud si Flight se ejecuta en un subdirectorio. (predeterminado: null) - flight.case_sensitive
bool
- Coincidencia sensible a mayúsculas y minúsculas para URLs. (predeterminado: false) - flight.handle_errors
bool
- Permite que Flight maneje todos los errores internamente. (predeterminado: true)- Si desea que Flight maneje los errores en lugar del comportamiento predeterminado de PHP, esto debe ser true.
- Si tiene Tracy instalado, debe establecer esto en false para que Tracy pueda manejar los errores.
- Si tiene el plugin APM instalado, debe establecer esto en true para que APM pueda registrar los errores.
- flight.log_errors
bool
- Registra errores en el archivo de registro de errores del servidor web. (predeterminado: false)- Si tiene Tracy instalado, Tracy registrará errores basados en las configuraciones de Tracy, no en esta configuración.
- flight.views.path
string
- Directorio que contiene archivos de plantillas de vista. (predeterminado: ./views) - flight.views.extension
string
- Extensión de archivo de plantilla de vista. (predeterminado: .php) - flight.content_length
bool
- Establece el encabezadoContent-Length
. (predeterminado: true)- Si está utilizando Tracy, esto debe establecerse en false para que Tracy pueda renderizarse correctamente.
- flight.v2.output_buffering
bool
- Usa el búfer de salida heredado. Vea migrando a v3. (predeterminado: false)
Configuración del Cargador
Adicionalmente, hay otra configuración para el cargador. Esto le permitirá cargar clases automáticamente con _
en el nombre de la clase.
// Habilitar la carga de clases con guiones bajos
// Predeterminado a true
Loader::$v2ClassLoading = false;
Variables
Flight le permite guardar variables para que puedan usarse en cualquier lugar de su aplicación.
// Guardar su variable
Flight::set('id', 123);
// En otro lugar de su aplicación
$id = Flight::get('id');
Para ver si se ha establecido una variable, puede hacer:
if (Flight::has('id')) {
// Hacer algo
}
Puede eliminar una variable haciendo:
// Elimina la variable id
Flight::clear('id');
// Elimina todas las variables
Flight::clear();
Nota: Solo porque puede establecer una variable no significa que deba hacerlo. Use esta función con moderación. La razón es que cualquier cosa almacenada aquí se convierte en una variable global. Las variables globales son malas porque pueden cambiarse desde cualquier lugar de su aplicación, lo que hace difícil rastrear errores. Además, esto puede complicar cosas como pruebas unitarias.
Errores y Excepciones
Todos los errores y excepciones son capturados por Flight y pasados al método error
si flight.handle_errors
está establecido en true.
El comportamiento predeterminado es enviar una respuesta genérica HTTP 500 Internal Server Error
con algo de información de error.
Puede anular este comportamiento para sus propias necesidades:
Flight::map('error', function (Throwable $error) {
// Manejar error
echo $error->getTraceAsString();
});
Por defecto, los errores no se registran en el servidor web. Puede habilitar esto cambiando la configuración:
Flight::set('flight.log_errors', true);
404 No Encontrado
Cuando no se puede encontrar una URL, Flight llama al método notFound
. El comportamiento predeterminado es enviar una respuesta HTTP 404 Not Found
con un mensaje simple.
Puede anular este comportamiento para sus propias necesidades:
Flight::map('notFound', function () {
// Manejar no encontrado
});
Ver También
- Extendiendo Flight - Cómo extender y personalizar la funcionalidad principal de Flight.
- Pruebas Unitarias - Cómo escribir pruebas unitarias para su aplicación Flight.
- Tracy - Un plugin para manejo avanzado de errores y depuración.
- Extensiones de Tracy - Extensiones para integrar Tracy con Flight.
- APM - Un plugin para monitoreo de rendimiento de aplicaciones y seguimiento de errores.
Solución de Problemas
- Si tiene problemas para descubrir todos los valores de su configuración, puede hacer
var_dump(Flight::get());
Registro de Cambios
- v3.5.0 - Agregada configuración para
flight.v2.output_buffering
para soportar el comportamiento de búfer de salida heredado. - v2.0 - Configuraciones principales agregadas.
Learn/ai
IA y Experiencia del Desarrollador con Flight
Resumen
Flight facilita potenciar tus proyectos PHP con herramientas impulsadas por IA y flujos de trabajo modernos para desarrolladores. Con comandos integrados para conectar con proveedores de LLM (Modelo de Lenguaje Grande) y generar instrucciones de codificación específicas del proyecto impulsadas por IA, Flight te ayuda a ti y a tu equipo a sacar el máximo provecho de asistentes de IA como GitHub Copilot, Cursor y Windsurf.
Comprensión
Los asistentes de codificación con IA son más útiles cuando entienden el contexto, las convenciones y los objetivos de tu proyecto. Los ayudantes de IA de Flight te permiten:
- Conectar tu proyecto con proveedores populares de LLM (OpenAI, Grok, Claude, etc.)
- Generar y actualizar instrucciones específicas del proyecto para herramientas de IA, para que todos reciban ayuda consistente y relevante
- Mantener a tu equipo alineado y productivo, con menos tiempo dedicado a explicar el contexto
Estas funciones están integradas en el CLI principal de Flight y en el proyecto inicial oficial flightphp/skeleton.
Uso Básico
Configuración de Credenciales de LLM
El comando ai:init
te guía a través de la conexión de tu proyecto con un proveedor de LLM.
php runway ai:init
Se te pedirá que:
- Elijas tu proveedor (OpenAI, Grok, Claude, etc.)
- Ingreses tu clave API
- Establezcas la URL base y el nombre del modelo
Esto crea un archivo .runway-creds.json
en la raíz de tu proyecto (y asegura que esté en tu .gitignore
).
Ejemplo:
¡Bienvenido a AI Init!
¿Cuál API de LLM quieres usar? [1] openai, [2] grok, [3] claude: 1
Ingresa la URL base para la API de LLM [https://api.openai.com]:
Ingresa tu clave API para openai: sk-...
Ingresa el nombre del modelo que quieres usar (p.ej. gpt-4, claude-3-opus, etc) [gpt-4o]:
Credenciales guardadas en .runway-creds.json
Generación de Instrucciones de IA Específicas del Proyecto
El comando ai:generate-instructions
te ayuda a crear o actualizar instrucciones para asistentes de codificación con IA, adaptadas a tu proyecto.
php runway ai:generate-instructions
Responderás a unas pocas preguntas sobre tu proyecto (descripción, base de datos, plantillas, seguridad, tamaño del equipo, etc.). Flight usa tu proveedor de LLM para generar instrucciones y luego las escribe en:
.github/copilot-instructions.md
(para GitHub Copilot).cursor/rules/project-overview.mdc
(para Cursor).windsurfrules
(para Windsurf)
Ejemplo:
Por favor describe para qué es tu proyecto? Mi increíble API
¿Qué base de datos planeas usar? MySQL
¿Qué motor de plantillas HTML planeas usar (si aplica)? latte
¿Es la seguridad un elemento importante de este proyecto? (y/n) y
...
Instrucciones de IA actualizadas exitosamente.
Ahora, tus herramientas de IA darán sugerencias más inteligentes y relevantes basadas en las necesidades reales de tu proyecto.
Uso Avanzado
- Puedes personalizar la ubicación de tus archivos de credenciales o instrucciones usando opciones de comando (ver
--help
para cada comando). - Los ayudantes de IA están diseñados para funcionar con cualquier proveedor de LLM que soporte APIs compatibles con OpenAI.
- Si quieres actualizar tus instrucciones a medida que evoluciona tu proyecto, solo vuelve a ejecutar
ai:generate-instructions
y responde a los prompts nuevamente.
Ver También
- Flight Skeleton – El inicial oficial con integración de IA
- Runway CLI – Más sobre la herramienta CLI que impulsa estos comandos
Solución de Problemas
- Si ves "Falta .runway-creds.json", ejecuta
php runway ai:init
primero. - Asegúrate de que tu clave API sea válida y tenga acceso al modelo seleccionado.
- Si las instrucciones no se actualizan, verifica los permisos de archivos en tu directorio de proyecto.
Registro de Cambios
- v3.16.0 – Agregados comandos CLI
ai:init
yai:generate-instructions
para integración de IA.
Learn/unit_testing_and_solid_principles
Este artículo se publicó originalmente en Airpair en 2015. Todo el crédito se otorga a Airpair y Brian Fenton, quien escribió originalmente este artículo, aunque el sitio web ya no está disponible y el artículo solo existe en la Wayback Machine. Este artículo se ha agregado al sitio con fines educativos y de aprendizaje para la comunidad de PHP en general.
1 Configuración y configuración inicial
1.1 Mantenerse actualizado
Digamos esto desde el principio: un número deprimentemente pequeño de instalaciones de PHP en uso están actualizadas o se mantienen actualizadas. Ya sea debido a restricciones de alojamiento compartido, valores predeterminados que nadie piensa en cambiar o falta de tiempo/presupuesto para las pruebas de actualización, los humildes binarios de PHP tienden a quedarse atrás. Por lo tanto, una práctica recomendada clara que necesita más énfasis es siempre usar una versión actual de PHP (5.6.x en el momento de este artículo). Además, también es importante programar actualizaciones regulares tanto de PHP como de cualquier extensión o bibliotecas de proveedores que pueda estar usando. Las actualizaciones le brindan nuevas características del lenguaje, mayor velocidad, menor uso de memoria y actualizaciones de seguridad. Cuanto más frecuentemente actualice, menos doloroso se vuelve el proceso.
1.2 Establecer valores predeterminados sensatos
PHP hace un trabajo decente al establecer buenos valores predeterminados de la caja con sus archivos php.ini.development y php.ini.production, pero podemos hacerlo mejor. Por un lado, no establecen una zona horaria para nosotros. Eso tiene sentido desde una perspectiva de distribución, pero sin una, PHP lanzará un error E_WARNING cada vez que llamemos a una función relacionada con fecha/hora. Aquí hay algunas configuraciones recomendadas:
- date.timezone - elija de la lista de zonas horarias compatibles
- session.savepath - si estamos usando archivos para sesiones y no algún otro controlador de guardado, establezca esto en algo fuera de /tmp. Dejar esto como /tmp puede ser riesgoso en un entorno de alojamiento compartido ya que /tmp_ suele tener permisos amplios. Incluso con el bit sticky establecido, cualquiera con acceso para listar el contenido de este directorio puede aprender todos sus ID de sesión activos.
- session.cookie_secure - obvio, active esto si está sirviendo su código PHP a través de HTTPS.
- session.cookie_httponly - establezca esto para evitar que las cookies de sesión de PHP sean accesibles a través de JavaScript
- Más... use una herramienta como iniscan para probar su configuración en busca de vulnerabilidades comunes
1.3 Extensiones
También es una buena idea deshabilitar (o al menos no habilitar) extensiones que no usará, como controladores de bases de datos. Para ver qué está habilitado, ejecute el comando phpinfo()
o vaya a la línea de comandos y ejecute esto.
$ php -i
La información es la misma, pero phpinfo() tiene formato HTML agregado. La versión de CLI es más fácil de canalizar a grep para encontrar información específica. Ej.
$ php -i | grep error_log
Una advertencia de este método: es posible tener configuraciones de PHP diferentes que se aplican a la versión orientada a la web y a la versión de CLI.
2 Use Composer
Esto puede sorprender, pero una de las mejores prácticas para escribir PHP moderno es escribir menos de él. Si bien es cierto que una de las mejores formas de mejorar en la programación es hacerlo, hay un gran número de problemas que ya se han resuelto en el espacio de PHP, como enrutamiento, bibliotecas de validación de entrada básica, conversión de unidades, capas de abstracción de bases de datos, etc... Solo vaya a Packagist y explore. Probablemente descubrirá que porciones significativas del problema que está intentando resolver ya se han escrito y probado.
Si bien es tentador escribir todo el código usted mismo (y no hay nada malo en escribir su propio framework o biblioteca como una experiencia de aprendizaje), debe luchar contra esos sentimientos de "No Inventado Aquí" y ahorrarse mucho tiempo y dolor de cabeza. Siga la doctrina de PIE en su lugar: Orgullosamente Inventado En Otro Lugar. Además, si decide escribir su propio "lo que sea", no lo libere a menos que haga algo significativamente diferente o mejor que las ofertas existentes.
Composer es un gestor de paquetes para PHP, similar a pip en Python, gem en Ruby y npm en Node. Le permite definir un archivo JSON que lista las dependencias de su código y tratará de resolver esos requisitos descargando e instalando los paquetes de código necesarios.
2.1 Instalación de Composer
Suponiendo que esto es un proyecto local, instalemos una instancia de Composer solo para el proyecto actual. Navegue a su directorio de proyecto y ejecute esto:
$ curl -sS https://getcomposer.org/installer | php
Tenga en cuenta que canalizar cualquier descarga directamente a un intérprete de scripts (sh, ruby, php, etc.) es un riesgo de seguridad, así que lea el código de instalación y asegúrese de estar cómodo con él antes de ejecutar cualquier comando como este.
Por conveniencia (si prefieres escribir composer install
en lugar de php composer.phar install
), puedes usar este comando para instalar una copia única de composer de forma global:
$ mv composer.phar /usr/local/bin/composer
$ chmod +x composer
Es posible que necesite ejecutar esos con sudo
dependiendo de sus permisos de archivo.
2.2 Usar Composer
Composer tiene dos categorías principales de dependencias que puede gestionar: "require" y "require-dev". Las dependencias listadas como "require" se instalan en todas partes, pero las dependencias "require-dev" solo se instalan cuando se solicitan específicamente. Por lo general, estas son herramientas para cuando el código está en desarrollo activo, como PHP_CodeSniffer. La línea a continuación muestra un ejemplo de cómo instalar Guzzle, una biblioteca HTTP popular.
$ php composer.phar require guzzle/guzzle
Para instalar una herramienta solo con fines de desarrollo, agregue la bandera --dev
:
$ php composer.phar require --dev 'sebastian/phpcpd'
Esto instala PHP Copy-Paste Detector, otra herramienta de calidad de código como una dependencia solo para desarrollo.
2.3 Instalar vs actualizar
Cuando ejecutamos composer install
por primera vez, instalará cualquier biblioteca y sus dependencias que necesitemos, basadas en el archivo composer.json. Una vez hecho eso, composer crea un archivo de bloqueo, predeciblemente llamado composer.lock. Este archivo contiene una lista de las dependencias que composer encontró para nosotros y sus versiones exactas, con hashes. Luego, cualquier futura vez que ejecutemos composer install
, mirará en el archivo de bloqueo e instalará esas versiones exactas.
composer update
es un poco diferente. Ignorará el archivo composer.lock (si está presente) e intentará encontrar las versiones más actualizadas de cada una de las dependencias que aún satisfagan las restricciones en composer.json. Luego escribe un nuevo archivo composer.lock cuando termine.
2.4 Autocarga
Tanto composer install como composer update generarán un autocargador para nosotros que le indica a PHP dónde encontrar todos los archivos necesarios para usar las bibliotecas que acabamos de instalar. Para usarlo, solo agregue esta línea (generalmente a un archivo de inicialización que se ejecute en cada solicitud):
require 'vendor/autoload.php';
3 Siga buenos principios de diseño
3.1 SOLID
SOLID es un mnemotécnico para recordarnos cinco principios clave en el buen diseño de software orientado a objetos.
3.1.1 S - Principio de Responsabilidad Única
Esto establece que las clases solo deben tener una responsabilidad, o dicho de otra manera, solo deben tener una sola razón para cambiar. Esto encaja bien con la filosofía de Unix de muchas herramientas pequeñas, haciendo una cosa bien. Las clases que solo hacen una cosa son mucho más fáciles de probar y depurar, y son menos propensas a sorprenderte. No quieres que una llamada a un método de una clase Validator actualice registros de base de datos. Aquí hay un ejemplo de una violación de SRP, como la que comúnmente verías en una aplicación basada en el patrón ActiveRecord.
class Person extends Model
{
public $name;
public $birthDate;
protected $preferences;
public function getPreferences() {}
public function save() {}
}
Entonces esto es un modelo de entidad bastante básico. Sin embargo, una de estas cosas no pertenece aquí. La única responsabilidad de un modelo de entidad debería ser el comportamiento relacionado con la entidad que representa, no debería ser responsable de persistirse a sí mismo.
class Person extends Model
{
public $name;
public $birthDate;
protected $preferences;
public function getPreferences() {}
}
class DataStore
{
public function save(Model $model) {}
}
Esto es mejor. El modelo Person está de vuelta a hacer solo una cosa, y el comportamiento de guardado se ha movido a un objeto de persistencia en su lugar. Nota también que solo hice una sugerencia de tipo en Model, no en Person. Volveremos a eso cuando lleguemos a las partes L y D de SOLID.
3.1.2 O - Principio Abierto-Cerrado
Hay una prueba increíble para esto que resume bastante bien qué es este principio: piensa en una característica para implementar, probablemente la más reciente en la que trabajaste o en la que estás trabajando. ¿Puedes implementar esa característica en tu base de código existente SOLO agregando nuevas clases y no cambiando ninguna clase existente en tu sistema? Tu código de configuración y cableado obtiene un poco de indulgencia, pero en la mayoría de los sistemas esto es sorprendentemente difícil. Tienes que depender mucho de la despacho polimórfica y la mayoría de las bases de código simplemente no están configuradas para eso. Si estás interesado en eso, hay una buena charla de Google en YouTube sobre polimorfismo y escribir código sin Ifs que profundiza más. Como bono, la charla la da Miško Hevery, a quien muchos pueden conocer como el creador de AngularJs.
3.1.3 L - Principio de Sustitución de Liskov
Este principio lleva el nombre de Barbara Liskov y se imprime a continuación:
"Los objetos en un programa deberían ser reemplazables con instancias de sus subtipos sin alterar la corrección de ese programa."
Esto suena bien, pero se ilustra más claramente con un ejemplo.
abstract class Shape
{
public function getHeight();
public function setHeight($height);
public function getLength();
public function setLength($length);
}
Esto va a representar nuestra forma básica de cuatro lados. Nada fancy aquí.
class Square extends Shape
{
protected $size;
public function getHeight() {
return $this->size;
}
public function setHeight($height) {
$this->size = $height;
}
public function getLength() {
return $this->size;
}
public function setLength($length) {
$this->size = $length;
}
}
Aquí está nuestra primera forma, el Cuadrado. Forma bastante directa, ¿verdad? Puedes asumir que hay un constructor donde establecemos las dimensiones, pero ves aquí de esta implementación que la longitud y la altura siempre van a ser las mismas. Los cuadrados son así.
class Rectangle extends Shape
{
protected $height;
protected $length;
public function getHeight() {
return $this->height;
}
public function setHeight($height) {
$this->height = $height;
}
public function getLength() {
return $this->length;
}
public function setLength($length) {
$this->length = $length;
}
}
Así que aquí tenemos una forma diferente. Todavía tiene las mismas firmas de métodos, todavía es una forma de cuatro lados, pero ¿qué pasa si empezamos a intentar usarlos en lugar de uno del otro? De repente, si cambiamos la altura de nuestra Shape, ya no podemos asumir que la longitud de nuestra forma coincida. Hemos violado el contrato que teníamos con el usuario cuando les dimos nuestra forma Square.
Este es un ejemplo de libro de texto de una violación del LSP y necesitamos este tipo de principio en su lugar para hacer el mejor uso de un sistema de tipos. Incluso duck typing no nos dirá si el comportamiento subyacente es diferente, y como no podemos saberlo sin verlo romperse, es mejor asegurarse de que no lo sea en primer lugar.
3.1.3 I - Principio de Segregación de Interfaces
Este principio dice favorecer muchas interfaces pequeñas y detalladas en lugar de una grande. Las interfaces deberían basarse en el comportamiento en lugar de "es una de estas clases". Piense en interfaces que vienen con PHP. Traversable, Countable, Serializable, cosas como esas. Anuncian capacidades que posee el objeto, no de qué hereda. Así que mantenga sus interfaces pequeñas. No quieres una interfaz con 30 métodos, 3 es un objetivo mucho mejor.
3.1.4 D - Principio de Inversión de Dependencias
Probablemente hayas oído hablar de esto en otros lugares que hablaron de Inyección de Dependencias, pero Inversión de Dependencias e Inyección de Dependencias no son exactamente lo mismo. La inversión de dependencias es realmente solo una forma de decir que deberías depender de abstracciones en tu sistema y no de sus detalles. ¿Qué significa eso para ti en el día a día?
No uses directamente mysqli_query() en todo tu código, usa algo como DataStore->query() en su lugar.
El núcleo de este principio es en realidad sobre abstracciones. Se trata más de decir "usa un adaptador de base de datos" en lugar de depender de llamadas directas a cosas como mysqli_query. Si estás usando directamente mysqli_query en la mitad de tus clases, estás atando todo directamente a tu base de datos. Nada en contra de MySQL aquí, pero si estás usando mysqli_query, ese tipo de detalle de bajo nivel debería estar oculto en solo un lugar y luego esa funcionalidad debería exponerse a través de un contenedor genérico.
Ahora sé que este es un ejemplo un poco trillado si lo piensas, porque el número de veces que vas a cambiar completamente el motor de tu base de datos después de que tu producto esté en producción es muy, muy bajo. Lo elegí porque pensé que la gente estaría familiarizada con la idea de su propio código. Además, incluso si tienes una base de datos con la que te vas a quedar, ese objeto contenedor abstracto te permite arreglar errores, cambiar el comportamiento o implementar características que deseas que tuviera tu base de datos elegida. También hace posible las pruebas unitarias donde las llamadas de bajo nivel no lo harían.
4 Calistenia de objetos
Esto no es un buceo completo en estos principios, pero los dos primeros son fáciles de recordar, proporcionan un buen valor y se pueden aplicar inmediatamente a casi cualquier base de código.
4.1 No más de un nivel de indentación por método
Esta es una forma útil de pensar en descomponer métodos en fragmentos más pequeños, dejándote con código que es más claro y autodocumentado. Cuantos más niveles de indentación tengas, más está haciendo el método y más estado tienes que rastrear en tu cabeza mientras trabajas con él.
Inmediatamente sé que la gente objetará esto, pero esto es solo una guía/heurística, no una regla estricta. No espero que nadie haga cumplir reglas de PHP_CodeSniffer para esto (aunque la gente ha).
Pasemos por una muestra rápida de cómo podría verse esto:
public function transformToCsv($data)
{
$csvLines = array();
$csvLines[] = implode(',', array_keys($data[0]));
foreach ($data as $row) {
if (!$row) {
continue;
}
$csvLines[] = implode(',', $row);
}
return $csvLines;
}
Si bien este no es un código terrible (es técnicamente correcto, probables, etc.), podemos hacer mucho más para aclararlo. ¿Cómo reduciríamos los niveles de anidamiento aquí?
Sabemos que necesitamos simplificar enormemente el contenido del bucle foreach (o eliminarlo por completo), así que empecemos allí.
if (!$row) {
continue;
}
Esta primera parte es fácil. Todo lo que está haciendo es ignorar filas vacías. Podemos acortar todo este proceso usando una función incorporada de PHP antes de llegar siquiera al bucle.
$data = array_filter($data);
foreach ($data as $row) {
$csvLines[] = implode(',', $row);
}
Ahora tenemos nuestro único nivel de anidamiento. Pero mirando esto, todo lo que estamos haciendo es aplicar una función a cada elemento de un arreglo. Ni siquiera necesitamos el bucle foreach para eso.
$data = array_filter($data);
$csvLines = array_map(function($row) {
return implode(',', $row);
}, $data);
Ahora no tenemos anidamiento en absoluto, y el código probablemente será más rápido ya que estamos haciendo todo el bucle con funciones nativas en C en lugar de PHP. Tenemos que participar en un poco de truco para pasar la coma a implode
, así que podrías argumentar que detenerte en el paso anterior es mucho más comprensible.
4.2 Intenta no usar else
Esto realmente trata con dos ideas principales. La primera es múltiples declaraciones de retorno de un método. Si tienes suficiente información para tomar una decisión sobre el resultado del método, adelante, toma esa decisión y retorna. La segunda es una idea conocida como Cláusulas de Guardia. Estas son básicamente verificaciones de validación combinadas con retornos tempranos, generalmente cerca de la parte superior de un método. Déjame mostrarte lo que quiero decir.
public function addThreeInts($first, $second, $third) {
if (is_int($first)) {
if (is_int($second)) {
if (is_int($third)) {
$sum = $first + $second + $third;
} else {
return null;
}
} else {
return null;
}
} else {
return null;
}
return $sum;
}
Entonces esto es bastante directo, suma 3 enteros y devuelve el resultado, o null
si cualquiera de los parámetros no es un entero. Ignorando el hecho de que podríamos combinar todas esas verificaciones en una sola línea con operadores AND, creo que puedes ver cómo la estructura if/else anidada hace que el código sea más difícil de seguir. Ahora mira este ejemplo en su lugar.
public function addThreeInts($first, $second, $third) {
if (!is_int($first)) {
return null;
}
if (!is_int($second)) {
return null;
}
if (!is_int($third)) {
return null;
}
return $first + $second + $third;
}
Para mí, este ejemplo es mucho más fácil de seguir. Aquí estamos usando cláusulas de guardia para verificar nuestras aserciones iniciales sobre los parámetros que estamos pasando e inmediatamente saliendo del método si no pasan. También ya no tenemos la variable intermedia para rastrear la suma a lo largo del método. En este caso hemos verificado que ya estamos en el camino feliz y podemos simplemente hacer lo que vinimos a hacer. Nuevamente podríamos hacer todas esas verificaciones en un if
solo, pero el principio debería estar claro.
5 Pruebas unitarias
Las pruebas unitarias son la práctica de escribir pruebas pequeñas que verifican el comportamiento en tu código. Casi siempre se escriben en el mismo lenguaje que el código (en este caso PHP) y están destinadas a ser lo suficientemente rápidas como para ejecutarse en cualquier momento. Son extremadamente valiosas como una herramienta para mejorar tu código. Además de los beneficios obvios de asegurar que tu código esté haciendo lo que crees que está haciendo, las pruebas unitarias pueden proporcionar retroalimentación de diseño muy útil también. Si un pedazo de código es difícil de probar, a menudo muestra problemas de diseño. También te dan una red de seguridad contra regresiones, y eso te permite refactorizar mucho más a menudo y evolucionar tu código a un diseño más limpio.
5.1 Herramientas
Hay varias herramientas de pruebas unitarias allí en PHP, pero con diferencia la más común es PHPUnit. Puedes instalarla descargando un PHAR directamente, o instalarla con composer. Dado que estamos usando composer para todo lo demás, mostraremos ese método. Además, como PHPUnit probablemente no se desplegará en producción, podemos instalarlo como una dependencia de desarrollo con el siguiente comando:
composer require --dev phpunit/phpunit
5.2 Las pruebas son una especificación
El rol más importante de las pruebas unitarias en tu código es proporcionar una especificación ejecutable de lo que se supone que debe hacer el código. Incluso si el código de prueba está equivocado o el código tiene errores, el conocimiento de lo que el sistema se supone que debe hacer es invaluable.
5.3 Escribe tus pruebas primero
Si has tenido la oportunidad de ver un conjunto de pruebas escritas antes del código y uno escrito después de que el código se terminó, son notablemente diferentes. Las pruebas "después" están mucho más preocupadas por los detalles de implementación de la clase y asegurándose de que tengan una buena cobertura de líneas, mientras que las pruebas "antes" se centran más en verificar el comportamiento externo deseado. Eso es realmente lo que nos importa con las pruebas unitarias de todos modos, es asegurarnos de que la clase exhiba el comportamiento correcto. Las pruebas enfocadas en la implementación realmente hacen que el refactoring sea más difícil porque se rompen si los internos de las clases cambian, y acabas de costarte los beneficios de ocultación de información de OOP.
5.4 Qué hace una buena prueba unitaria
Las buenas pruebas unitarias comparten muchas de las siguientes características:
- Rápidas - deberían ejecutarse en milisegundos.
- Sin acceso a la red - deberían poder apagar el inalámbrico/desconectar y todas las pruebas aún pasar.
- Acceso limitado al sistema de archivos - esto agrega velocidad y flexibilidad si se despliega código a otros entornos.
- Sin acceso a la base de datos - evita actividades costosas de configuración y desmontaje.
- Prueba solo una cosa a la vez - una prueba unitaria debería tener solo una razón para fallar.
- Bien nombradas - véase 5.2 arriba.
- Mayormente objetos falsos - los únicos "reales" objetos en pruebas unitarias deberían ser el objeto que estamos probando y objetos de valor simples. El resto debería ser alguna forma de doble de prueba
Hay razones para ir en contra de algunas de estas, pero como guías generales te servirán bien.
5.5 Cuando las pruebas son dolorosas
Las pruebas unitarias te obligan a sentir el dolor del mal diseño desde el principio - Michael Feathers
Cuando escribes pruebas unitarias, te obligas a usar realmente la clase para lograr cosas. Si escribes pruebas al final, o peor aún, solo arrojas el código sobre la pared para QA o quien sea para escribir pruebas, no obtienes retroalimentación sobre cómo se comporta realmente la clase. Si estamos escribiendo pruebas y la clase es un dolor real de usar, lo descubriremos mientras la escribimos, que es casi el momento más barato para arreglarlo.
Si una clase es difícil de probar, es un defecto de diseño. Diferentes defectos se manifiestan de diferentes maneras. Si tienes que hacer un montón de burlas, tu clase probablemente tiene demasiadas dependencias o tus métodos están haciendo demasiado. Cuanto más configuración tengas que hacer para cada prueba, más probable es que tus métodos estén haciendo demasiado. Si tienes que escribir escenarios de prueba realmente enredados para ejercer el comportamiento, los métodos de la clase probablemente están haciendo demasiado. Si tienes que cavar dentro de un montón de métodos privados y estado para probar cosas, quizás haya otra clase tratando de salir. Las pruebas unitarias son muy buenas para exponer "clases iceberg" donde el 80% de lo que hace la clase está oculto en código protegido o privado. Solía ser un gran fan de hacer lo más posible protegido, pero ahora me di cuenta de que solo estaba haciendo que mis clases individuales fueran responsables de demasiado, y la solución real era dividir la clase en piezas más pequeñas.
Escrito por Brian Fenton - Brian Fenton ha sido un desarrollador de PHP durante 8 años en el Medio Oeste y el Área de la Bahía, actualmente en Thismoment. Se enfoca en la artesanía del código y los principios de diseño. Blog en www.brianfenton.us, Twitter en @brianfenton. Cuando no está ocupado siendo padre, disfruta de la comida, la cerveza, los juegos y el aprendizaje.
Learn/security
Seguridad
Resumen
La seguridad es un asunto importante cuando se trata de aplicaciones web. Quieres asegurarte de que tu aplicación sea segura y de que los datos de tus usuarios estén seguros. Flight proporciona una serie de funciones para ayudarte a asegurar tus aplicaciones web.
Comprensión
Hay una serie de amenazas de seguridad comunes de las que debes estar al tanto al construir aplicaciones web. Algunas de las amenazas más comunes incluyen:
- Cross Site Request Forgery (CSRF)
- Cross Site Scripting (XSS)
- SQL Injection
- Cross Origin Resource Sharing (CORS)
Templates ayudan con XSS escapando la salida por defecto para que no tengas que recordarlo. Sessions puede ayudar con CSRF almacenando un token CSRF en la sesión del usuario como se describe a continuación. Usar declaraciones preparadas con PDO puede ayudar a prevenir ataques de inyección SQL (o usar métodos útiles en la clase PdoWrapper). CORS puede manejarse con un gancho simple antes de que se llame Flight::start()
.
Todos estos métodos trabajan juntos para ayudar a mantener tus aplicaciones web seguras. Siempre debe estar a la vanguardia de tu mente aprender y entender las mejores prácticas de seguridad.
Uso Básico
Encabezados
Los encabezados HTTP son una de las formas más fáciles de asegurar tus aplicaciones web. Puedes usar encabezados para prevenir clickjacking, XSS y otros ataques. Hay varias formas en que puedes agregar estos encabezados a tu aplicación.
Dos excelentes sitios web para verificar la seguridad de tus encabezados son securityheaders.com y observatory.mozilla.org. Después de configurar el código a continuación, puedes verificar fácilmente que tus encabezados estén funcionando con esos dos sitios web.
Agregar Manualmente
Puedes agregar estos encabezados manualmente usando el método header
en el objeto Flight\Response
.
// Establecer el encabezado X-Frame-Options para prevenir clickjacking
Flight::response()->header('X-Frame-Options', 'SAMEORIGIN');
// Establecer el encabezado Content-Security-Policy para prevenir XSS
// Nota: este encabezado puede volverse muy complejo, así que querrás
// consultar ejemplos en internet para tu aplicación
Flight::response()->header("Content-Security-Policy", "default-src 'self'");
// Establecer el encabezado X-XSS-Protection para prevenir XSS
Flight::response()->header('X-XSS-Protection', '1; mode=block');
// Establecer el encabezado X-Content-Type-Options para prevenir MIME sniffing
Flight::response()->header('X-Content-Type-Options', 'nosniff');
// Establecer el encabezado Referrer-Policy para controlar cuánta información de referrer se envía
Flight::response()->header('Referrer-Policy', 'no-referrer-when-downgrade');
// Establecer el encabezado Strict-Transport-Security para forzar HTTPS
Flight::response()->header('Strict-Transport-Security', 'max-age=31536000; includeSubDomains; preload');
// Establecer el encabezado Permissions-Policy para controlar qué funciones y APIs pueden usarse
Flight::response()->header('Permissions-Policy', 'geolocation=()');
Estos pueden agregarse al inicio de tus archivos routes.php
o index.php
.
Agregar como un Filtro
También puedes agregarlos en un filtro/gancho como el siguiente:
// Agregar los encabezados en un filtro
Flight::before('start', function() {
Flight::response()->header('X-Frame-Options', 'SAMEORIGIN');
Flight::response()->header("Content-Security-Policy", "default-src 'self'");
Flight::response()->header('X-XSS-Protection', '1; mode=block');
Flight::response()->header('X-Content-Type-Options', 'nosniff');
Flight::response()->header('Referrer-Policy', 'no-referrer-when-downgrade');
Flight::response()->header('Strict-Transport-Security', 'max-age=31536000; includeSubDomains; preload');
Flight::response()->header('Permissions-Policy', 'geolocation=()');
});
Agregar como un Middleware
También puedes agregarlos como una clase middleware que proporciona la mayor flexibilidad para qué rutas aplicar esto. En general, estos encabezados deben aplicarse a todas las respuestas HTML y API.
// app/middlewares/SecurityHeadersMiddleware.php
namespace app\middlewares;
use flight\Engine;
class SecurityHeadersMiddleware
{
protected Engine $app;
public function __construct(Engine $app)
{
$this->app = $app;
}
public function before(array $params): void
{
$response = $this->app->response();
$response->header('X-Frame-Options', 'SAMEORIGIN');
$response->header("Content-Security-Policy", "default-src 'self'");
$response->header('X-XSS-Protection', '1; mode=block');
$response->header('X-Content-Type-Options', 'nosniff');
$response->header('Referrer-Policy', 'no-referrer-when-downgrade');
$response->header('Strict-Transport-Security', 'max-age=31536000; includeSubDomains; preload');
$response->header('Permissions-Policy', 'geolocation=()');
}
}
// index.php o donde tengas tus rutas
// FYI, este grupo de cadena vacía actúa como un middleware global para
// todas las rutas. Por supuesto, podrías hacer lo mismo y solo agregar
// esto a rutas específicas.
Flight::group('', function(Router $router) {
$router->get('/users', [ 'UserController', 'getUsers' ]);
// más rutas
}, [ SecurityHeadersMiddleware::class ]);
Cross Site Request Forgery (CSRF)
Cross Site Request Forgery (CSRF) es un tipo de ataque donde un sitio web malicioso puede hacer que el navegador de un usuario envíe una solicitud a tu sitio web. Esto puede usarse para realizar acciones en tu sitio web sin el conocimiento del usuario. Flight no proporciona un mecanismo de protección CSRF incorporado, pero puedes implementar fácilmente el tuyo propio usando middleware.
Configuración
Primero necesitas generar un token CSRF y almacenarlo en la sesión del usuario. Luego puedes usar este token en tus formularios y verificarlo cuando el formulario se envíe. Usaremos el plugin flightphp/session para manejar sesiones.
// Generar un token CSRF y almacenarlo en la sesión del usuario
// (asumiendo que has creado un objeto de sesión y lo has adjuntado a Flight)
// consulta la documentación de sesión para más información
Flight::register('session', flight\Session::class);
// Solo necesitas generar un solo token por sesión (para que funcione
// a través de múltiples pestañas y solicitudes para el mismo usuario)
if(Flight::session()->get('csrf_token') === null) {
Flight::session()->set('csrf_token', bin2hex(random_bytes(32)) );
}
Usando la Plantilla PHP Flight Predeterminada
<!-- Usar el token CSRF en tu formulario -->
<form method="post">
<input type="hidden" name="csrf_token" value="<?= Flight::session()->get('csrf_token') ?>">
<!-- otros campos del formulario -->
</form>
Usando Latte
También puedes establecer una función personalizada para mostrar el token CSRF en tus plantillas Latte.
Flight::map('render', function(string $template, array $data, ?string $block): void {
$latte = new Latte\Engine;
// otras configuraciones...
// Establecer una función personalizada para mostrar el token CSRF
$latte->addFunction('csrf', function() {
$csrfToken = Flight::session()->get('csrf_token');
return new \Latte\Runtime\Html('<input type="hidden" name="csrf_token" value="' . $csrfToken . '">');
});
$latte->render($finalPath, $data, $block);
});
Y ahora en tus plantillas Latte puedes usar la función csrf()
para mostrar el token CSRF.
<form method="post">
{csrf()}
<!-- otros campos del formulario -->
</form>
Verificar el Token CSRF
Puedes verificar el token CSRF usando varios métodos.
Middleware
// app/middlewares/CsrfMiddleware.php
namespace app\middleware;
use flight\Engine;
class CsrfMiddleware
{
protected Engine $app;
public function __construct(Engine $app)
{
$this->app = $app;
}
public function before(array $params): void
{
if($this->app->request()->method == 'POST') {
$token = $this->app->request()->data->csrf_token;
if($token !== $this->app->session()->get('csrf_token')) {
$this->app->halt(403, 'Invalid CSRF token');
}
}
}
}
// index.php o donde tengas tus rutas
use app\middlewares\CsrfMiddleware;
Flight::group('', function(Router $router) {
$router->get('/users', [ 'UserController', 'getUsers' ]);
// más rutas
}, [ CsrfMiddleware::class ]);
Filtros de Eventos
// Este middleware verifica si la solicitud es una solicitud POST y si lo es, verifica si el token CSRF es válido
Flight::before('start', function() {
if(Flight::request()->method == 'POST') {
// capturar el token csrf de los valores del formulario
$token = Flight::request()->data->csrf_token;
if($token !== Flight::session()->get('csrf_token')) {
Flight::halt(403, 'Invalid CSRF token');
// o para una respuesta JSON
Flight::jsonHalt(['error' => 'Invalid CSRF token'], 403);
}
}
});
Cross Site Scripting (XSS)
Cross Site Scripting (XSS) es un tipo de ataque donde una entrada de formulario maliciosa puede inyectar código en tu sitio web. La mayoría de estas oportunidades provienen de valores de formulario que tus usuarios finales completarán. ¡Nunca confíes en la salida de tus usuarios! Siempre asume que todos ellos son los mejores hackers del mundo. Pueden inyectar JavaScript o HTML malicioso en tu página. Este código puede usarse para robar información de tus usuarios o realizar acciones en tu sitio web. Usando la clase de vista de Flight o otro motor de plantillas como Latte, puedes escapar fácilmente la salida para prevenir ataques XSS.
// Supongamos que el usuario es astuto e intenta usar esto como su nombre
$name = '<script>alert("XSS")</script>';
// Esto escapará la salida
Flight::view()->set('name', $name);
// Esto mostrará: <script>alert("XSS")</script>
// Si usas algo como Latte registrado como tu clase de vista, también escapará esto automáticamente.
Flight::view()->render('template', ['name' => $name]);
SQL Injection
SQL Injection es un tipo de ataque donde un usuario malicioso puede inyectar código SQL en tu base de datos. Esto puede usarse para robar información
de tu base de datos o realizar acciones en tu base de datos. De nuevo, ¡nunca confíes en la entrada de tus usuarios! Siempre asume que están
buscando sangre. Puedes usar declaraciones preparadas en tus objetos PDO
para prevenir inyección SQL.
// Asumiendo que tienes Flight::db() registrado como tu objeto PDO
$statement = Flight::db()->prepare('SELECT * FROM users WHERE username = :username');
$statement->execute([':username' => $username]);
$users = $statement->fetchAll();
// Si usas la clase PdoWrapper, esto puede hacerse fácilmente en una línea
$users = Flight::db()->fetchAll('SELECT * FROM users WHERE username = :username', [ 'username' => $username ]);
// Puedes hacer lo mismo con un objeto PDO con marcadores de posición ?
$statement = Flight::db()->fetchAll('SELECT * FROM users WHERE username = ?', [ $username ]);
Ejemplo Inseguro
A continuación se explica por qué usamos declaraciones SQL preparadas para protegernos de ejemplos inocentes como el de abajo:
// el usuario final completa un formulario web.
// para el valor del formulario, el hacker pone algo como esto:
$username = "' OR 1=1; -- ";
$sql = "SELECT * FROM users WHERE username = '$username' LIMIT 5";
$users = Flight::db()->fetchAll($sql);
// Después de que la consulta se construye, se ve así
// SELECT * FROM users WHERE username = '' OR 1=1; -- LIMIT 5
// Se ve extraño, pero es una consulta válida que funcionará. De hecho,
// es un ataque de inyección SQL muy común que devolverá todos los usuarios.
var_dump($users); // esto volcará todos los usuarios en la base de datos, no solo el nombre de usuario único
CORS
Cross-Origin Resource Sharing (CORS) es un mecanismo que permite que muchos recursos (p. ej., fuentes, JavaScript, etc.) en una página web se
soliciten desde otro dominio fuera del dominio desde el cual se originó el recurso. Flight no tiene funcionalidad incorporada,
pero esto puede manejarse fácilmente con un gancho para ejecutar antes de que se llame el método Flight::start()
.
// app/utils/CorsUtil.php
namespace app\utils;
class CorsUtil
{
public function set(array $params): void
{
$request = Flight::request();
$response = Flight::response();
if ($request->getVar('HTTP_ORIGIN') !== '') {
$this->allowOrigins();
$response->header('Access-Control-Allow-Credentials', 'true');
$response->header('Access-Control-Max-Age', '86400');
}
if ($request->method === 'OPTIONS') {
if ($request->getVar('HTTP_ACCESS_CONTROL_REQUEST_METHOD') !== '') {
$response->header(
'Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, PATCH, OPTIONS, HEAD'
);
}
if ($request->getVar('HTTP_ACCESS_CONTROL_REQUEST_HEADERS') !== '') {
$response->header(
"Access-Control-Allow-Headers",
$request->getVar('HTTP_ACCESS_CONTROL_REQUEST_HEADERS')
);
}
$response->status(200);
$response->send();
exit;
}
}
private function allowOrigins(): void
{
// personaliza tus hosts permitidos aquí.
$allowed = [
'capacitor://localhost',
'ionic://localhost',
'http://localhost',
'http://localhost:4200',
'http://localhost:8080',
'http://localhost:8100',
];
$request = Flight::request();
if (in_array($request->getVar('HTTP_ORIGIN'), $allowed, true) === true) {
$response = Flight::response();
$response->header("Access-Control-Allow-Origin", $request->getVar('HTTP_ORIGIN'));
}
}
}
// index.php o donde tengas tus rutas
$CorsUtil = new CorsUtil();
// Esto necesita ejecutarse antes de que start se ejecute.
Flight::before('start', [ $CorsUtil, 'setupCors' ]);
Manejo de Errores
Oculta detalles de errores sensibles en producción para evitar filtrar información a atacantes. En producción, registra errores en lugar de mostrarlos con display_errors
establecido en 0
.
// En tu bootstrap.php o index.php
// agrega esto a tu app/config/config.php
$environment = ENVIRONMENT;
if ($environment === 'production') {
ini_set('display_errors', 0); // Deshabilitar la visualización de errores
ini_set('log_errors', 1); // Registrar errores en su lugar
ini_set('error_log', '/path/to/error.log');
}
// En tus rutas o controladores
// Usa Flight::halt() para respuestas de error controladas
Flight::halt(403, 'Access denied');
Sanitización de Entrada
Nunca confíes en la entrada del usuario. Sánitala usando filter_var antes de procesarla para prevenir que datos maliciosos se cuelen.
// Supongamos una solicitud $_POST con $_POST['input'] y $_POST['email']
// Sanitizar una entrada de cadena
$clean_input = filter_var(Flight::request()->data->input, FILTER_SANITIZE_STRING);
// Sanitizar un email
$clean_email = filter_var(Flight::request()->data->email, FILTER_SANITIZE_EMAIL);
Hash de Contraseñas
Almacena contraseñas de manera segura y verifícalas de forma segura usando las funciones integradas de PHP como password_hash y password_verify. Las contraseñas nunca deben almacenarse en texto plano, ni deben encriptarse con métodos reversibles. El hashing asegura que incluso si tu base de datos es comprometida, las contraseñas reales permanezcan protegidas.
$password = Flight::request()->data->password;
// Hashear una contraseña al almacenar (p. ej., durante el registro)
$hashed_password = password_hash($password, PASSWORD_DEFAULT);
// Verificar una contraseña (p. ej., durante el inicio de sesión)
if (password_verify($password, $stored_hash)) {
// La contraseña coincide
}
Limitación de Tasa
Protege contra ataques de fuerza bruta o ataques de denegación de servicio limitando las tasas de solicitud con una caché.
// Asumiendo que tienes flightphp/cache instalado y registrado
// Usando flightphp/cache en un filtro
Flight::before('start', function() {
$cache = Flight::cache();
$ip = Flight::request()->ip;
$key = "rate_limit_{$ip}";
$attempts = (int) $cache->retrieve($key);
if ($attempts >= 10) {
Flight::halt(429, 'Too many requests');
}
$cache->set($key, $attempts + 1, 60); // Reiniciar después de 60 segundos
});
Ver También
- Sessions - Cómo manejar sesiones de usuario de manera segura.
- Templates - Usar plantillas para escapar automáticamente la salida y prevenir XSS.
- PDO Wrapper - Interacciones simplificadas con la base de datos usando declaraciones preparadas.
- Middleware - Cómo usar middleware para simplificar el proceso de agregar encabezados de seguridad.
- Responses - Cómo personalizar respuestas HTTP con encabezados seguros.
- Requests - Cómo manejar y sanitizar la entrada del usuario.
- filter_var - Función PHP para sanitización de entrada.
- password_hash - Función PHP para hashing seguro de contraseñas.
- password_verify - Función PHP para verificar contraseñas hasheadas.
Solución de Problemas
- Consulta la sección "Ver También" anterior para información de solución de problemas relacionada con problemas con componentes del Framework Flight.
Registro de Cambios
- v3.1.0 - Agregadas secciones sobre CORS, Manejo de Errores, Sanitización de Entrada, Hash de Contraseñas y Limitación de Tasa.
- v2.0 - Agregado escaping para vistas predeterminadas para prevenir XSS.
Learn/routing
Enrutamiento
Resumen
El enrutamiento en Flight PHP mapea patrones de URL a funciones de devolución de llamada o métodos de clase, permitiendo un manejo rápido y simple de solicitudes. Está diseñado para un overhead mínimo, un uso amigable para principiantes y extensibilidad sin dependencias externas.
Comprensión
El enrutamiento es el mecanismo central que conecta las solicitudes HTTP con la lógica de tu aplicación en Flight. Al definir rutas, especificas cómo diferentes URLs activan código específico, ya sea a través de funciones, métodos de clase o acciones de controladores. El sistema de enrutamiento de Flight es flexible, soporta patrones básicos, parámetros con nombre, expresiones regulares y características avanzadas como inyección de dependencias y enrutamiento con recursos. Este enfoque mantiene tu código organizado y fácil de mantener, mientras permanece rápido y simple para principiantes y extensible para usuarios avanzados.
Nota: ¿Quieres entender más sobre enrutamiento? Consulta la página "¿por qué un framework?" para una explicación más detallada.
Uso Básico
Definiendo una Ruta Simple
El enrutamiento básico en Flight se realiza coincidiendo un patrón de URL con una función de devolución de llamada o un array de una clase y un método.
Flight::route('/', function(){
echo 'hello world!';
});
Las rutas se coinciden en el orden en que se definen. La primera ruta que coincida con una solicitud será invocada.
Usando Funciones como Devoluciones de Llamada
La devolución de llamada puede ser cualquier objeto que sea invocable. Así que puedes usar una función regular:
function hello() {
echo 'hello world!';
}
Flight::route('/', 'hello');
Usando Clases y Métodos como un Controlador
También puedes usar un método (estático o no) de una clase:
class GreetingController {
public function hello() {
echo 'hello world!';
}
}
Flight::route('/', [ 'GreetingController','hello' ]);
// o
Flight::route('/', [ GreetingController::class, 'hello' ]); // método preferido
// o
Flight::route('/', [ 'GreetingController::hello' ]);
// o
Flight::route('/', [ 'GreetingController->hello' ]);
O creando un objeto primero y luego llamando al método:
use flight\Engine;
// GreetingController.php
class GreetingController
{
protected Engine $app
public function __construct(Engine $app) {
$this->app = $app;
$this->name = 'John Doe';
}
public function hello() {
echo "Hello, {$this->name}!";
}
}
// index.php
$app = Flight::app();
$greeting = new GreetingController($app);
Flight::route('/', [ $greeting, 'hello' ]);
Nota: Por defecto, cuando un controlador es llamado dentro del framework, la clase
flight\Engine
siempre se inyecta a menos que especifiques a través de un contenedor de inyección de dependencias
Enrutamiento Específico de Método
Por defecto, los patrones de ruta se coinciden contra todos los métodos de solicitud. Puedes responder a métodos específicos colocando un identificador antes de la URL.
Flight::route('GET /', function () {
echo 'I received a GET request.';
});
Flight::route('POST /', function () {
echo 'I received a POST request.';
});
// No puedes usar Flight::get() para rutas ya que ese es un método
// para obtener variables, no para crear una ruta.
Flight::post('/', function() { /* code */ });
Flight::patch('/', function() { /* code */ });
Flight::put('/', function() { /* code */ });
Flight::delete('/', function() { /* code */ });
También puedes mapear múltiples métodos a una sola devolución de llamada usando un delimitador |
:
Flight::route('GET|POST /', function () {
echo 'I received either a GET or a POST request.';
});
Manejo Especial para Solicitudes HEAD y OPTIONS
Flight proporciona un manejo integrado para solicitudes HTTP HEAD
y OPTIONS
:
Solicitudes HEAD
- Las solicitudes HEAD se tratan igual que las solicitudes
GET
, pero Flight elimina automáticamente el cuerpo de la respuesta antes de enviarla al cliente. - Esto significa que puedes definir una ruta para
GET
, y las solicitudes HEAD a la misma URL devolverán solo encabezados (sin contenido), como se espera según los estándares HTTP.
Flight::route('GET /info', function() {
echo 'This is some info!';
});
// Una solicitud HEAD a /info devolverá los mismos encabezados, pero sin cuerpo.
Solicitudes OPTIONS
Las solicitudes OPTIONS
son manejadas automáticamente por Flight para cualquier ruta definida.
- Cuando se recibe una solicitud OPTIONS, Flight responde con un estado
204 No Content
y un encabezadoAllow
que lista todos los métodos HTTP soportados para esa ruta. - No necesitas definir una ruta separada para OPTIONS.
// Para una ruta definida como:
Flight::route('GET|POST /users', function() { /* ... */ });
// Una solicitud OPTIONS a /users responderá con:
//
// Status: 204 No Content
// Allow: GET, POST, HEAD, OPTIONS
Usando el Objeto Router
Además, puedes obtener el objeto Router que tiene algunos métodos auxiliares para usar:
$router = Flight::router();
// mapea todos los métodos igual que Flight::route()
$router->map('/', function() {
echo 'hello world!';
});
// solicitud GET
$router->get('/users', function() {
echo 'users';
});
$router->post('/users', function() { /* code */});
$router->put('/users/update/@id', function() { /* code */});
$router->delete('/users/@id', function() { /* code */});
$router->patch('/users/@id', function() { /* code */});
Expresiones Regulares (Regex)
Puedes usar expresiones regulares en tus rutas:
Flight::route('/user/[0-9]+', function () {
// Esto coincidirá con /user/1234
});
Aunque este método está disponible, se recomienda usar parámetros con nombre, o parámetros con nombre con expresiones regulares, ya que son más legibles y fáciles de mantener.
Parámetros con Nombre
Puedes especificar parámetros con nombre en tus rutas que se pasarán a tu función de devolución de llamada. Esto es más para la legibilidad de la ruta que para cualquier otra cosa. Por favor, ve la sección a continuación sobre una advertencia importante.
Flight::route('/@name/@id', function (string $name, string $id) {
echo "hello, $name ($id)!";
});
También puedes incluir expresiones regulares con tus parámetros con nombre usando el delimitador :
:
Flight::route('/@name/@id:[0-9]{3}', function (string $name, string $id) {
// Esto coincidirá con /bob/123
// Pero no coincidirá con /bob/12345
});
Nota: La coincidencia de grupos regex
()
con parámetros posicionales no está soportada. Ej::'\(
Advertencia Importante
Aunque en el ejemplo anterior, parece que @name
está directamente ligado a la variable $name
, no lo está. El orden de los parámetros en la función de devolución de llamada es lo que determina qué se pasa a ella. Si cambiaras el orden de los parámetros en la función de devolución de llamada, las variables también se cambiarían. Aquí hay un ejemplo:
Flight::route('/@name/@id', function (string $id, string $name) {
echo "hello, $name ($id)!";
});
Y si fueras a la siguiente URL: /bob/123
, la salida sería hello, 123 (bob)!
.
¡Por favor ten cuidado cuando configures tus rutas y tus funciones de devolución de llamada!
Parámetros Opcionales
Puedes especificar parámetros con nombre que sean opcionales para la coincidencia envolviendo segmentos en paréntesis.
Flight::route(
'/blog(/@year(/@month(/@day)))',
function(?string $year, ?string $month, ?string $day) {
// Esto coincidirá con las siguientes URLs:
// /blog/2012/12/10
// /blog/2012/12
// /blog/2012
// /blog
}
);
Cualquier parámetro opcional que no coincida se pasará como NULL
.
Enrutamiento con Comodín
La coincidencia solo se realiza en segmentos individuales de URL. Si quieres coincidir múltiples segmentos, puedes usar el comodín *
.
Flight::route('/blog/*', function () {
// Esto coincidirá con /blog/2000/02/01
});
Para enrutar todas las solicitudes a una sola devolución de llamada, puedes hacer:
Flight::route('*', function () {
// Haz algo
});
Manejador de 404 No Encontrado
Por defecto, si una URL no se encuentra, Flight enviará una respuesta HTTP 404 Not Found
que es muy simple y plana.
Si quieres tener una respuesta 404 más personalizada, puedes mapear tu propio método notFound
:
Flight::map('notFound', function() {
$url = Flight::request()->url;
// También podrías usar Flight::render() con una plantilla personalizada.
$output = <<<HTML
<h1>My Custom 404 Not Found</h1>
<h3>The page you have requested {$url} could not be found.</h3>
HTML;
$this->response()
->clearBody()
->status(404)
->write($output)
->send();
});
Manejador de Método No Encontrado
Por defecto, si una URL se encuentra pero el método no está permitido, Flight enviará una respuesta HTTP 405 Method Not Allowed
que es muy simple y plana (Ej: Method Not Allowed. Allowed Methods are: GET, POST). También incluirá un encabezado Allow
con los métodos permitidos para esa URL.
Si quieres tener una respuesta 405 más personalizada, puedes mapear tu propio método methodNotFound
:
use flight\net\Route;
Flight::map('methodNotFound', function(Route $route) {
$url = Flight::request()->url;
$methods = implode(', ', $route->methods);
// También podrías usar Flight::render() con una plantilla personalizada.
$output = <<<HTML
<h1>My Custom 405 Method Not Allowed</h1>
<h3>The method you have requested for {$url} is not allowed.</h3>
<p>Allowed Methods are: {$methods}</p>
HTML;
$this->response()
->clearBody()
->status(405)
->setHeader('Allow', $methods)
->write($output)
->send();
});
Uso Avanzado
Inyección de Dependencias en Rutas
Si quieres usar inyección de dependencias a través de un contenedor (PSR-11, PHP-DI, Dice, etc.), el único tipo de rutas donde eso está disponible es creando directamente el objeto tú mismo y usando el contenedor para crear tu objeto o puedes usar cadenas para definir la clase y el método a llamar. Puedes ir a la página de Inyección de Dependencias para más información.
Aquí hay un ejemplo rápido:
use flight\database\PdoWrapper;
// Greeting.php
class Greeting
{
protected PdoWrapper $pdoWrapper;
public function __construct(PdoWrapper $pdoWrapper) {
$this->pdoWrapper = $pdoWrapper;
}
public function hello(int $id) {
// haz algo con $this->pdoWrapper
$name = $this->pdoWrapper->fetchField("SELECT name FROM users WHERE id = ?", [ $id ]);
echo "Hello, world! My name is {$name}!";
}
}
// index.php
// Configura el contenedor con los parámetros que necesites
// Ve la página de Inyección de Dependencias para más información sobre PSR-11
$dice = new \Dice\Dice();
// ¡No olvides reasignar la variable con '$dice = '!!!!!
$dice = $dice->addRule('flight\database\PdoWrapper', [
'shared' => true,
'constructParams' => [
'mysql:host=localhost;dbname=test',
'root',
'password'
]
]);
// Registra el manejador del contenedor
Flight::registerContainerHandler(function($class, $params) use ($dice) {
return $dice->create($class, $params);
});
// Rutas como de costumbre
Flight::route('/hello/@id', [ 'Greeting', 'hello' ]);
// o
Flight::route('/hello/@id', 'Greeting->hello');
// o
Flight::route('/hello/@id', 'Greeting::hello');
Flight::start();
Pasando la Ejecución a la Siguiente Ruta
Obsoleto
Puedes pasar la ejecución a la siguiente ruta coincidente devolviendo true
desde tu función de devolución de llamada.
Flight::route('/user/@name', function (string $name) {
// Verifica alguna condición
if ($name !== "Bob") {
// Continúa a la siguiente ruta
return true;
}
});
Flight::route('/user/*', function () {
// Esto se llamará
});
Ahora se recomienda usar middleware para manejar casos de uso complejos como este.
Alias de Ruta
Al asignar un alias a una ruta, puedes llamar más tarde ese alias en tu aplicación dinámicamente para que se genere después en tu código (ej: un enlace en una plantilla HTML, o generando una URL de redirección).
Flight::route('/users/@id', function($id) { echo 'user:'.$id; }, false, 'user_view');
// o
Flight::route('/users/@id', function($id) { echo 'user:'.$id; })->setAlias('user_view');
// más tarde en el código en algún lugar
class UserController {
public function update() {
// código para guardar usuario...
$id = $user['id']; // 5 por ejemplo
$redirectUrl = Flight::getUrl('user_view', [ 'id' => $id ]); // devolverá '/users/5'
Flight::redirect($redirectUrl);
}
}
Esto es especialmente útil si tu URL cambia. En el ejemplo anterior, supongamos que users se movió a /admin/users/@id
en su lugar.
Con el alias en su lugar para la ruta, ya no necesitas encontrar todas las URLs antiguas en tu código y cambiarlas porque el alias ahora devolverá /admin/users/5
como en el ejemplo anterior.
El alias de ruta todavía funciona en grupos también:
Flight::group('/users', function() {
Flight::route('/@id', function($id) { echo 'user:'.$id; }, false, 'user_view');
// o
Flight::route('/@id', function($id) { echo 'user:'.$id; })->setAlias('user_view');
});
Inspeccionando Información de Ruta
Si quieres inspeccionar la información de la ruta coincidente, hay 2 formas de hacerlo:
- Puedes usar una propiedad
executedRoute
en el objetoFlight::router()
. - Puedes solicitar que el objeto ruta se pase a tu devolución de llamada pasando
true
como el tercer parámetro en el método ruta. El objeto ruta siempre será el último parámetro pasado a tu función de devolución de llamada.
executedRoute
Flight::route('/', function() {
$route = Flight::router()->executedRoute;
// Haz algo con $route
// Array de métodos HTTP coincidentes
$route->methods;
// Array de parámetros con nombre
$route->params;
// Expresión regular coincidente
$route->regex;
// Contiene el contenido de cualquier '*' usado en el patrón de URL
$route->splat;
// Muestra la ruta de la url....si realmente la necesitas
$route->pattern;
// Muestra qué middleware está asignado a esto
$route->middleware;
// Muestra el alias asignado a esta ruta
$route->alias;
});
Nota: La propiedad
executedRoute
solo se establecerá después de que una ruta haya sido ejecutada. Si intentas acceder a ella antes de que una ruta haya sido ejecutada, seráNULL
. También puedes usar executedRoute en middleware ¡también!
Pasar true
a la definición de ruta
Flight::route('/', function(\flight\net\Route $route) {
// Array de métodos HTTP coincidentes
$route->methods;
// Array de parámetros con nombre
$route->params;
// Expresión regular coincidente
$route->regex;
// Contiene el contenido de cualquier '*' usado en el patrón de URL
$route->splat;
// Muestra la ruta de la url....si realmente la necesitas
$route->pattern;
// Muestra qué middleware está asignado a esto
$route->middleware;
// Muestra el alias asignado a esta ruta
$route->alias;
}, true);// <-- Este parámetro true es lo que hace que eso suceda
Agrupación de Rutas y Middleware
Puede haber veces cuando quieras agrupar rutas relacionadas juntas (como /api/v1
).
Puedes hacerlo usando el método group
:
Flight::group('/api/v1', function () {
Flight::route('/users', function () {
// Coincide con /api/v1/users
});
Flight::route('/posts', function () {
// Coincide con /api/v1/posts
});
});
Incluso puedes anidar grupos de grupos:
Flight::group('/api', function () {
Flight::group('/v1', function () {
// Flight::get() obtiene variables, ¡no establece una ruta! Ver contexto de objeto a continuación
Flight::route('GET /users', function () {
// Coincide con GET /api/v1/users
});
Flight::post('/posts', function () {
// Coincide con POST /api/v1/posts
});
Flight::put('/posts/1', function () {
// Coincide con PUT /api/v1/posts
});
});
Flight::group('/v2', function () {
// Flight::get() obtiene variables, ¡no establece una ruta! Ver contexto de objeto a continuación
Flight::route('GET /users', function () {
// Coincide con GET /api/v2/users
});
});
});
Agrupación con Contexto de Objeto
Aún puedes usar agrupación de rutas con el objeto Engine
de la siguiente manera:
$app = Flight::app();
$app->group('/api/v1', function (Router $router) {
// usa la variable $router
$router->get('/users', function () {
// Coincide con GET /api/v1/users
});
$router->post('/posts', function () {
// Coincide con POST /api/v1/posts
});
});
Nota: Este es el método preferido de definir rutas y grupos con el objeto
$router
.
Agrupación con Middleware
También puedes asignar middleware a un grupo de rutas:
Flight::group('/api/v1', function () {
Flight::route('/users', function () {
// Coincide con /api/v1/users
});
}, [ MyAuthMiddleware::class ]); // o [ new MyAuthMiddleware() ] si quieres usar una instancia
Ver más detalles en la página de middleware de grupo.
Enrutamiento con Recursos
Puedes crear un conjunto de rutas para un recurso usando el método resource
. Esto creará un conjunto de rutas para un recurso que sigue las convenciones RESTful.
Para crear un recurso, haz lo siguiente:
Flight::resource('/users', UsersController::class);
Y lo que sucederá en segundo plano es que creará las siguientes rutas:
[
'index' => 'GET /users',
'create' => 'GET /users/create',
'store' => 'POST /users',
'show' => 'GET /users/@id',
'edit' => 'GET /users/@id/edit',
'update' => 'PUT /users/@id',
'destroy' => 'DELETE /users/@id'
]
Y tu controlador usará los siguientes métodos:
class UsersController
{
public function index(): void
{
}
public function show(string $id): void
{
}
public function create(): void
{
}
public function store(): void
{
}
public function edit(string $id): void
{
}
public function update(string $id): void
{
}
public function destroy(string $id): void
{
}
}
Nota: Puedes ver las rutas recién agregadas con
runway
ejecutandophp runway routes
.
Personalizando Rutas de Recursos
Hay algunas opciones para configurar las rutas de recursos.
Alias Base
Puedes configurar el aliasBase
. Por defecto, el alias es la última parte de la URL especificada.
Por ejemplo, /users/
resultaría en un aliasBase
de users
. Cuando se crean estas rutas, los alias son users.index
, users.create
, etc. Si quieres cambiar el alias, establece el aliasBase
al valor que quieras.
Flight::resource('/users', UsersController::class, [ 'aliasBase' => 'user' ]);
Solo y Excepto
También puedes especificar qué rutas quieres crear usando las opciones only
y except
.
// Lista blanca solo estos métodos y lista negra el resto
Flight::resource('/users', UsersController::class, [ 'only' => [ 'index', 'show' ] ]);
// Lista negra solo estos métodos y lista blanca el resto
Flight::resource('/users', UsersController::class, [ 'except' => [ 'create', 'store', 'edit', 'update', 'destroy' ] ]);
Estas son básicamente opciones de lista blanca y lista negra para que puedas especificar qué rutas quieres crear.
Middleware
También puedes especificar middleware para ejecutarse en cada una de las rutas creadas por el método resource
.
Flight::resource('/users', UsersController::class, [ 'middleware' => [ MyAuthMiddleware::class ] ]);
Respuestas en Streaming
Ahora puedes transmitir respuestas al cliente usando stream()
o streamWithHeaders()
.
Esto es útil para enviar archivos grandes, procesos de larga duración o generar respuestas grandes.
Transmitir una ruta se maneja un poco diferente a una ruta regular.
Nota: Las respuestas en streaming solo están disponibles si tienes
flight.v2.output_buffering
establecido enfalse
.
Streaming con Encabezados Manuales
Puedes transmitir una respuesta al cliente usando el método stream()
en una ruta. Si haces esto, debes establecer todos los encabezados a mano antes de que salgas algo al cliente.
Esto se hace con la función php header()
o el método Flight::response()->setRealHeader()
.
Flight::route('/@filename', function($filename) {
$response = Flight::response();
// obviamente sanitizarías la ruta y demás.
$fileNameSafe = basename($filename);
// Si tienes encabezados adicionales para establecer aquí después de que la ruta se haya ejecutado
// debes definirlos antes de que nada se haga eco.
// Deben ser todos una llamada cruda a la función header() o
// una llamada a Flight::response()->setRealHeader()
header('Content-Disposition: attachment; filename="'.$fileNameSafe.'"');
// o
$response->setRealHeader('Content-Disposition: attachment; filename="'.$fileNameSafe.'"');
$filePath = '/some/path/to/files/'.$fileNameSafe;
if (!is_readable($filePath)) {
Flight::halt(404, 'File not found');
}
// establece manualmente la longitud del contenido si lo deseas
header('Content-Length: '.filesize($filePath));
// o
$response->setRealHeader('Content-Length: '.filesize($filePath));
// Transmite el archivo al cliente mientras se lee
readfile($filePath);
// Esta es la línea mágica aquí
})->stream();
Streaming con Encabezados
También puedes usar el método streamWithHeaders()
para establecer los encabezados antes de comenzar a transmitir.
Flight::route('/stream-users', function() {
// puedes agregar cualquier encabezado adicional que quieras aquí
// solo debes usar header() o Flight::response()->setRealHeader()
// sin embargo, como obtengas tus datos, solo como ejemplo...
$users_stmt = Flight::db()->query("SELECT id, first_name, last_name FROM users");
echo '{';
$user_count = count($users);
while($user = $users_stmt->fetch(PDO::FETCH_ASSOC)) {
echo json_encode($user);
if(--$user_count > 0) {
echo ',';
}
// Esto es requerido para enviar los datos al cliente
ob_flush();
}
echo '}';
// Esta es la forma en que establecerás los encabezados antes de comenzar a transmitir.
})->streamWithHeaders([
'Content-Type' => 'application/json',
'Content-Disposition' => 'attachment; filename="users.json"',
// código de estado opcional, por defecto 200
'status' => 200
]);
Ver También
- Middleware - Usando middleware con rutas para autenticación, registro, etc.
- Inyección de Dependencias - Simplificando la creación y gestión de objetos en rutas.
- ¿Por qué un Framework? - Entendiendo los beneficios de usar un framework como Flight.
- Extensión - Cómo extender Flight con tu propia funcionalidad incluyendo el método
notFound
. - php.net: preg_match - Función PHP para coincidencia de expresiones regulares.
Solución de Problemas
- Los parámetros de ruta se coinciden por orden, no por nombre. Asegúrate de que el orden de los parámetros de la devolución de llamada coincida con la definición de la ruta.
- Usar
Flight::get()
no define una ruta; usaFlight::route('GET /...')
para enrutamiento o el contexto del objeto Router en grupos (ej.$router->get(...)
). - La propiedad executedRoute solo se establece después de que una ruta se ejecuta; es NULL antes de la ejecución.
- El streaming requiere que la funcionalidad de búfer de salida legacy de Flight esté deshabilitada (
flight.v2.output_buffering = false
). - Para inyección de dependencias, solo ciertas definiciones de ruta soportan instanciación basada en contenedor.
404 No Encontrado o Comportamiento de Ruta Inesperado
Si estás viendo un error 404 No Encontrado (pero juras por tu vida que realmente está ahí y no es un error tipográfico), esto en realidad podría ser un problema con que devuelves un valor en tu punto final de ruta en lugar de solo hacer eco de él. La razón para esto es intencional pero podría sorprender a algunos desarrolladores.
Flight::route('/hello', function(){
// Esto podría causar un error 404 No Encontrado
return 'Hello World';
});
// Lo que probablemente quieres
Flight::route('/hello', function(){
echo 'Hello World';
});
La razón para esto es por un mecanismo especial incorporado en el router que maneja la salida de retorno como una señal para "ir a la siguiente ruta". Puedes ver el comportamiento documentado en la sección de Enrutamiento.
Registro de Cambios
- v3: Agregado enrutamiento con recursos, alias de ruta y soporte para streaming, grupos de ruta y soporte para middleware.
- v1: La gran mayoría de características básicas disponibles.
Learn/learn
Aprende Sobre Flight
Flight es un framework rápido, simple y extensible para PHP. Es bastante versátil y se puede usar para construir cualquier tipo de aplicación web. Está construido con la simplicidad en mente y está escrito de una manera que es fácil de entender y usar.
Nota: Verás ejemplos que usan
Flight::
como una variable estática y algunos que usan el objeto Engine$app->
. Ambos funcionan de manera intercambiable con el otro.$app
y$this->app
en un controlador/middleware es el enfoque recomendado por el equipo de Flight.
Componentes Principales
Enrutamiento
Aprende cómo gestionar rutas para tu aplicación web. Esto también incluye agrupar rutas, parámetros de ruta y middleware.
Middleware
Aprende cómo usar middleware para filtrar solicitudes y respuestas en tu aplicación.
Carga Automática
Aprende cómo cargar automáticamente tus propias clases en tu aplicación.
Solicitudes
Aprende cómo manejar solicitudes y respuestas en tu aplicación.
Respuestas
Aprende cómo enviar respuestas a tus usuarios.
Plantillas HTML
Aprende cómo usar el motor de vistas incorporado para renderizar tus plantillas HTML.
Seguridad
Aprende cómo asegurar tu aplicación contra amenazas de seguridad comunes.
Configuración
Aprende cómo configurar el framework para tu aplicación.
Gestor de Eventos
Aprende cómo usar el sistema de eventos para agregar eventos personalizados a tu aplicación.
Extender Flight
Aprende cómo extender el framework agregando tus propios métodos y clases.
Ganchos de Métodos y Filtrado
Aprende cómo agregar ganchos de eventos a tus métodos y métodos internos del framework.
Contenedor de Inyección de Dependencias (DIC)
Aprende cómo usar contenedores de inyección de dependencias (DIC) para gestionar las dependencias de tu aplicación.
Clases de Utilidad
Colecciones
Las colecciones se usan para contener datos y ser accesibles como un arreglo o como un objeto para facilitar su uso.
Envoltorio JSON
Esto tiene unas pocas funciones simples para hacer que la codificación y decodificación de tu JSON sea consistente.
Envoltorio PDO
PDO a veces puede causar más dolores de cabeza de los necesarios. Esta clase envolvente simple puede hacer que sea significativamente más fácil interactuar con tu base de datos.
Manejador de Archivos Subidos
Una clase simple para ayudar a gestionar archivos subidos y moverlos a una ubicación permanente.
Conceptos Importantes
¿Por Qué un Framework?
Aquí hay un artículo corto sobre por qué deberías usar un framework. Es una buena idea entender los beneficios de usar un framework antes de comenzar a usar uno.
Además, se ha creado un excelente tutorial por @lubiana. Aunque no entra en gran detalle sobre Flight específicamente, esta guía te ayudará a entender algunos de los conceptos principales que rodean a un framework y por qué son beneficiosos de usar. Puedes encontrar el tutorial aquí.
Flight Comparado con Otros Frameworks
Si estás migrando desde otro framework como Laravel, Slim, Fat-Free o Symfony a Flight, esta página te ayudará a entender las diferencias entre los dos.
Otros Temas
Pruebas Unitarias
Sigue esta guía para aprender cómo realizar pruebas unitarias en tu código de Flight para que sea sólido como una roca.
IA y Experiencia del Desarrollador
Aprende cómo Flight funciona con herramientas de IA y flujos de trabajo modernos de desarrolladores para ayudarte a codificar más rápido e inteligente.
Migrando v2 -> v3
La compatibilidad hacia atrás se ha mantenido en su mayor parte, pero hay algunos cambios de los que debes estar al tanto al migrar de v2 a v3.
Learn/unit_testing
Pruebas Unitarias
Resumen
Las pruebas unitarias en Flight te ayudan a asegurar que tu aplicación se comporte como se espera, detecta errores tempranamente y hace que tu código sea más fácil de mantener. Flight está diseñado para trabajar sin problemas con PHPUnit, el framework de pruebas para PHP más popular.
Comprensión
Las pruebas unitarias verifican el comportamiento de pequeñas piezas de tu aplicación (como controladores o servicios) de manera aislada. En Flight, esto significa probar cómo responden tus rutas, controladores y lógica a diferentes entradas—sin depender de estados globales o servicios externos reales.
Principios clave:
- Probar comportamiento, no implementación: Enfócate en lo que hace tu código, no en cómo lo hace.
- Evitar estados globales: Usa inyección de dependencias en lugar de
Flight::set()
oFlight::get()
. - Simular servicios externos: Reemplaza cosas como bases de datos o remitentes de correo con dobles de prueba.
- Mantén las pruebas rápidas y enfocadas: Las pruebas unitarias no deben acceder a bases de datos reales o APIs.
Uso Básico
Configuración de PHPUnit
- Instala PHPUnit con Composer:
composer require --dev phpunit/phpunit
- Crea un directorio
tests
en la raíz de tu proyecto. - Agrega un script de prueba a tu
composer.json
:"scripts": { "test": "phpunit --configuration phpunit.xml" }
- Crea un archivo
phpunit.xml
:<?xml version="1.0" encoding="UTF-8"?> <phpunit bootstrap="vendor/autoload.php"> <testsuites> <testsuite name="Flight Tests"> <directory>tests</directory> </testsuite> </testsuites> </phpunit>
Ahora puedes ejecutar tus pruebas con composer test
.
Probando un Manipulador de Ruta Simple
Supongamos que tienes una ruta que valida un correo electrónico:
// index.php
$app->route('POST /register', [ UserController::class, 'register' ]);
// UserController.php
class UserController {
protected $app;
public function __construct(flight\Engine $app) {
$this->app = $app;
}
public function register() {
$email = $this->app->request()->data->email;
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
return $this->app->json(['status' => 'error', 'message' => 'Invalid email']);
}
return $this->app->json(['status' => 'success', 'message' => 'Valid email']);
}
}
Una prueba simple para este controlador:
use PHPUnit\Framework\TestCase;
use flight\Engine;
class UserControllerTest extends TestCase {
public function testValidEmailReturnsSuccess() {
$app = new Engine();
$app->request()->data->email = 'test@example.com';
$controller = new UserController($app);
$controller->register();
$response = $app->response()->getBody();
$output = json_decode($response, true);
$this->assertEquals('success', $output['status']);
$this->assertEquals('Valid email', $output['message']);
}
public function testInvalidEmailReturnsError() {
$app = new Engine();
$app->request()->data->email = 'invalid-email';
$controller = new UserController($app);
$controller->register();
$response = $app->response()->getBody();
$output = json_decode($response, true);
$this->assertEquals('error', $output['status']);
$this->assertEquals('Invalid email', $output['message']);
}
}
Consejos:
- Simula datos POST usando
$app->request()->data
. - Evita usar estáticos
Flight::
en tus pruebas—usa la instancia$app
.
Usando Inyección de Dependencias para Controladores Probables
Inyecta dependencias (como la base de datos o el remitente de correo) en tus controladores para hacerlos fáciles de simular en pruebas:
use flight\database\PdoWrapper;
class UserController {
protected $app;
protected $db;
protected $mailer;
public function __construct($app, $db, $mailer) {
$this->app = $app;
$this->db = $db;
$this->mailer = $mailer;
}
public function register() {
$email = $this->app->request()->data->email;
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
return $this->app->json(['status' => 'error', 'message' => 'Invalid email']);
}
$this->db->runQuery('INSERT INTO users (email) VALUES (?)', [$email]);
$this->mailer->sendWelcome($email);
return $this->app->json(['status' => 'success', 'message' => 'User registered']);
}
}
Y una prueba con simulaciones:
use PHPUnit\Framework\TestCase;
class UserControllerDICTest extends TestCase {
public function testValidEmailSavesAndSendsEmail() {
$mockDb = $this->createMock(flight\database\PdoWrapper::class);
$mockDb->method('runQuery')->willReturn(true);
$mockMailer = new class {
public $sentEmail = null;
public function sendWelcome($email) { $this->sentEmail = $email; return true; }
};
$app = new flight\Engine();
$app->request()->data->email = 'test@example.com';
$controller = new UserController($app, $mockDb, $mockMailer);
$controller->register();
$response = $app->response()->getBody();
$result = json_decode($response, true);
$this->assertEquals('success', $result['status']);
$this->assertEquals('User registered', $result['message']);
$this->assertEquals('test@example.com', $mockMailer->sentEmail);
}
}
Uso Avanzado
- Simulación: Usa las simulaciones integradas de PHPUnit o clases anónimas para reemplazar dependencias.
- Probando controladores directamente: Instancia controladores con un nuevo
Engine
y simula dependencias. - Evita sobre-simular: Deja que la lógica real se ejecute donde sea posible; solo simula servicios externos.
Ver También
- Guía de Pruebas Unitarias - Una guía completa sobre las mejores prácticas para pruebas unitarias.
- Contenedor de Inyección de Dependencias - Cómo usar DIC para gestionar dependencias y mejorar la probabilidad de pruebas.
- Extensión - Cómo agregar tus propios ayudantes o sobrescribir clases principales.
- Envoltorio PDO - Simplifica las interacciones con la base de datos y es más fácil de simular en pruebas.
- Solicitudes - Manejo de solicitudes HTTP en Flight.
- Respuestas - Envío de respuestas a los usuarios.
- Pruebas Unitarias y Principios SOLID - Aprende cómo los principios SOLID pueden mejorar tus pruebas unitarias.
Solución de Problemas
- Evita usar estados globales (
Flight::set()
,$_SESSION
, etc.) en tu código y pruebas. - Si tus pruebas son lentas, es posible que estés escribiendo pruebas de integración—simula servicios externos para mantener las pruebas unitarias rápidas.
- Si la configuración de pruebas es compleja, considera refactorizar tu código para usar inyección de dependencias.
Registro de Cambios
- v3.15.0 - Agregados ejemplos para inyección de dependencias y simulación.
Learn/flight_vs_symfony
Vuelo vs Symfony
¿Qué es Symfony?
Symfony es un conjunto de componentes reutilizables de PHP y un framework de PHP para proyectos web.
El fundamento estándar sobre el cual se construyen las mejores aplicaciones PHP. Elija cualquiera de los 50 componentes independientes disponibles para sus propias aplicaciones.
Acelere la creación y el mantenimiento de sus aplicaciones web de PHP. Finalice las tareas de codificación repetitivas y disfrute del poder de controlar su código.
Pros en comparación con Vuelo
- Symfony tiene un enorme ecosistema de desarrolladores y módulos que se pueden utilizar para resolver problemas comunes.
- Symfony tiene un ORM completo (Doctrine) que se puede utilizar para interactuar con su base de datos.
- Symfony tiene una gran cantidad de documentación y tutoriales que se pueden utilizar para aprender el framework.
- Symfony tiene podcasts, conferencias, reuniones, videos y otros recursos que se pueden utilizar para aprender el framework.
- Symfony está orientado hacia un desarrollador experimentado que busca construir una aplicación web empresarial con todas las funciones.
Contras en comparación con Vuelo
- Symfony tiene mucho más en marcha bajo el capó que Vuelo. Esto conlleva un costo dramático en términos de rendimiento. Consulte los benchmarks de TechEmpower para obtener más información.
- Vuelo está orientado hacia un desarrollador que busca construir una aplicación web ligera, rápida y fácil de usar.
- Vuelo está orientado hacia la simplicidad y facilidad de uso.
- Una de las características principales de Vuelo es que hace todo lo posible para mantener la compatibilidad hacia atrás.
- Vuelo no tiene dependencias, mientras que Symfony tiene una serie de dependencias
- Vuelo está destinado a desarrolladores que se aventuran en el mundo de los frameworks por primera vez.
- Vuelo también puede realizar aplicaciones a nivel empresarial, pero no tiene tantos ejemplos y tutoriales como Symfony. También requerirá más disciplina por parte del desarrollador para mantener las cosas organizadas y bien estructuradas.
- Vuelo le da al desarrollador más control sobre la aplicación, mientras que Symfony puede introducir algo de magia entre bastidores.
Learn/flight_vs_another_framework
Comparación de Flight con Otro Framework
Si estás migrando de otro framework como Laravel, Slim, Fat-Free o Symfony a Flight, esta página te ayudará a entender las diferencias entre los dos.
Laravel
Laravel es un framework completo que tiene todas las funciones y una increíble comunidad enfocada en el desarrollador, pero a un costo en rendimiento y complejidad.
Ver la comparación entre Laravel y Flight.
Slim
Slim es un micro-framework similar a Flight. Está diseñado para ser ligero y fácil de usar, pero puede ser un poco más complejo que Flight.
Ver la comparación entre Slim y Flight.
Fat-Free
Fat-Free es un framework full-stack en un paquete mucho más pequeño. Aunque tiene todas las herramientas necesarias, tiene una arquitectura de datos que puede hacer que algunos proyectos sean más complejos de lo necesario.
Ver la comparación entre Fat-Free y Flight.
Symfony
Symfony es un framework modular a nivel empresarial que está diseñado para ser flexible y escalable. Para proyectos más pequeños o desarrolladores nuevos, Symfony puede resultar un poco abrumador.
Ver la comparación entre Symfony y Flight.
Learn/pdo_wrapper
Clase Ayudante PDO PdoWrapper
Resumen
La clase PdoWrapper
en Flight es un ayudante amigable para trabajar con bases de datos usando PDO. Simplifica tareas comunes de base de datos, agrega algunos métodos útiles para obtener resultados y devuelve los resultados como Collections para un acceso fácil. También soporta registro de consultas y monitoreo de rendimiento de aplicaciones (APM) para casos de uso avanzados.
Entendiendo
Trabajar con bases de datos en PHP puede ser un poco verboso, especialmente cuando se usa PDO directamente. PdoWrapper
extiende PDO y agrega métodos que hacen que consultar, obtener y manejar resultados sea mucho más fácil. En lugar de manejar declaraciones preparadas y modos de obtención, obtienes métodos simples para tareas comunes, y cada fila se devuelve como una Collection, por lo que puedes usar notación de array u objeto.
Puedes registrar PdoWrapper
como un servicio compartido en Flight, y luego usarlo en cualquier lugar de tu aplicación a través de Flight::db()
.
Uso Básico
Registrando el Ayudante PDO
Primero, registra la clase PdoWrapper
con Flight:
Flight::register('db', \flight\database\PdoWrapper::class, [
'mysql:host=localhost;dbname=cool_db_name', 'user', 'pass', [
PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES \'utf8mb4\'',
PDO::ATTR_EMULATE_PREPARES => false,
PDO::ATTR_STRINGIFY_FETCHES => false,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
]
]);
Ahora puedes usar Flight::db()
en cualquier lugar para obtener tu conexión a la base de datos.
Ejecutando Consultas
runQuery()
function runQuery(string $sql, array $params = []): PDOStatement
Usa esto para INSERTs, UPDATEs, o cuando quieras obtener resultados manualmente:
$db = Flight::db();
$statement = $db->runQuery("SELECT * FROM users WHERE status = ?", ['active']);
while ($row = $statement->fetch()) {
// $row is an array
}
También puedes usarlo para escrituras:
$db->runQuery("INSERT INTO users (name) VALUES (?)", ['Alice']);
$db->runQuery("UPDATE users SET name = ? WHERE id = ?", ['Bob', 1]);
fetchField()
function fetchField(string $sql, array $params = []): mixed
Obtén un solo valor de la base de datos:
$count = Flight::db()->fetchField("SELECT COUNT(*) FROM users WHERE status = ?", ['active']);
fetchRow()
function fetchRow(string $sql, array $params = []): Collection
Obtén una sola fila como una Collection (acceso array/objeto):
$user = Flight::db()->fetchRow("SELECT * FROM users WHERE id = ?", [123]);
echo $user['name'];
// or
echo $user->name;
fetchAll()
function fetchAll(string $sql, array $params = []): array<Collection>
Obtén todas las filas como un array de Collections:
$users = Flight::db()->fetchAll("SELECT * FROM users WHERE status = ?", ['active']);
foreach ($users as $user) {
echo $user['name'];
// or
echo $user->name;
}
Usando Marcadores de Posición IN()
Puedes usar un solo ?
en una cláusula IN()
y pasar un array o una cadena separada por comas:
$ids = [1, 2, 3];
$users = Flight::db()->fetchAll("SELECT * FROM users WHERE id IN (?)", [$ids]);
// or
$users = Flight::db()->fetchAll("SELECT * FROM users WHERE id IN (?)", ['1,2,3']);
Uso Avanzado
Registro de Consultas & APM
Si quieres rastrear el rendimiento de las consultas, habilita el seguimiento APM al registrar:
Flight::register('db', \flight\database\PdoWrapper::class, [
'mysql:host=localhost;dbname=cool_db_name', 'user', 'pass', [/* options */], true // last param enables APM
]);
Después de ejecutar consultas, puedes registrarlas manualmente pero el APM las registrará automáticamente si está habilitado:
Flight::db()->logQueries();
Esto activará un evento (flight.db.queries
) con métricas de conexión y consulta, que puedes escuchar usando el sistema de eventos de Flight.
Ejemplo Completo
Flight::route('/users', function () {
// Get all users
$users = Flight::db()->fetchAll('SELECT * FROM users');
// Stream all users
$statement = Flight::db()->runQuery('SELECT * FROM users');
while ($user = $statement->fetch()) {
echo $user['name'];
}
// Get a single user
$user = Flight::db()->fetchRow('SELECT * FROM users WHERE id = ?', [123]);
// Get a single value
$count = Flight::db()->fetchField('SELECT COUNT(*) FROM users');
// Special IN() syntax
$users = Flight::db()->fetchAll('SELECT * FROM users WHERE id IN (?)', [[1,2,3,4,5]]);
$users = Flight::db()->fetchAll('SELECT * FROM users WHERE id IN (?)', ['1,2,3,4,5']);
// Insert a new user
Flight::db()->runQuery("INSERT INTO users (name, email) VALUES (?, ?)", ['Bob', 'bob@example.com']);
$insert_id = Flight::db()->lastInsertId();
// Update a user
Flight::db()->runQuery("UPDATE users SET name = ? WHERE id = ?", ['Bob', 123]);
// Delete a user
Flight::db()->runQuery("DELETE FROM users WHERE id = ?", [123]);
// Get the number of affected rows
$statement = Flight::db()->runQuery("UPDATE users SET name = ? WHERE name = ?", ['Bob', 'Sally']);
$affected_rows = $statement->rowCount();
});
Ver También
- Collections - Aprende cómo usar la clase Collection para un acceso fácil a los datos.
Solución de Problemas
- Si obtienes un error sobre la conexión a la base de datos, verifica tu DSN, nombre de usuario, contraseña y opciones.
- Todas las filas se devuelven como Collections—si necesitas un array plano, usa
$collection->getData()
. - Para consultas
IN (?)
, asegúrate de pasar un array o una cadena separada por comas.
Registro de Cambios
- v3.2.0 - Lanzamiento inicial de PdoWrapper con métodos básicos de consulta y obtención.
Learn/dependency_injection_container
Contenedor de Inyección de Dependencias
Resumen
El Contenedor de Inyección de Dependencias (DIC) es una potente mejora que te permite gestionar las dependencias de tu aplicación.
Comprensión
La Inyección de Dependencias (DI) es un concepto clave en los frameworks PHP modernos y se utiliza para gestionar la instanciación y configuración de objetos. Algunos ejemplos de bibliotecas DIC son: flightphp/container, Dice, Pimple, PHP-DI, y league/container.
Un DIC es una forma elegante de permitirte crear y gestionar tus clases en un lugar centralizado. Esto es útil cuando necesitas pasar el mismo objeto a múltiples clases (como tus controladores o middleware, por ejemplo).
Uso Básico
La forma antigua de hacer las cosas podría verse así:
require 'vendor/autoload.php';
// clase para gestionar usuarios desde la base de datos
class UserController {
protected PDO $pdo;
public function __construct(PDO $pdo) {
$this->pdo = $pdo;
}
public function view(int $id) {
$stmt = $this->pdo->prepare('SELECT * FROM users WHERE id = :id');
$stmt->execute(['id' => $id]);
print_r($stmt->fetch());
}
}
// en tu archivo routes.php
$db = new PDO('mysql:host=localhost;dbname=test', 'user', 'pass');
$UserController = new UserController($db);
Flight::route('/user/@id', [ $UserController, 'view' ]);
// otras rutas de UserController...
Flight::start();
Puedes ver en el código anterior que estamos creando un nuevo objeto PDO
y pasándolo
a nuestra clase UserController
. Esto está bien para una aplicación pequeña, pero a medida que
tu aplicación crece, encontrarás que estás creando o pasando el mismo objeto PDO
en múltiples lugares. Aquí es donde un DIC resulta útil.
Aquí está el mismo ejemplo usando un DIC (usando Dice):
require 'vendor/autoload.php';
// misma clase que arriba. Nada cambió
class UserController {
protected PDO $pdo;
public function __construct(PDO $pdo) {
$this->pdo = $pdo;
}
public function view(int $id) {
$stmt = $this->pdo->prepare('SELECT * FROM users WHERE id = :id');
$stmt->execute(['id' => $id]);
print_r($stmt->fetch());
}
}
// crear un nuevo contenedor
$container = new \Dice\Dice;
// agregar una regla para decirle al contenedor cómo crear un objeto PDO
// ¡no olvides reasignarlo a sí mismo como a continuación!
$container = $container->addRule('PDO', [
// shared significa que el mismo objeto se retornará cada vez
'shared' => true,
'constructParams' => ['mysql:host=localhost;dbname=test', 'user', 'pass' ]
]);
// Esto registra el manejador del contenedor para que Flight sepa usarlo.
Flight::registerContainerHandler(function($class, $params) use ($container) {
return $container->create($class, $params);
});
// ahora podemos usar el contenedor para crear nuestro UserController
Flight::route('/user/@id', [ UserController::class, 'view' ]);
Flight::start();
Apuesto a que podrías estar pensando que se agregó mucho código extra al ejemplo.
La magia ocurre cuando tienes otro controlador que necesita el objeto PDO
.
// Si todos tus controladores tienen un constructor que necesita un objeto PDO
// ¡cada una de las rutas a continuación lo tendrá inyectado automáticamente!!!
Flight::route('/company/@id', [ CompanyController::class, 'view' ]);
Flight::route('/organization/@id', [ OrganizationController::class, 'view' ]);
Flight::route('/category/@id', [ CategoryController::class, 'view' ]);
Flight::route('/settings', [ SettingsController::class, 'view' ]);
El beneficio adicional de utilizar un DIC es que las pruebas unitarias se vuelven mucho más fáciles. Puedes crear un objeto simulado y pasarlo a tu clase. ¡Esto es un gran beneficio cuando estás escribiendo pruebas para tu aplicación!
Creando un manejador DIC centralizado
Puedes crear un manejador DIC centralizado en tu archivo de servicios extendiendo tu app. Aquí hay un ejemplo:
// services.php
// crear un nuevo contenedor
$container = new \Dice\Dice;
// ¡no olvides reasignarlo a sí mismo como a continuación!
$container = $container->addRule('PDO', [
// shared significa que el mismo objeto se retornará cada vez
'shared' => true,
'constructParams' => ['mysql:host=localhost;dbname=test', 'user', 'pass' ]
]);
// ahora podemos crear un método mapeable para crear cualquier objeto.
Flight::map('make', function($class, $params = []) use ($container) {
return $container->create($class, $params);
});
// Esto registra el manejador del contenedor para que Flight sepa usarlo para controladores/middleware
Flight::registerContainerHandler(function($class, $params) {
Flight::make($class, $params);
});
// supongamos que tenemos la siguiente clase de muestra que toma un objeto PDO en el constructor
class EmailCron {
protected PDO $pdo;
public function __construct(PDO $pdo) {
$this->pdo = $pdo;
}
public function send() {
// código que envía un email
}
}
// Y finalmente puedes crear objetos usando inyección de dependencias
$emailCron = Flight::make(EmailCron::class);
$emailCron->send();
flightphp/container
Flight tiene un plugin que proporciona un contenedor simple compatible con PSR-11 que puedes usar para manejar tu inyección de dependencias. Aquí hay un ejemplo rápido de cómo usarlo:
// index.php por ejemplo
require 'vendor/autoload.php';
use flight\Container;
$container = new Container;
$container->set(PDO::class, fn(): PDO => new PDO('sqlite::memory:'));
Flight::registerContainerHandler([$container, 'get']);
class TestController {
private PDO $pdo;
function __construct(PDO $pdo) {
$this->pdo = $pdo;
}
function index() {
var_dump($this->pdo);
// ¡imprimirá esto correctamente!
}
}
Flight::route('GET /', [TestController::class, 'index']);
Flight::start();
Uso Avanzado de flightphp/container
También puedes resolver dependencias de manera recursiva. Aquí hay un ejemplo:
<?php
require 'vendor/autoload.php';
use flight\Container;
class User {}
interface UserRepository {
function find(int $id): ?User;
}
class PdoUserRepository implements UserRepository {
private PDO $pdo;
function __construct(PDO $pdo) {
$this->pdo = $pdo;
}
function find(int $id): ?User {
// Implementación ...
return null;
}
}
$container = new Container;
$container->set(PDO::class, static fn(): PDO => new PDO('sqlite::memory:'));
$container->set(UserRepository::class, PdoUserRepository::class);
$userRepository = $container->get(UserRepository::class);
var_dump($userRepository);
/*
object(PdoUserRepository)#4 (1) {
["pdo":"PdoUserRepository":private]=>
object(PDO)#3 (0) {
}
}
*/
DICE
También puedes crear tu propio manejador DIC. Esto es útil si tienes un contenedor personalizado que quieres usar que no es PSR-11 (Dice). Consulta la sección de uso básico para saber cómo hacerlo.
Además, hay algunos valores predeterminados útiles que facilitarán tu vida al usar Flight.
Instancia de Engine
Si estás usando la instancia Engine
en tus controladores/middleware, aquí está
cómo la configurarías:
// En algún lugar de tu archivo de bootstrap
$engine = Flight::app();
$container = new \Dice\Dice;
$container = $container->addRule('*', [
'substitutions' => [
// Aquí es donde pasas la instancia
Engine::class => $engine
]
]);
$engine->registerContainerHandler(function($class, $params) use ($container) {
return $container->create($class, $params);
});
// Ahora puedes usar la instancia de Engine en tus controladores/middleware
class MyController {
public function __construct(Engine $app) {
$this->app = $app;
}
public function index() {
$this->app->render('index');
}
}
Agregando Otras Clases
Si tienes otras clases que quieres agregar al contenedor, con Dice es fácil ya que serán resueltas automáticamente por el contenedor. Aquí hay un ejemplo:
$container = new \Dice\Dice;
// Si no necesitas inyectar dependencias en tus clases
// ¡no necesitas definir nada!
Flight::registerContainerHandler(function($class, $params) use ($container) {
return $container->create($class, $params);
});
class MyCustomClass {
public function parseThing() {
return 'thing';
}
}
class UserController {
protected MyCustomClass $MyCustomClass;
public function __construct(MyCustomClass $MyCustomClass) {
$this->MyCustomClass = $MyCustomClass;
}
public function index() {
echo $this->MyCustomClass->parseThing();
}
}
Flight::route('/user', 'UserController->index');
PSR-11
Flight también puede usar cualquier contenedor compatible con PSR-11. Esto significa que puedes usar cualquier contenedor que implemente la interfaz PSR-11. Aquí hay un ejemplo usando el contenedor PSR-11 de League:
require 'vendor/autoload.php';
// misma clase UserController que arriba
$container = new \League\Container\Container();
$container->add(UserController::class)->addArgument(PdoWrapper::class);
$container->add(PdoWrapper::class)
->addArgument('mysql:host=localhost;dbname=test')
->addArgument('user')
->addArgument('pass');
Flight::registerContainerHandler($container);
Flight::route('/user', [ 'UserController', 'view' ]);
Flight::start();
Esto puede ser un poco más verboso que el ejemplo anterior de Dice, ¡aún así cumple el trabajo con los mismos beneficios!
Ver También
- Extending Flight - Aprende cómo puedes agregar inyección de dependencias a tus propias clases extendiendo el framework.
- Configuration - Aprende cómo configurar Flight para tu aplicación.
- Routing - Aprende cómo definir rutas para tu aplicación y cómo funciona la inyección de dependencias con controladores.
- Middleware - Aprende cómo crear middleware para tu aplicación y cómo funciona la inyección de dependencias con middleware.
Solución de Problemas
- Si tienes problemas con tu contenedor, asegúrate de que estás pasando los nombres de clase correctos al contenedor.
Registro de Cambios
- v3.7.0 - Agregada la capacidad de registrar un manejador DIC en Flight.
Learn/middleware
Middleware
Resumen
Flight soporta middleware de rutas y grupos de rutas. El middleware es una parte de tu aplicación donde se ejecuta código antes (o después) de la devolución de llamada de la ruta. Esta es una excelente manera de agregar verificaciones de autenticación de API en tu código, o para validar que el usuario tiene permiso para acceder a la ruta.
Entendimiento
El middleware puede simplificar enormemente tu aplicación. En lugar de herencia compleja de clases abstractas o sobrescrituras de métodos, el middleware te permite controlar tus rutas asignando tu lógica de aplicación personalizada a ellas. Puedes pensar en el middleware como un sándwich. Tienes pan por fuera, y luego capas de ingredientes como lechuga, tomates, carnes y queso. Luego imagina que cada solicitud es como tomar un bocado del sándwich donde comes las capas externas primero y avanzas hacia el centro.
Aquí hay una visualización de cómo funciona el middleware. Luego te mostraremos un ejemplo práctico de cómo funciona esto.
Solicitud de usuario en URL /api ---->
Middleware->before() ejecutado ----->
Callable/método adjunto a /api ejecutado y respuesta generada ------>
Middleware->after() ejecutado ----->
Usuario recibe respuesta del servidor
Y aquí hay un ejemplo práctico:
Usuario navega a URL /dashboard
LoggedInMiddleware->before() se ejecuta
before() verifica una sesión de inicio de sesión válida
si sí, no hace nada y continúa la ejecución
si no, redirige al usuario a /login
Callable/método adjunto a /api ejecutado y respuesta generada
LoggedInMiddleware->after() no tiene nada definido, así que deja que la ejecución continúe
Usuario recibe HTML del dashboard del servidor
Orden de Ejecución
Las funciones de middleware se ejecutan en el orden en que se agregan a la ruta. La ejecución es similar a cómo Slim Framework maneja esto.
Los métodos before()
se ejecutan en el orden agregado, y los métodos after()
se ejecutan en orden inverso.
Ej: Middleware1->before(), Middleware2->before(), Middleware2->after(), Middleware1->after().
Uso Básico
Puedes usar middleware como cualquier método callable, incluyendo una función anónima o una clase (recomendado)
Función Anónima
Aquí hay un ejemplo simple:
Flight::route('/path', function() { echo ' Here I am!'; })->addMiddleware(function() {
echo 'Middleware first!';
});
Flight::start();
// Esto imprimirá "Middleware first! Here I am!"
Nota: Cuando uses una función anónima, el único método que se interpreta es un método
before()
. No puedes definir comportamientoafter()
con una clase anónima.
Usando Clases
El middleware puede (y debe) registrarse como una clase. Si necesitas la funcionalidad "after", debes usar una clase.
class MyMiddleware {
public function before($params) {
echo 'Middleware first!';
}
public function after($params) {
echo 'Middleware last!';
}
}
$MyMiddleware = new MyMiddleware();
Flight::route('/path', function() { echo ' Here I am! '; })->addMiddleware($MyMiddleware);
// también ->addMiddleware([ $MyMiddleware, $MyMiddleware2 ]);
Flight::start();
// Esto mostrará "Middleware first! Here I am! Middleware last!"
También puedes definir solo el nombre de la clase de middleware y se instanciará la clase.
Flight::route('/path', function() { echo ' Here I am! '; })->addMiddleware(MyMiddleware::class);
Nota: Si pasas solo el nombre del middleware, se ejecutará automáticamente por el contenedor de inyección de dependencias y el middleware se ejecutará con los parámetros que necesita. Si no tienes un contenedor de inyección de dependencias registrado, pasará por defecto la instancia de
flight\Engine
en el__construct(Engine $app)
.
Usando Rutas con Parámetros
Si necesitas parámetros de tu ruta, se pasarán en un solo array a tu función de middleware. (function($params) { ... }
o public function before($params) { ... }
). La razón de esto es que puedes estructurar tus parámetros en grupos y en algunos de esos grupos, tus parámetros pueden aparecer en un orden diferente, lo que rompería la función de middleware al referirse al parámetro incorrecto. De esta manera, puedes acceder a ellos por nombre en lugar de por posición.
use flight\Engine;
class RouteSecurityMiddleware {
protected Engine $app;
public function __construct(Engine $app) {
$this->app = $app;
}
public function before(array $params) {
$clientId = $params['clientId'];
// jobId puede o no ser pasado
$jobId = $params['jobId'] ?? 0;
// tal vez si no hay ID de trabajo, no necesitas buscar nada.
if($jobId === 0) {
return;
}
// realiza una búsqueda de algún tipo en tu base de datos
$isValid = !!$this->app->db()->fetchField("SELECT 1 FROM client_jobs WHERE client_id = ? AND job_id = ?", [ $clientId, $jobId ]);
if($isValid !== true) {
$this->app->halt(400, 'You are blocked, muahahaha!');
}
}
}
// routes.php
$router->group('/client/@clientId/job/@jobId', function(Router $router) {
// Este grupo de abajo aún obtiene el middleware padre
// Pero los parámetros se pasan en un solo array
// en el middleware.
$router->group('/job/@jobId', function(Router $router) {
$router->get('', [ JobController::class, 'view' ]);
$router->put('', [ JobController::class, 'update' ]);
$router->delete('', [ JobController::class, 'delete' ]);
// más rutas...
});
}, [ RouteSecurityMiddleware::class ]);
Agrupando Rutas con Middleware
Puedes agregar un grupo de rutas, y luego cada ruta en ese grupo tendrá el mismo middleware también. Esto es útil si necesitas agrupar un montón de rutas por, digamos, un middleware de Auth para verificar la clave API en el encabezado.
// agregado al final del método de grupo
Flight::group('/api', function() {
// Esta ruta "vacía" coincidirá realmente con /api
Flight::route('', function() { echo 'api'; }, false, 'api');
// Esto coincidirá con /api/users
Flight::route('/users', function() { echo 'users'; }, false, 'users');
// Esto coincidirá con /api/users/1234
Flight::route('/users/@id', function($id) { echo 'user:'.$id; }, false, 'user_view');
}, [ new ApiAuthMiddleware() ]);
Si quieres aplicar un middleware global a todas tus rutas, puedes agregar un grupo "vacío":
// agregado al final del método de grupo
Flight::group('', function() {
// Esto sigue siendo /users
Flight::route('/users', function() { echo 'users'; }, false, 'users');
// Y esto sigue siendo /users/1234
Flight::route('/users/@id', function($id) { echo 'user:'.$id; }, false, 'user_view');
}, [ ApiAuthMiddleware::class ]); // o [ new ApiAuthMiddleware() ], lo mismo
Casos de Uso Comunes
Validación de Clave API
Si quisieras proteger tus rutas /api
verificando que la clave API sea correcta, puedes manejarlo fácilmente con middleware.
use flight\Engine;
class ApiMiddleware {
protected Engine $app;
public function __construct(Engine $app) {
$this->app = $app;
}
public function before(array $params) {
$authorizationHeader = $this->app->request()->getHeader('Authorization');
$apiKey = str_replace('Bearer ', '', $authorizationHeader);
// realiza una búsqueda en tu base de datos para la clave api
$apiKeyHash = hash('sha256', $apiKey);
$hasValidApiKey = !!$this->db()->fetchField("SELECT 1 FROM api_keys WHERE hash = ? AND valid_date >= NOW()", [ $apiKeyHash ]);
if($hasValidApiKey !== true) {
$this->app->jsonHalt(['error' => 'Invalid API Key']);
}
}
}
// routes.php
$router->group('/api', function(Router $router) {
$router->get('/users', [ ApiController::class, 'getUsers' ]);
$router->get('/companies', [ ApiController::class, 'getCompanies' ]);
// más rutas...
}, [ ApiMiddleware::class ]);
¡Ahora todas tus rutas API están protegidas por este middleware de validación de clave API que has configurado! Si pones más rutas en el grupo del router, tendrán instantáneamente la misma protección!
Validación de Inicio de Sesión
¿Quieres proteger algunas rutas para que solo estén disponibles para usuarios que han iniciado sesión? ¡Eso se puede lograr fácilmente con middleware!
use flight\Engine;
class LoggedInMiddleware {
protected Engine $app;
public function __construct(Engine $app) {
$this->app = $app;
}
public function before(array $params) {
$session = $this->app->session();
if($session->get('logged_in') !== true) {
$this->app->redirect('/login');
exit;
}
}
}
// routes.php
$router->group('/admin', function(Router $router) {
$router->get('/dashboard', [ DashboardController::class, 'index' ]);
$router->get('/clients', [ ClientController::class, 'index' ]);
// más rutas...
}, [ LoggedInMiddleware::class ]);
Validación de Parámetro de Ruta
¿Quieres proteger a tus usuarios de cambiar valores en la URL para acceder a datos que no deberían? ¡Eso se puede resolver con middleware!
use flight\Engine;
class RouteSecurityMiddleware {
protected Engine $app;
public function __construct(Engine $app) {
$this->app = $app;
}
public function before(array $params) {
$clientId = $params['clientId'];
$jobId = $params['jobId'];
// realiza una búsqueda de algún tipo en tu base de datos
$isValid = !!$this->app->db()->fetchField("SELECT 1 FROM client_jobs WHERE client_id = ? AND job_id = ?", [ $clientId, $jobId ]);
if($isValid !== true) {
$this->app->halt(400, 'You are blocked, muahahaha!');
}
}
}
// routes.php
$router->group('/client/@clientId/job/@jobId', function(Router $router) {
$router->get('', [ JobController::class, 'view' ]);
$router->put('', [ JobController::class, 'update' ]);
$router->delete('', [ JobController::class, 'delete' ]);
// más rutas...
}, [ RouteSecurityMiddleware::class ]);
Manejo de la Ejecución de Middleware
Supongamos que tienes un middleware de autenticación y quieres redirigir al usuario a una página de inicio de sesión si no está autenticado. Tienes un par de opciones a tu disposición:
- Puedes devolver false desde la función de middleware y Flight devolverá automáticamente un error 403 Forbidden, pero sin personalización.
- Puedes redirigir al usuario a una página de inicio de sesión usando
Flight::redirect()
. - Puedes crear un error personalizado dentro del middleware y detener la ejecución de la ruta.
Simple y Directo
Aquí hay un ejemplo simple de return false;
:
class MyMiddleware {
public function before($params) {
$hasUserKey = Flight::session()->exists('user');
if ($hasUserKey === false) {
return false;
}
// ya que es verdadero, todo sigue adelante
}
}
Ejemplo de Redirección
Aquí hay un ejemplo de redirigir al usuario a una página de inicio de sesión:
class MyMiddleware {
public function before($params) {
$hasUserKey = Flight::session()->exists('user');
if ($hasUserKey === false) {
Flight::redirect('/login');
exit;
}
}
}
Ejemplo de Error Personalizado
Supongamos que necesitas lanzar un error JSON porque estás construyendo una API. Puedes hacerlo así:
class MyMiddleware {
public function before($params) {
$authorization = Flight::request()->getHeader('Authorization');
if(empty($authorization)) {
Flight::jsonHalt(['error' => 'You must be logged in to access this page.'], 403);
// o
Flight::json(['error' => 'You must be logged in to access this page.'], 403);
exit;
// o
Flight::halt(403, json_encode(['error' => 'You must be logged in to access this page.']);
}
}
}
Ver También
- Routing - Cómo mapear rutas a controladores y renderizar vistas.
- Requests - Entendiendo cómo manejar solicitudes entrantes.
- Responses - Cómo personalizar respuestas HTTP.
- Dependency Injection - Simplificando la creación y gestión de objetos en rutas.
- Why a Framework? - Entendiendo los beneficios de usar un framework como Flight.
- Middleware Execution Strategy Example
Solución de Problemas
- Si tienes una redirección en tu middleware, pero tu aplicación no parece estar redirigiendo, asegúrate de agregar una declaración
exit;
en tu middleware.
Registro de Cambios
- v3.1: Agregado soporte para middleware.
Learn/filtering
Filtrado
Resumen
Flight te permite filtrar métodos mapeados antes y después de que se llamen.
Comprensión
No hay ganchos predefinidos que necesites memorizar. Puedes filtrar cualquiera de los métodos predeterminados del framework, así como cualquier método personalizado que hayas mapeado.
Una función de filtro se ve así:
/**
* @param array $params Los parámetros pasados al método que se está filtrando.
* @param string $output (solo v2 con búfer de salida) La salida del método que se está filtrando.
* @return bool Devuelve true/void o no devuelvas nada para continuar la cadena, false para romper la cadena.
*/
function (array &$params, string &$output): bool {
// Código de filtro
}
Usando las variables pasadas, puedes manipular los parámetros de entrada y/o la salida.
Puedes hacer que un filtro se ejecute antes de un método haciendo:
Flight::before('start', function (array &$params, string &$output): bool {
// Haz algo
});
Puedes hacer que un filtro se ejecute después de un método haciendo:
Flight::after('start', function (array &$params, string &$output): bool {
// Haz algo
});
Puedes agregar tantos filtros como quieras a cualquier método. Se llamarán en el orden en que se declaren.
Aquí hay un ejemplo del proceso de filtrado:
// Mapa un método personalizado
Flight::map('hello', function (string $name) {
return "Hello, $name!";
});
// Agrega un filtro antes
Flight::before('hello', function (array &$params, string &$output): bool {
// Manipula el parámetro
$params[0] = 'Fred';
return true;
});
// Agrega un filtro después
Flight::after('hello', function (array &$params, string &$output): bool {
// Manipula la salida
$output .= " Have a nice day!";
return true;
});
// Invoca el método personalizado
echo Flight::hello('Bob');
Esto debería mostrar:
Hello Fred! Have a nice day!
Si has definido múltiples filtros, puedes romper la cadena devolviendo false
en cualquiera de tus funciones de filtro:
Flight::before('start', function (array &$params, string &$output): bool {
echo 'one';
return true;
});
Flight::before('start', function (array &$params, string &$output): bool {
echo 'two';
// Esto terminará la cadena
return false;
});
// Esto no se llamará
Flight::before('start', function (array &$params, string &$output): bool {
echo 'three';
return true;
});
Nota: Los métodos principales como
map
yregister
no se pueden filtrar porque se llaman directamente y no se invocan dinámicamente. Consulta Extending Flight para obtener más información.
Ver también
Solución de problemas
- Asegúrate de devolver
false
desde tus funciones de filtro si quieres que la cadena se detenga. Si no devuelves nada, la cadena continuará.
Registro de cambios
- v2.0 - Lanzamiento inicial.
Learn/requests
Solicitudes
Resumen
Flight encapsula la solicitud HTTP en un solo objeto, que se puede acceder haciendo:
$request = Flight::request();
Comprensión
Las solicitudes HTTP son uno de los aspectos centrales para entender sobre el ciclo de vida de HTTP. Un usuario realiza una acción en un navegador web o un cliente HTTP, y envían una serie de encabezados, cuerpo, URL, etc. a su proyecto. Puede capturar estos encabezados (el idioma del navegador, qué tipo de compresión pueden manejar, el agente de usuario, etc.) y capturar el cuerpo y la URL que se envía a su aplicación Flight. Estas solicitudes son esenciales para que su app entienda qué hacer a continuación.
Uso básico
PHP tiene varios super globals incluyendo $_GET
, $_POST
, $_REQUEST
, $_SERVER
, $_FILES
y $_COOKIE
. Flight abstrae estos en colecciones prácticas Collections. Puede acceder a las propiedades query
, data
, cookies
y files
como arrays u objetos.
Nota: Se DESACONSEJA EN GRAN MEDIDA usar estos super globals en su proyecto y deben referenciarse a través del objeto
request()
.Nota: No hay abstracción disponible para
$_ENV
.
$_GET
Puede acceder al array $_GET
a través de la propiedad query
:
// GET /search?keyword=something
Flight::route('/search', function(){
$keyword = Flight::request()->query['keyword'];
// o
$keyword = Flight::request()->query->keyword;
echo "You are searching for: $keyword";
// consultar una base de datos o algo más con el $keyword
});
$_POST
Puede acceder al array $_POST
a través de la propiedad data
:
Flight::route('POST /submit', function(){
$name = Flight::request()->data['name'];
$email = Flight::request()->data['email'];
// o
$name = Flight::request()->data->name;
$email = Flight::request()->data->email;
echo "You submitted: $name, $email";
// guardar en una base de datos o algo más con el $name y $email
});
$_COOKIE
Puede acceder al array $_COOKIE
a través de la propiedad cookies
:
Flight::route('GET /login', function(){
$savedLogin = Flight::request()->cookies['myLoginCookie'];
// o
$savedLogin = Flight::request()->cookies->myLoginCookie;
// verificar si realmente está guardado o no y si lo está, iniciar sesión automáticamente
if($savedLogin) {
Flight::redirect('/dashboard');
return;
}
});
Para obtener ayuda sobre cómo establecer nuevos valores de cookies, vea overclokk/cookie
$_SERVER
Hay un método abreviado disponible para acceder al array $_SERVER
a través del método getVar()
:
$host = Flight::request()->getVar('HTTP_HOST');
$_FILES
Puede acceder a los archivos subidos a través de la propiedad files
:
// acceso crudo a la propiedad $_FILES. Vea abajo para el enfoque recomendado
$uploadedFile = Flight::request()->files['myFile'];
// o
$uploadedFile = Flight::request()->files->myFile;
Vea Uploaded File Handler para más información.
Procesamiento de subidas de archivos
v3.12.0
Puede procesar subidas de archivos usando el framework con algunos métodos de ayuda. Básicamente se reduce a extraer los datos del archivo de la solicitud y moverlo a una nueva ubicación.
Flight::route('POST /upload', function(){
// Si tenía un campo de entrada como <input type="file" name="myFile">
$uploadedFileData = Flight::request()->getUploadedFiles();
$uploadedFile = $uploadedFileData['myFile'];
$uploadedFile->moveTo('/path/to/uploads/' . $uploadedFile->getClientFilename());
});
Si tiene múltiples archivos subidos, puede iterar a través de ellos:
Flight::route('POST /upload', function(){
// Si tenía un campo de entrada como <input type="file" name="myFiles[]">
$uploadedFiles = Flight::request()->getUploadedFiles()['myFiles'];
foreach ($uploadedFiles as $uploadedFile) {
$uploadedFile->moveTo('/path/to/uploads/' . $uploadedFile->getClientFilename());
}
});
Nota de seguridad: Siempre valide y sanitice la entrada del usuario, especialmente al tratar con subidas de archivos. Siempre valide el tipo de extensiones que permitirá subir, pero también debe validar los "magic bytes" del archivo para asegurar que realmente sea el tipo de archivo que el usuario afirma que es. Hay artículos y bibliotecas disponibles para ayudar con esto.
Cuerpo de la solicitud
Para obtener el cuerpo crudo de la solicitud HTTP, por ejemplo al tratar con solicitudes POST/PUT, puede hacer:
Flight::route('POST /users/xml', function(){
$xmlBody = Flight::request()->getBody();
// hacer algo con el XML que fue enviado.
});
Cuerpo JSON
Si recibe una solicitud con el tipo de contenido application/json
y los datos de ejemplo {"id": 123}
, estará disponible desde la propiedad data
:
$id = Flight::request()->data->id;
Encabezados de la solicitud
Puede acceder a los encabezados de la solicitud usando el método getHeader()
o getHeaders()
:
// Tal vez necesite el encabezado Authorization
$host = Flight::request()->getHeader('Authorization');
// o
$host = Flight::request()->header('Authorization');
// Si necesita obtener todos los encabezados
$headers = Flight::request()->getHeaders();
// o
$headers = Flight::request()->headers();
Método de la solicitud
Puede acceder al método de la solicitud usando la propiedad method
o el método getMethod()
:
$method = Flight::request()->method; // realmente poblado por getMethod()
$method = Flight::request()->getMethod();
Nota: El método getMethod()
primero extrae el método de $_SERVER['REQUEST_METHOD']
, luego puede ser sobrescrito por $_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE']
si existe o $_REQUEST['_method']
si existe.
Propiedades del objeto de solicitud
El objeto de solicitud proporciona las siguientes propiedades:
- body - El cuerpo crudo de la solicitud HTTP
- url - La URL solicitada
- base - El subdirectorio padre de la URL
- method - El método de la solicitud (GET, POST, PUT, DELETE)
- referrer - La URL de referencia
- ip - Dirección IP del cliente
- ajax - Si la solicitud es una solicitud AJAX
- scheme - El protocolo del servidor (http, https)
- user_agent - Información del navegador
- type - El tipo de contenido
- length - La longitud del contenido
- query - Parámetros de la cadena de consulta
- data - Datos POST o datos JSON
- cookies - Datos de cookies
- files - Archivos subidos
- secure - Si la conexión es segura
- accept - Parámetros de aceptación HTTP
- proxy_ip - Dirección IP del proxy del cliente. Escanea el array
$_SERVER
en busca deHTTP_CLIENT_IP
,HTTP_X_FORWARDED_FOR
,HTTP_X_FORWARDED
,HTTP_X_CLUSTER_CLIENT_IP
,HTTP_FORWARDED_FOR
,HTTP_FORWARDED
en ese orden. - host - El nombre de host de la solicitud
- servername - El SERVER_NAME de
$_SERVER
Métodos de ayuda
Hay algunos métodos de ayuda para ensamblar partes de una URL o tratar con ciertos encabezados.
URL completa
Puede acceder a la URL completa de la solicitud usando el método getFullUrl()
:
$url = Flight::request()->getFullUrl();
// https://example.com/some/path?foo=bar
URL base
Puede acceder a la URL base usando el método getBaseUrl()
:
// http://example.com/path/to/something/cool?query=yes+thanks
$url = Flight::request()->getBaseUrl();
// https://example.com
// Note, no trailing slash.
Análisis de consultas
Puede pasar una URL al método parseQuery()
para analizar la cadena de consulta en un array asociativo:
$query = Flight::request()->parseQuery('https://example.com/some/path?foo=bar');
// ['foo' => 'bar']
Negociación de tipos de aceptación de contenido
v3.17.2
Puede usar el método negotiateContentType()
para determinar el mejor tipo de contenido para responder basado en el encabezado Accept
enviado por el cliente.
// Ejemplo de encabezado Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
// Lo de abajo define lo que soporta.
$availableTypes = ['application/json', 'application/xml'];
$typeToServe = Flight::request()->negotiateContentType($availableTypes);
if ($typeToServe === 'application/json') {
// Servir respuesta JSON
} elseif ($typeToServe === 'application/xml') {
// Servir respuesta XML
} else {
// Por defecto algo más o lanzar un error
}
Nota: Si ninguno de los tipos disponibles se encuentra en el encabezado
Accept
, el método retornaránull
. Si no hay encabezadoAccept
definido, el método retornará el primer tipo en el array$availableTypes
.
Ver también
- Routing - Vea cómo mapear rutas a controladores y renderizar vistas.
- Responses - Cómo personalizar respuestas HTTP.
- Why a Framework? - Cómo las solicitudes encajan en el panorama general.
- Collections - Trabajando con colecciones de datos.
- Uploaded File Handler - Manejo de subidas de archivos.
Solución de problemas
request()->ip
yrequest()->proxy_ip
pueden ser diferentes si su servidor web está detrás de un proxy, balanceador de carga, etc.
Registro de cambios
- v3.17.2 - Agregado negotiateContentType()
- v3.12.0 - Agregada capacidad para manejar subidas de archivos a través del objeto de solicitud.
- v1.0 - Lanzamiento inicial.
Learn/why_frameworks
¿Por qué un Framework?
Algunos programadores se oponen vehementemente a utilizar frameworks. Argumentan que los frameworks son pesados, lentos y difíciles de aprender. Dicen que los frameworks son innecesarios y que puedes escribir un código mejor sin ellos. Ciertamente se pueden hacer algunos puntos válidos sobre las desventajas de usar frameworks. Sin embargo, también existen muchas ventajas al utilizar frameworks.
Razones para usar un Framework
Aquí hay algunas razones por las cuales podrías considerar utilizar un framework:
- Desarrollo Rápido: Los frameworks proporcionan mucha funcionalidad de serie. Esto significa que puedes construir aplicaciones web más rápidamente. No necesitas escribir tanto código porque el framework proporciona mucha de la funcionalidad que necesitas.
- Consistencia: Los frameworks ofrecen una forma consistente de hacer las cosas. Esto facilita tu comprensión de cómo funciona el código y también facilita que otros desarrolladores entiendan tu código. Si lo tienes guion por guion, podrías perder consistencia entre guiones, especialmente si estás trabajando con un equipo de desarrolladores.
- Seguridad: Los frameworks ofrecen funciones de seguridad que ayudan a proteger tus aplicaciones web de amenazas de seguridad comunes. Esto significa que no tienes que preocuparte tanto por la seguridad porque el framework se encarga de gran parte de ello.
- Comunidad: Los frameworks cuentan con grandes comunidades de desarrolladores que contribuyen al framework. Esto significa que puedes obtener ayuda de otros desarrolladores cuando tengas preguntas o problemas. También significa que hay muchos recursos disponibles para ayudarte a aprender cómo utilizar el framework.
- Mejores Prácticas: Los frameworks están construidos utilizando mejores prácticas. Esto significa que puedes aprender del framework y usar las mismas mejores prácticas en tu propio código. Esto puede ayudarte a ser un mejor programador. A veces no sabes lo que no sabes y eso puede perjudicarte al final.
- Extensibilidad: Los frameworks están diseñados para ser extendidos. Esto significa que puedes agregar tu propia funcionalidad al framework. Esto te permite construir aplicaciones web adaptadas a tus necesidades específicas.
Flight es un micro-framework. Esto significa que es pequeño y ligero. No proporciona tanta funcionalidad como frameworks más grandes como Laravel o Symfony. Sin embargo, proporciona mucha de la funcionalidad que necesitas para construir aplicaciones web. También es fácil de aprender y usar. Esto lo convierte en una buena elección para construir aplicaciones web rápidamente y fácilmente. Si eres nuevo en los frameworks, Flight es un gran framework para principiantes con el que empezar. Te ayudará a entender las ventajas de usar frameworks sin abrumarte con demasiada complejidad. Después de tener algo de experiencia con Flight, será más fácil pasar a frameworks más complejos como Laravel o Symfony, sin embargo, Flight aún puede crear una aplicación robusta y exitosa.
¿Qué es el Enrutamiento?
El enrutamiento es el núcleo del framework Flight, ¿pero qué es exactamente? Enrutamiento es el proceso de tomar una URL y emparejarla con una función específica en tu código.
Esto es cómo puedes hacer que tu sitio web haga cosas diferentes basadas en la URL que se solicita. Por ejemplo, es posible que desees mostrar el perfil de un usuario cuando
visitan /usuario/1234
, pero mostrar una lista de todos los usuarios cuando visitan /usuarios
. Todo esto se hace a través del enrutamiento.
Podría funcionar algo así:
- Un usuario va a tu navegador y escribe
http://ejemplo.com/usuario/1234
. - El servidor recibe la solicitud y mira la URL y la pasa a tu código de aplicación de Flight.
- Digamos que en tu código de Flight tienes algo así como
Flight::route('/usuario/@id', [ 'ControladorUsuario', 'verPerfilUsuario' ]);
. Tu código de la aplicación de Flight mira la URL y ve que coincide con una ruta que has definido, y luego ejecuta el código que has definido para esa ruta. - El enrutador de Flight luego ejecutará y llamará el método
verPerfilUsuario($id)
en la claseControladorUsuario
, pasando el1234
como el argumento$id
en el método. - El código en tu método
verPerfilUsuario()
se ejecutará y hará lo que le hayas indicado. Podrías terminar imprimiendo algo de HTML para la página del perfil del usuario, o si se trata de una API RESTful, podrías imprimir una respuesta JSON con la información del usuario. - Flight envuelve esto en un bonito lazo, genera los encabezados de respuesta y lo envía de vuelta al navegador del usuario.
- ¡El usuario se llena de alegría y se da un cálido abrazo a sí mismo!
¿Y por qué es importante?
¡Tener un enrutador centralizado adecuado puede realmente hacer tu vida mucho más fácil! Al principio, podría ser difícil verlo. Aquí hay algunas razones por las cuales:
- Enrutamiento Centralizado: Puedes mantener todas tus rutas en un solo lugar. Esto facilita ver qué rutas tienes y qué hacen. También facilita cambiarlas si es necesario.
- Parámetros de Ruta: Puedes usar parámetros de ruta para pasar datos a tus métodos de ruta. Esta es una excelente manera de mantener tu código limpio y organizado.
- Grupos de Rutas: Puedes agrupar rutas juntas. Esto es excelente para mantener tu código organizado y para aplicar middleware a un grupo de rutas.
- Alias de Ruta: Puedes asignar un alias a una ruta, para que la URL pueda generarse dinámicamente más tarde en tu código (como una plantilla, por ejemplo). Ej: en lugar de codificar
/usuario/1234
en tu código, podrías en su lugar hacer referencia al aliasvista_usuario
y pasar elid
como parámetro. Esto es útil en caso de que decidas cambiarlo a/admin/usuario/1234
más adelante. No tendrías que cambiar todas tus URLs codificadas, solo la URL vinculada a la ruta. - Middleware de Ruta: Puedes agregar middleware a tus rutas. El middleware es increíblemente potente para agregar comportamientos específicos a tu aplicación como autenticar que cierto usuario pueda acceder a una ruta o grupo de rutas.
Seguro que estás familiarizado con la forma guion por guion de crear un sitio web. Podrías tener un archivo llamado index.php
que tiene un montón de declaraciones if
para verificar la URL y luego ejecutar una función específica basada en la URL. Esto es una forma de enrutamiento, pero no es muy organizado y puede
salirse de control rápidamente. El sistema de enrutamiento de Flight es una forma mucho más organizada y poderosa de manejar el enrutamiento.
¿Esto?
// /usuario/ver_perfil.php?id=1234
if ($_GET['id']) {
$id = $_GET['id'];
verPerfilUsuario($id);
}
// /usuario/editar_perfil.php?id=1234
if ($_GET['id']) {
$id = $_GET['id'];
editarPerfilUsuario($id);
}
// etc...
¿O esto?
// index.php
Flight::route('/usuario/@id', [ 'ControladorUsuario', 'verPerfilUsuario' ]);
Flight::route('/usuario/@id/editar', [ 'ControladorUsuario', 'editarPerfilUsuario' ]);
// En tal vez tu app/controladores/ControladorUsuario.php
class ControladorUsuario {
public function verPerfilUsuario($id) {
// hacer algo
}
public function editarPerfilUsuario($id) {
// hacer algo
}
}
¡Espero que comiences a ver los beneficios de usar un sistema de enrutamiento centralizado. Es mucho más fácil de gestionar y entender a largo plazo!
Solicitudes y Respuestas
Flight proporciona una forma simple y fácil de manejar solicitudes y respuestas. Esto es lo básico de lo que hace un framework web. Toma una solicitud de un navegador del usuario, la procesa, y luego envía una respuesta. Así es como puedes construir aplicaciones web que hagan cosas como mostrar el perfil de un usuario, permitir a un usuario iniciar sesión o permitir a un usuario publicar una nueva publicación en un blog.
Solicitudes
Una solicitud es lo que un navegador del usuario envía a tu servidor cuando visita tu sitio web. Esta solicitud contiene información sobre lo que el usuario quiere hacer. Por ejemplo, podría contener información sobre qué URL quiere visitar el usuario, qué datos quiere enviar el usuario a tu servidor, o qué tipo de datos quiere recibir del servidor. Es importante tener en cuenta que una solicitud es de solo lectura. No puedes cambiar la solicitud, pero puedes leer de ella.
Flight proporciona una forma simple de acceder a información sobre la solicitud. Puedes acceder a información sobre la solicitud utilizando el método Flight::request()
Este método devuelve un objeto Request
que contiene información sobre la solicitud. Puedes usar este objeto para acceder a información sobre la solicitud,
como la URL, el método o los datos que el usuario envió a tu servidor.
Respuestas
Una respuesta es lo que tu servidor envía de vuelta al navegador del usuario cuando visita tu sitio web. Esta respuesta contiene información sobre lo que tu servidor quiere hacer. Por ejemplo, podría contener información sobre qué tipo de datos tu servidor quiere enviar al usuario, qué tipo de datos tu servidor quiere recibir del usuario, o qué tipo de datos tu servidor quiere almacenar en la computadora del usuario.
Flight proporciona una forma simple de enviar una respuesta al navegador del usuario. Puedes enviar una respuesta usando el método Flight::response()
Este método toma un objeto Response
como argumento y envía la respuesta al navegador del usuario. Puedes usar este objeto para enviar una respuesta al navegador del usuario,
como HTML, JSON o un archivo. Flight te ayuda a generar automáticamente algunas partes de la respuesta para facilitar las cosas, pero en última instancia tú tienes
control sobre lo que envías de vuelta al usuario.
Learn/responses
Respuestas
Resumen
Flight ayuda a generar parte de los encabezados de respuesta por ti, pero tú tienes la mayoría del control sobre lo que envías de vuelta al usuario. La mayoría del tiempo accederás directamente al objeto response()
, pero Flight tiene algunos métodos auxiliares para configurar algunos de los encabezados de respuesta por ti.
Comprensión
Después de que el usuario envíe su solicitud a tu aplicación, necesitas generar una respuesta adecuada para ellos. Te han enviado información como el idioma que prefieren, si pueden manejar ciertos tipos de compresión, su agente de usuario, etc., y después de procesar todo, es hora de enviarles una respuesta adecuada. Esto puede ser configurar encabezados, generar un cuerpo de HTML o JSON para ellos, o redirigirlos a una página.
Uso básico
Envío de un cuerpo de respuesta
Flight usa ob_start()
para almacenar en búfer la salida. Esto significa que puedes usar echo
o print
para enviar una respuesta al usuario y Flight la capturará y la enviará de vuelta al usuario con los encabezados apropiados.
// Esto enviará "Hello, World!" al navegador del usuario
Flight::route('/', function() {
echo "Hello, World!";
});
// HTTP/1.1 200 OK
// Content-Type: text/html
//
// Hello, World!
Como alternativa, puedes llamar al método write()
para agregar al cuerpo también.
// Esto enviará "Hello, World!" al navegador del usuario
Flight::route('/', function() {
// verboso, pero a veces hace el trabajo cuando lo necesitas
Flight::response()->write("Hello, World!");
// si quieres recuperar el cuerpo que has configurado en este punto
// puedes hacerlo así
$body = Flight::response()->getBody();
});
JSON
Flight proporciona soporte para enviar respuestas JSON y JSONP. Para enviar una respuesta JSON, pasa algunos datos para que se codifiquen en JSON:
Flight::route('/@companyId/users', function(int $companyId) {
// de alguna manera extrae tus usuarios de una base de datos por ejemplo
$users = Flight::db()->fetchAll("SELECT id, first_name, last_name FROM users WHERE company_id = ?", [ $companyId ]);
Flight::json($users);
});
// [{"id":1,"first_name":"Bob","last_name":"Jones"}, /* more users */ ]
Nota: Por defecto, Flight enviará un encabezado
Content-Type: application/json
con la respuesta. También usará las banderasJSON_THROW_ON_ERROR
yJSON_UNESCAPED_SLASHES
al codificar el JSON.
JSON con código de estado
También puedes pasar un código de estado como segundo argumento:
Flight::json(['id' => 123], 201);
JSON con impresión bonita
También puedes pasar un argumento en la última posición para habilitar la impresión bonita:
Flight::json(['id' => 123], 200, true, 'utf-8', JSON_PRETTY_PRINT);
Cambiar el orden de los argumentos JSON
Flight::json()
es un método muy antiguo, pero el objetivo de Flight es mantener la compatibilidad hacia atrás para los proyectos. En realidad es muy simple si quieres rehacer el orden de los argumentos para usar una sintaxis más simple, puedes simplemente remapear el método JSON como cualquier otro método de Flight:
Flight::map('json', function($data, $code = 200, $options = 0) {
// ahora no tienes que usar `true, 'utf-8'` al usar el método json()!
Flight::_json($data, $code, true, 'utf-8', $options);
}
// Y ahora se puede usar así
Flight::json(['id' => 123], 200, JSON_PRETTY_PRINT);
JSON y detención de ejecución
v3.10.0
Si quieres enviar una respuesta JSON y detener la ejecución, puedes usar el método jsonHalt()
. Esto es útil para casos en los que estás verificando quizás algún tipo de autorización y si el usuario no está autorizado, puedes enviar una respuesta JSON inmediatamente, limpiar el contenido del cuerpo existente y detener la ejecución.
Flight::route('/users', function() {
$authorized = someAuthorizationCheck();
// Verifica si el usuario está autorizado
if($authorized === false) {
Flight::jsonHalt(['error' => 'Unauthorized'], 401);
// no se necesita exit; aquí.
}
// Continúa con el resto de la ruta
});
Antes de v3.10.0, tendrías que hacer algo como esto:
Flight::route('/users', function() {
$authorized = someAuthorizationCheck();
// Verifica si el usuario está autorizado
if($authorized === false) {
Flight::halt(401, json_encode(['error' => 'Unauthorized']));
}
// Continúa con el resto de la ruta
});
Limpieza de un cuerpo de respuesta
Si quieres limpiar el cuerpo de la respuesta, puedes usar el método clearBody
:
Flight::route('/', function() {
if($someCondition) {
Flight::response()->write("Hello, World!");
} else {
Flight::response()->clearBody();
}
});
El caso de uso anterior probablemente no es común, sin embargo podría ser más común si se usara en un middleware.
Ejecución de un callback en el cuerpo de la respuesta
Puedes ejecutar un callback en el cuerpo de la respuesta usando el método addResponseBodyCallback
:
Flight::route('/users', function() {
$db = Flight::db();
$users = $db->fetchAll("SELECT * FROM users");
Flight::render('users_table', ['users' => $users]);
});
// Esto comprime con gzip todas las respuestas para cualquier ruta
Flight::response()->addResponseBodyCallback(function($body) {
return gzencode($body, 9);
});
Puedes agregar múltiples callbacks y se ejecutarán en el orden en que se agregaron. Dado que esto puede aceptar cualquier llamable, puede aceptar un array de clase [ $class, 'method' ]
, una closure $strReplace = function($body) { str_replace('hi', 'there', $body); };
, o un nombre de función 'minify'
si tuvieras una función para minificar tu código HTML por ejemplo.
Nota: Los callbacks de ruta no funcionarán si estás usando la opción de configuración flight.v2.output_buffering
.
Callback de ruta específica
Si quisieras que esto solo se aplique a una ruta específica, podrías agregar el callback en la ruta misma:
Flight::route('/users', function() {
$db = Flight::db();
$users = $db->fetchAll("SELECT * FROM users");
Flight::render('users_table', ['users' => $users]);
// Esto comprime con gzip solo la respuesta para esta ruta
Flight::response()->addResponseBodyCallback(function($body) {
return gzencode($body, 9);
});
});
Opción de middleware
También puedes usar middleware para aplicar el callback a todas las rutas a través de middleware:
// MinifyMiddleware.php
class MinifyMiddleware {
public function before() {
// Aplica el callback aquí en el objeto response().
Flight::response()->addResponseBodyCallback(function($body) {
return $this->minify($body);
});
}
protected function minify(string $body): string {
// minifica el cuerpo de alguna manera
return $body;
}
}
// index.php
Flight::group('/users', function() {
Flight::route('', function() { /* ... */ });
Flight::route('/@id', function($id) { /* ... */ });
}, [ new MinifyMiddleware() ]);
Códigos de estado
Puedes configurar el código de estado de la respuesta usando el método status
:
Flight::route('/@id', function($id) {
if($id == 123) {
Flight::response()->status(200);
echo "Hello, World!";
} else {
Flight::response()->status(403);
echo "Forbidden";
}
});
Si quieres obtener el código de estado actual, puedes usar el método status
sin argumentos:
Flight::response()->status(); // 200
Configuración de un encabezado de respuesta
Puedes configurar un encabezado como el tipo de contenido de la respuesta usando el método header
:
// Esto enviará "Hello, World!" al navegador del usuario en texto plano
Flight::route('/', function() {
Flight::response()->header('Content-Type', 'text/plain');
// o
Flight::response()->setHeader('Content-Type', 'text/plain');
echo "Hello, World!";
});
Redirección
Puedes redirigir la solicitud actual usando el método redirect()
y pasando una nueva URL:
Flight::route('/login', function() {
$username = Flight::request()->data->username;
$password = Flight::request()->data->password;
$passwordConfirm = Flight::request()->data->password_confirm;
if($password !== $passwordConfirm) {
Flight::redirect('/new/location');
return; // esto es necesario para que la funcionalidad a continuación no se ejecute
}
// agrega el nuevo usuario...
Flight::db()->runQuery("INSERT INTO users ....");
Flight::redirect('/admin/dashboard');
});
Nota: Por defecto, Flight envía un código de estado HTTP 303 ("See Other"). Puedes configurar opcionalmente un código personalizado:
Flight::redirect('/new/location', 301); // permanente
Detención de la ejecución de la ruta
Puedes detener el framework y salir inmediatamente en cualquier punto llamando al método halt
:
Flight::halt();
También puedes especificar un código de estado HTTP
y mensaje opcionales:
Flight::halt(200, 'Be right back...');
Llamar a halt
descartará cualquier contenido de respuesta hasta ese punto y detendrá toda la ejecución. Si quieres detener el framework y generar la respuesta actual, usa el método stop
:
Flight::stop($httpStatusCode = null);
Nota:
Flight::stop()
tiene un comportamiento extraño, como que generará la respuesta pero continuará ejecutando tu script, lo cual podría no ser lo que buscas. Puedes usarexit
oreturn
después de llamar aFlight::stop()
para prevenir la ejecución adicional, pero en general se recomienda usarFlight::halt()
.
Esto guardará la clave y el valor del encabezado en el objeto de respuesta. Al final del ciclo de vida de la solicitud, construirá los encabezados y enviará una respuesta.
Uso avanzado
Envío de un encabezado inmediatamente
Puede haber veces en las que necesites hacer algo personalizado con el encabezado y necesites enviar el encabezado en esa misma línea de código con la que estás trabajando. Si estás configurando una ruta transmitida, esto es lo que necesitarías. Eso se logra a través de response()->setRealHeader()
.
Flight::route('/', function() {
Flight::response()->setRealHeader('Content-Type: text/plain');
echo 'Streaming response...';
sleep(5);
echo 'Done!';
})->stream();
JSONP
Para solicitudes JSONP, puedes pasar opcionalmente el nombre del parámetro de consulta que estás usando para definir tu función de callback:
Flight::jsonp(['id' => 123], 'q');
Entonces, al hacer una solicitud GET usando ?q=my_func
, deberías recibir la salida:
my_func({"id":123});
Si no pasas un nombre de parámetro de consulta, se usará jsonp
por defecto.
Nota: Si aún estás usando solicitudes JSONP en 2025 y más allá, únete al chat y cuéntanos por qué! ¡Nos encanta escuchar algunas buenas historias de batalla/horror!
Limpieza de datos de respuesta
Puedes limpiar el cuerpo de la respuesta y los encabezados usando el método clear()
. Esto limpiará cualquier encabezado asignado a la respuesta, limpiará el cuerpo de la respuesta y configurará el código de estado en 200
.
Flight::response()->clear();
Limpieza solo del cuerpo de respuesta
Si solo quieres limpiar el cuerpo de la respuesta, puedes usar el método clearBody()
:
// Esto aún mantendrá cualquier encabezado configurado en el objeto response().
Flight::response()->clearBody();
Caché HTTP
Flight proporciona soporte integrado para caché a nivel HTTP. Si se cumple la condición de caché, Flight devolverá una respuesta HTTP 304 Not Modified
. La próxima vez que el cliente solicite el mismo recurso, se le indicará que use su versión en caché localmente.
Caché a nivel de ruta
Si quieres cachear toda tu respuesta, puedes usar el método cache()
y pasar el tiempo para cachear.
// Esto cacheará la respuesta por 5 minutos
Flight::route('/news', function () {
Flight::response()->cache(time() + 300);
echo 'This content will be cached.';
});
// Alternativamente, puedes usar una cadena que pasarías
// al método strtotime()
Flight::route('/news', function () {
Flight::response()->cache('+5 minutes');
echo 'This content will be cached.';
});
Última modificación
Puedes usar el método lastModified
y pasar una marca de tiempo UNIX para configurar la fecha y hora en que una página fue modificada por última vez. El cliente continuará usando su caché hasta que el valor de última modificación cambie.
Flight::route('/news', function () {
Flight::lastModified(1234567890);
echo 'This content will be cached.';
});
ETag
El caché ETag
es similar a Last-Modified
, excepto que puedes especificar cualquier id que quieras para el recurso:
Flight::route('/news', function () {
Flight::etag('my-unique-id');
echo 'This content will be cached.';
});
Ten en cuenta que llamar a cualquiera de lastModified
o etag
configurará y verificará ambos el valor de caché. Si el valor de caché es el mismo entre solicitudes, Flight enviará inmediatamente una respuesta HTTP 304
y detendrá el procesamiento.
Descarga de un archivo
v3.12.0
Hay un método auxiliar para transmitir un archivo al usuario final. Puedes usar el método download
y pasar la ruta.
Flight::route('/download', function () {
Flight::download('/path/to/file.txt');
// A partir de v3.17.1 puedes especificar un nombre de archivo personalizado para la descarga
Flight::download('/path/to/file.txt', 'custom_name.txt');
});
Ver también
- Enrutamiento - Cómo mapear rutas a controladores y renderizar vistas.
- Solicitudes - Comprender cómo manejar solicitudes entrantes.
- Middleware - Usar middleware con rutas para autenticación, registro, etc.
- ¿Por qué un framework? - Comprender los beneficios de usar un framework como Flight.
- Extensión - Cómo extender Flight con tu propia funcionalidad.
Solución de problemas
- Si tienes problemas con las redirecciones que no funcionan, asegúrate de agregar un
return;
al método. stop()
yhalt()
no son lo mismo.halt()
detendrá la ejecución inmediatamente, mientras questop()
permitirá que la ejecución continúe.
Registro de cambios
- v3.17.1 - Agregado
$fileName
al métododownloadFile()
. - v3.12.0 - Agregado método auxiliar
downloadFile
. - v3.10.0 - Agregado
jsonHalt
. - v1.0 - Lanzamiento inicial.
Learn/events
Gestor de Eventos
a partir de v3.15.0
Resumen
Los eventos te permiten registrar y activar comportamientos personalizados en tu aplicación. Con la adición de Flight::onEvent()
y Flight::triggerEvent()
, ahora puedes engancharte en momentos clave del ciclo de vida de tu app o definir tus propios eventos (como notificaciones y correos electrónicos) para hacer tu código más modular y extensible. Estos métodos son parte de los métodos mapeables de Flight, lo que significa que puedes sobrescribir su comportamiento para adaptarlo a tus necesidades.
Comprensión
Los eventos te permiten separar diferentes partes de tu aplicación para que no dependan demasiado unas de otras. Esta separación—a menudo llamada desacoplamiento—hace que tu código sea más fácil de actualizar, extender o depurar. En lugar de escribir todo en un gran bloque, puedes dividir tu lógica en piezas más pequeñas e independientes que respondan a acciones específicas (eventos).
Imagina que estás construyendo una app de blog:
- Cuando un usuario publica un comentario, podrías querer:
- Guardar el comentario en la base de datos.
- Enviar un correo electrónico al propietario del blog.
- Registrar la acción por seguridad.
Sin eventos, todo esto se amontonarían en una sola función. Con eventos, puedes dividirlo: una parte guarda el comentario, otra activa un evento como 'comment.posted'
, y oyentes separados manejan el correo electrónico y el registro. Esto mantiene tu código más limpio y te permite agregar o eliminar características (como notificaciones) sin tocar la lógica principal.
Casos de Uso Comunes
En la mayoría de los casos, los eventos son buenos para cosas que son opcionales, pero no una parte absolutamente central de tu sistema. Por ejemplo, lo siguiente es bueno tenerlo, pero si fallan por alguna razón, tu aplicación debería seguir funcionando:
- Registro: Registrar acciones como inicios de sesión o errores sin ensuciar tu código principal.
- Notificaciones: Enviar correos electrónicos o alertas cuando algo sucede.
- Actualizaciones de Caché: Refrescar cachés o notificar a otros sistemas sobre cambios.
Sin embargo, supongamos que tienes una función de contraseña olvidada. Eso debería ser parte de tu funcionalidad principal y no un evento porque si ese correo no se envía, tu usuario no puede restablecer su contraseña y usar tu aplicación.
Uso Básico
El sistema de eventos de Flight se construye alrededor de dos métodos principales: Flight::onEvent()
para registrar oyentes de eventos y Flight::triggerEvent()
para activar eventos. Aquí te explico cómo usarlos:
Registrando Oyentes de Eventos
Para escuchar un evento, usa Flight::onEvent()
. Este método te permite definir qué debería suceder cuando ocurre un evento.
Flight::onEvent(string $event, callable $callback): void
$event
: Un nombre para tu evento (por ejemplo,'user.login'
).$callback
: La función a ejecutar cuando se active el evento.
Te "suscribes" a un evento diciéndole a Flight qué hacer cuando suceda. El callback puede aceptar argumentos pasados desde la activación del evento.
El sistema de eventos de Flight es síncrono, lo que significa que cada oyente de evento se ejecuta en secuencia, uno después del otro. Cuando activas un evento, todos los oyentes registrados para ese evento se ejecutarán hasta completarse antes de que tu código continúe. Esto es importante entenderlo ya que difiere de los sistemas de eventos asíncronos donde los oyentes podrían ejecutarse en paralelo o en un momento posterior.
Ejemplo Simple
Flight::onEvent('user.login', function ($username) {
echo "¡Bienvenido de nuevo, $username!";
// puedes enviar un correo si el inicio de sesión es desde una nueva ubicación
});
Aquí, cuando se active el evento 'user.login'
, saludará al usuario por su nombre y podría incluir lógica para enviar un correo si es necesario.
Nota: El callback puede ser una función, una función anónima o un método de una clase.
Activando Eventos
Para hacer que un evento suceda, usa Flight::triggerEvent()
. Esto le dice a Flight que ejecute todos los oyentes registrados para ese evento, pasando cualquier dato que proporciones.
Flight::triggerEvent(string $event, ...$args): void
$event
: El nombre del evento que estás activando (debe coincidir con un evento registrado)....$args
: Argumentos opcionales para enviar a los oyentes (puede ser cualquier número de argumentos).
Ejemplo Simple
$username = 'alice';
Flight::triggerEvent('user.login', $username);
Esto activa el evento 'user.login'
y envía 'alice'
al oyente que definimos anteriormente, lo que producirá: ¡Bienvenido de nuevo, alice!
.
- Si no hay oyentes registrados, no pasa nada—tu app no se romperá.
- Usa el operador de propagación (
...
) para pasar múltiples argumentos de manera flexible.
Deteniendo Eventos
Si un oyente devuelve false
, no se ejecutarán oyentes adicionales para ese evento. Esto te permite detener la cadena de eventos basada en condiciones específicas. Recuerda, el orden de los oyentes importa, ya que el primero en devolver false
detendrá el resto de la ejecución.
Ejemplo:
Flight::onEvent('user.login', function ($username) {
if (isBanned($username)) {
logoutUser($username);
return false; // Detiene oyentes subsiguientes
}
});
Flight::onEvent('user.login', function ($username) {
sendWelcomeEmail($username); // esto nunca se envía
});
Sobrescribiendo Métodos de Eventos
Flight::onEvent()
y Flight::triggerEvent()
están disponibles para ser extendidos, lo que significa que puedes redefinir cómo funcionan. Esto es genial para usuarios avanzados que quieran personalizar el sistema de eventos, como agregar registro o cambiar cómo se despachan los eventos.
Ejemplo: Personalizando onEvent
Flight::map('onEvent', function (string $event, callable $callback) {
// Registrar cada registro de evento
error_log("Nuevo oyente de evento agregado para: $event");
// Llamar al comportamiento predeterminado (asumiendo un sistema de eventos interno)
Flight::_onEvent($event, $callback);
});
Ahora, cada vez que registres un evento, se registrará antes de proceder.
¿Por Qué Sobrescribir?
- Agregar depuración o monitoreo.
- Restringir eventos en ciertos entornos (por ejemplo, deshabilitar en pruebas).
- Integrar con una biblioteca de eventos diferente.
Dónde Poner Tus Eventos
Si eres nuevo en los conceptos de eventos en tu proyecto, podrías preguntarte: ¿dónde registro todos estos eventos en mi app? La simplicidad de Flight significa que no hay una regla estricta—puedes ponerlos donde tenga sentido para tu proyecto. Sin embargo, mantenerlos organizados te ayuda a mantener tu código a medida que tu app crece. Aquí hay algunas opciones prácticas y mejores prácticas, adaptadas a la naturaleza ligera de Flight:
Opción 1: En Tu Archivo Principal index.php
Para apps pequeñas o prototipos rápidos, puedes registrar eventos directamente en tu archivo index.php
junto con tus rutas. Esto mantiene todo en un solo lugar, lo cual está bien cuando la simplicidad es tu prioridad.
require 'vendor/autoload.php';
// Registrar eventos
Flight::onEvent('user.login', function ($username) {
error_log("$username logged in at " . date('Y-m-d H:i:s'));
});
// Definir rutas
Flight::route('/login', function () {
$username = 'bob';
Flight::triggerEvent('user.login', $username);
echo "Logged in!";
});
Flight::start();
- Pros: Simple, sin archivos extra, genial para proyectos pequeños.
- Cons: Puede volverse desordenado a medida que tu app crece con más eventos y rutas.
Opción 2: Un Archivo Separado events.php
Para una app un poco más grande, considera mover los registros de eventos a un archivo dedicado como app/config/events.php
. Incluye este archivo en tu index.php
antes de tus rutas. Esto imita cómo se organizan a menudo las rutas en app/config/routes.php
en proyectos de Flight.
// app/config/events.php
Flight::onEvent('user.login', function ($username) {
error_log("$username logged in at " . date('Y-m-d H:i:s'));
});
Flight::onEvent('user.registered', function ($email, $name) {
echo "Email sent to $email: Welcome, $name!";
});
// index.php
require 'vendor/autoload.php';
require 'app/config/events.php';
Flight::route('/login', function () {
$username = 'bob';
Flight::triggerEvent('user.login', $username);
echo "Logged in!";
});
Flight::start();
- Pros: Mantiene
index.php
enfocado en el enrutamiento, organiza los eventos lógicamente, fácil de encontrar y editar. - Cons: Agrega un poco de estructura, lo que podría parecer excesivo para apps muy pequeñas.
Opción 3: Cerca de Dónde Se Activan
Otro enfoque es registrar eventos cerca de donde se activan, como dentro de un controlador o definición de ruta. Esto funciona bien si un evento es específico de una parte de tu app.
Flight::route('/signup', function () {
// Registrar evento aquí
Flight::onEvent('user.registered', function ($email) {
echo "Welcome email sent to $email!";
});
$email = 'jane@example.com';
Flight::triggerEvent('user.registered', $email);
echo "Signed up!";
});
- Pros: Mantiene el código relacionado junto, bueno para características aisladas.
- Cons: Dispersa los registros de eventos, haciendo más difícil ver todos los eventos de una vez; riesgo de registros duplicados si no se tiene cuidado.
Mejor Práctica para Flight
- Empieza Simple: Para apps pequeñas, pon los eventos en
index.php
. Es rápido y se alinea con el minimalismo de Flight. - Crece Inteligente: A medida que tu app se expande (por ejemplo, más de 5-10 eventos), usa un archivo
app/config/events.php
. Es un paso natural, como organizar rutas, y mantiene tu código ordenado sin agregar marcos complejos. - Evita la Sobreingeniería: No crees una clase o directorio "gestor de eventos" completo a menos que tu app sea enorme—Flight prospera en la simplicidad, así que manténlo ligero.
Consejo: Agrupa por Propósito
En events.php
, agrupa eventos relacionados (por ejemplo, todos los eventos relacionados con usuarios juntos) con comentarios para mayor claridad:
// app/config/events.php
// Eventos de Usuario
Flight::onEvent('user.login', function ($username) {
error_log("$username logged in");
});
Flight::onEvent('user.registered', function ($email) {
echo "Welcome to $email!";
});
// Eventos de Página
Flight::onEvent('page.updated', function ($pageId) {
Flight::cache()->delete("page_$pageId");
});
Esta estructura escala bien y se mantiene amigable para principiantes.
Ejemplos del Mundo Real
Vamos a recorrer algunos escenarios del mundo real para mostrar cómo funcionan los eventos y por qué son útiles.
Ejemplo 1: Registrando un Inicio de Sesión de Usuario
// Paso 1: Registrar un oyente
Flight::onEvent('user.login', function ($username) {
$time = date('Y-m-d H:i:s');
error_log("$username logged in at $time");
});
// Paso 2: Activarlo en tu app
Flight::route('/login', function () {
$username = 'bob'; // Pretende que esto viene de un formulario
Flight::triggerEvent('user.login', $username);
echo "Hi, $username!";
});
Por Qué Es Útil: El código de inicio de sesión no necesita saber sobre el registro—solo activa el evento. Puedes agregar más oyentes después (por ejemplo, enviar un correo de bienvenida) sin cambiar la ruta.
Ejemplo 2: Notificando Sobre Nuevos Usuarios
// Oyente para nuevos registros
Flight::onEvent('user.registered', function ($email, $name) {
// Simular envío de correo
echo "Email sent to $email: Welcome, $name!";
});
// Activar cuando alguien se registra
Flight::route('/signup', function () {
$email = 'jane@example.com';
$name = 'Jane';
Flight::triggerEvent('user.registered', $email, $name);
echo "Thanks for signing up!";
});
Por Qué Es Útil: La lógica de registro se enfoca en crear el usuario, mientras que el evento maneja las notificaciones. Podrías agregar más oyentes (por ejemplo, registrar el registro) después.
Ejemplo 3: Limpiando un Caché
// Oyente para limpiar un caché
Flight::onEvent('page.updated', function ($pageId) {
// si usas el plugin flightphp/cache
Flight::cache()->delete("page_$pageId");
echo "Cache cleared for page $pageId.";
});
// Activar cuando se edita una página
Flight::route('/edit-page/(@id)', function ($pageId) {
// Pretende que actualizamos la página
Flight::triggerEvent('page.updated', $pageId);
echo "Page $pageId updated.";
});
Por Qué Es Útil: El código de edición no se preocupa por el caché—solo señala la actualización. Otras partes de la app pueden reaccionar según sea necesario.
Mejores Prácticas
- Nombra Eventos Claramente: Usa nombres específicos como
'user.login'
o'page.updated'
para que sea obvio qué hacen. - Mantén Oyentes Simples: No pongas tareas lentas o complejas en oyentes—mantén tu app rápida.
- Prueba Tus Eventos: Actívalos manualmente para asegurar que los oyentes funcionen como se espera.
- Usa Eventos con Sabiduría: Son geniales para desacoplar, pero demasiados pueden hacer que tu código sea difícil de seguir—úsalos cuando tenga sentido.
El sistema de eventos en Flight PHP, con Flight::onEvent()
y Flight::triggerEvent()
, te da una manera simple pero poderosa de construir aplicaciones flexibles. Al permitir que diferentes partes de tu app se comuniquen entre sí a través de eventos, puedes mantener tu código organizado, reutilizable y fácil de expandir. Ya sea que estés registrando acciones, enviando notificaciones o gestionando actualizaciones, los eventos te ayudan a hacerlo sin enredar tu lógica. Además, con la capacidad de sobrescribir estos métodos, tienes la libertad de adaptar el sistema a tus necesidades. Empieza pequeño con un solo evento y observa cómo transforma la estructura de tu app!
Eventos Integrados
Flight PHP viene con algunos eventos integrados que puedes usar para engancharte en el ciclo de vida del framework. Estos eventos se activan en puntos específicos del ciclo de solicitud/respuesta, permitiéndote ejecutar lógica personalizada cuando ocurren ciertas acciones.
Lista de Eventos Integrados
- flight.request.received:
function(Request $request)
Activado cuando se recibe, analiza y procesa una solicitud. - flight.error:
function(Throwable $exception)
Activado cuando ocurre un error durante el ciclo de vida de la solicitud. - flight.redirect:
function(string $url, int $status_code)
Activado cuando se inicia una redirección. - flight.cache.checked:
function(string $cache_key, bool $hit, float $executionTime)
Activado cuando se verifica el caché para una clave específica y si hubo acierto o fallo en el caché. - flight.middleware.before:
function(Route $route)
Activado después de que se ejecute el middleware before. - flight.middleware.after:
function(Route $route)
Activado después de que se ejecute el middleware after. - flight.middleware.executed:
function(Route $route, $middleware, string $method, float $executionTime)
Activado después de que se ejecute cualquier middleware. - flight.route.matched:
function(Route $route)
Activado cuando se coincide una ruta, pero aún no se ejecuta. - flight.route.executed:
function(Route $route, float $executionTime)
Activado después de que se ejecute y procese una ruta.$executionTime
es el tiempo que tomó ejecutar la ruta (llamar al controlador, etc.). - flight.view.rendered:
function(string $template_file_path, float $executionTime)
Activado después de que se renderice una vista.$executionTime
es el tiempo que tomó renderizar la plantilla. Nota: Si sobrescribes el métodorender
, necesitarás reactivar este evento. - flight.response.sent:
function(Response $response, float $executionTime)
Activado después de que se envíe una respuesta al cliente.$executionTime
es el tiempo que tomó construir la respuesta.
Ver También
- Extending Flight - Cómo extender y personalizar la funcionalidad principal de Flight.
- Cache - Ejemplo de usar eventos para limpiar el caché cuando se actualiza una página.
Solución de Problemas
- Si no ves que se llamen tus oyentes de eventos, asegúrate de registrarlos antes de activar los eventos. El orden de registro importa.
Registro de Cambios
- v3.15.0 - Agregados eventos a Flight.
Learn/templates
Vistas y Plantillas HTML
Resumen
Flight proporciona funcionalidad básica de plantillas HTML por defecto. El templado es una forma muy efectiva para que desconectes la lógica de tu aplicación de la capa de presentación.
Comprensión
Cuando estás construyendo una aplicación, probablemente tendrás HTML que querrás entregar de vuelta al usuario final. PHP por sí solo es un lenguaje de plantillas, pero es muy fácil envolver lógica de negocio como llamadas a bases de datos, llamadas a API, etc., en tu archivo HTML y hacer que las pruebas y el desacoplamiento sean un proceso muy difícil. Al empujar datos a una plantilla y dejar que la plantilla se renderice a sí misma, se vuelve mucho más fácil desacoplar y realizar pruebas unitarias en tu código. ¡Nos lo agradecerás si usas plantillas!
Uso Básico
Flight te permite intercambiar el motor de vistas predeterminado simplemente registrando tu propia clase de vista. ¡Desplázate hacia abajo para ver ejemplos de cómo usar Smarty, Latte, Blade y más!
Latte
recomendado
Aquí te explico cómo usar el motor de plantillas Latte para tus vistas.
Instalación
composer require latte/latte
Configuración Básica
La idea principal es que sobrescribas el método render
para usar Latte en lugar del renderizador PHP predeterminado.
// sobrescribe el método render para usar latte en lugar del renderizador PHP predeterminado
Flight::map('render', function(string $template, array $data, ?string $block): void {
$latte = new Latte\Engine;
// Dónde Latte almacena específicamente su caché
$latte->setTempDirectory(__DIR__ . '/../cache/');
$finalPath = Flight::get('flight.views.path') . $template;
$latte->render($finalPath, $data, $block);
});
Usar Latte en Flight
Ahora que puedes renderizar con Latte, puedes hacer algo como esto:
<!-- app/views/home.latte -->
<html>
<head>
<title>{$title ? $title . ' - '}Mi App</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<h1>¡Hola, {$name}!</h1>
</body>
</html>
// routes.php
Flight::route('/@name', function ($name) {
Flight::render('home.latte', [
'title' => 'Página de Inicio',
'name' => $name
]);
});
Cuando visites /Bob
en tu navegador, la salida sería:
<html>
<head>
<title>Página de Inicio - Mi App</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<h1>¡Hola, Bob!</h1>
</body>
</html>
Lectura Adicional
Un ejemplo más complejo de usar Latte con layouts se muestra en la sección de plugins increíbles de esta documentación.
Puedes aprender más sobre las capacidades completas de Latte, incluyendo traducción y capacidades de idioma, leyendo la documentación oficial.
Motor de Vistas Integrado
deprecado
Nota: Aunque esta sigue siendo la funcionalidad predeterminada y técnicamente aún funciona.
Para mostrar una plantilla de vista, llama al método render
con el nombre del archivo de plantilla y datos de plantilla opcionales:
Flight::render('hello.php', ['name' => 'Bob']);
Los datos de plantilla que pasas se inyectan automáticamente en la plantilla y pueden referenciarse como una variable local. Los archivos de plantilla son simplemente archivos PHP. Si el contenido del archivo de plantilla hello.php
es:
¡Hola, <?= $name ?>!
La salida sería:
¡Hola, Bob!
También puedes establecer variables de vista manualmente usando el método set:
Flight::view()->set('name', 'Bob');
La variable name
ahora está disponible en todas tus vistas. Así que puedes simplemente hacer:
Flight::render('hello');
Ten en cuenta que al especificar el nombre de la plantilla en el método render, puedes omitir la extensión .php
.
Por defecto, Flight buscará un directorio views
para los archivos de plantilla. Puedes establecer una ruta alternativa para tus plantillas configurando lo siguiente:
Flight::set('flight.views.path', '/path/to/views');
Layouts
Es común que los sitios web tengan un solo archivo de plantilla de layout con contenido intercambiable. Para renderizar contenido que se use en un layout, puedes pasar un parámetro opcional al método render
.
Flight::render('header', ['heading' => 'Hola'], 'headerContent');
Flight::render('body', ['body' => 'Mundo'], 'bodyContent');
Tu vista entonces tendrá variables guardadas llamadas headerContent
y bodyContent
. Puedes renderizar tu layout haciendo:
Flight::render('layout', ['title' => 'Página de Inicio']);
Si los archivos de plantilla se ven así:
header.php
:
<h1><?= $heading ?></h1>
body.php
:
<div><?= $body ?></div>
layout.php
:
<html>
<head>
<title><?= $title ?></title>
</head>
<body>
<?= $headerContent ?>
<?= $bodyContent ?>
</body>
</html>
La salida sería:
<html>
<head>
<title>Página de Inicio</title>
</head>
<body>
<h1>Hola</h1>
<div>Mundo</div>
</body>
</html>
Smarty
Aquí te explico cómo usar el motor de plantillas Smarty para tus vistas:
// Cargar la biblioteca Smarty
require './Smarty/libs/Smarty.class.php';
// Registrar Smarty como la clase de vista
// También pasar una función de devolución de llamada para configurar Smarty al cargar
Flight::register('view', Smarty::class, [], function (Smarty $smarty) {
$smarty->setTemplateDir('./templates/');
$smarty->setCompileDir('./templates_c/');
$smarty->setConfigDir('./config/');
$smarty->setCacheDir('./cache/');
});
// Asignar datos de plantilla
Flight::view()->assign('name', 'Bob');
// Mostrar la plantilla
Flight::view()->display('hello.tpl');
Para mayor completitud, también deberías sobrescribir el método render predeterminado de Flight:
Flight::map('render', function(string $template, array $data): void {
Flight::view()->assign($data);
Flight::view()->display($template);
});
Blade
Aquí te explico cómo usar el motor de plantillas Blade para tus vistas:
Primero, necesitas instalar la biblioteca BladeOne a través de Composer:
composer require eftec/bladeone
Luego, puedes configurar BladeOne como la clase de vista en Flight:
<?php
// Cargar la biblioteca BladeOne
use eftec\bladeone\BladeOne;
// Registrar BladeOne como la clase de vista
// También pasar una función de devolución de llamada para configurar BladeOne al cargar
Flight::register('view', BladeOne::class, [], function (BladeOne $blade) {
$views = __DIR__ . '/../views';
$cache = __DIR__ . '/../cache';
$blade->setPath($views);
$blade->setCompiledPath($cache);
});
// Asignar datos de plantilla
Flight::view()->share('name', 'Bob');
// Mostrar la plantilla
echo Flight::view()->run('hello', []);
Para mayor completitud, también deberías sobrescribir el método render predeterminado de Flight:
<?php
Flight::map('render', function(string $template, array $data): void {
echo Flight::view()->run($template, $data);
});
En este ejemplo, el archivo de plantilla hello.blade.php podría verse así:
<?php
¡Hola, {{ $name }}!
La salida sería:
¡Hola, Bob!
Ver También
- Extensión - Cómo sobrescribir el método
render
para usar un motor de plantillas diferente. - Enrutamiento - Cómo mapear rutas a controladores y renderizar vistas.
- Respuestas - Cómo personalizar respuestas HTTP.
- ¿Por qué un Framework? - Cómo encajan las plantillas en el panorama general.
Solución de Problemas
- Si tienes una redirección en tu middleware, pero tu app no parece estar redirigiendo, asegúrate de agregar una instrucción
exit;
en tu middleware.
Registro de Cambios
- v2.0 - Lanzamiento inicial.
Learn/collections
Colecciones
Resumen
La clase Collection
en Flight es una utilidad práctica para gestionar conjuntos de datos. Te permite acceder y manipular datos utilizando tanto notación de array como de objeto, haciendo que tu código sea más limpio y flexible.
Entendiendo
Una Collection
es básicamente un envoltorio alrededor de un array, pero con algunos poderes extra. Puedes usarla como un array, iterar sobre ella, contar sus elementos e incluso acceder a los elementos como si fueran propiedades de un objeto. Esto es especialmente útil cuando quieres pasar datos estructurados en tu aplicación, o cuando quieres hacer que tu código sea un poco más legible.
Las colecciones implementan varias interfaces de PHP:
ArrayAccess
(para que puedas usar sintaxis de array)Iterator
(para que puedas iterar conforeach
)Countable
(para que puedas usarcount()
)JsonSerializable
(para que puedas convertir fácilmente a JSON)
Uso Básico
Creando una Colección
Puedes crear una colección simplemente pasando un array a su constructor:
use flight\util\Collection;
$data = [
'name' => 'Flight',
'version' => 3,
'features' => ['routing', 'views', 'extending']
];
$collection = new Collection($data);
Accediendo a Elementos
Puedes acceder a los elementos utilizando notación de array o de objeto:
// Notación de array
echo $collection['name']; // Salida: FlightPHP
// Notación de objeto
echo $collection->version; // Salida: 3
Si intentas acceder a una clave que no existe, obtendrás null
en lugar de un error.
Estableciendo Elementos
Puedes establecer elementos utilizando cualquiera de las dos notaciones:
// Notación de array
$collection['author'] = 'Mike Cao';
// Notación de objeto
$collection->license = 'MIT';
Verificando y Eliminando Elementos
Verifica si un elemento existe:
if (isset($collection['name'])) {
// Haz algo
}
if (isset($collection->version)) {
// Haz algo
}
Elimina un elemento:
unset($collection['author']);
unset($collection->license);
Iterando Sobre una Colección
Las colecciones son iterables, por lo que puedes usarlas en un bucle foreach
:
foreach ($collection as $key => $value) {
echo "$key: $value\n";
}
Contando Elementos
Puedes contar el número de elementos en una colección:
echo count($collection); // Salida: 4
Obteniendo Todas las Claves o Datos
Obtén todas las claves:
$keys = $collection->keys(); // ['name', 'version', 'features', 'license']
Obtén todos los datos como un array:
$data = $collection->getData();
Limpiando la Colección
Elimina todos los elementos:
$collection->clear();
Serialización JSON
Las colecciones se pueden convertir fácilmente a JSON:
echo json_encode($collection);
// Salida: {"name":"FlightPHP","version":3,"features":["routing","views","extending"],"license":"MIT"}
Uso Avanzado
Puedes reemplazar completamente el array de datos interno si es necesario:
$collection->setData(['foo' => 'bar']);
Las colecciones son especialmente útiles cuando quieres pasar datos estructurados entre componentes, o cuando quieres proporcionar una interfaz más orientada a objetos para datos de array.
Ver También
- Requests - Aprende cómo manejar solicitudes HTTP y cómo las colecciones se pueden usar para gestionar datos de solicitud.
- PDO Wrapper - Aprende cómo usar el envoltorio PDO en Flight y cómo las colecciones se pueden usar para gestionar resultados de base de datos.
Solución de Problemas
- Si intentas acceder a una clave que no existe, obtendrás
null
en lugar de un error. - Recuerda que las colecciones no son recursivas: los arrays anidados no se convierten automáticamente a colecciones.
- Si necesitas restablecer la colección, usa
$collection->clear()
o$collection->setData([])
.
Registro de Cambios
- v3.0 - Mejoras en las sugerencias de tipo y soporte para PHP 8+.
- v1.0 - Lanzamiento inicial de la clase Collection.
Learn/flight_vs_fat_free
Flight vs Fat-Free
¿Qué es Fat-Free?
Fat-Free (conocido afectuosamente como F3) es un micro-framework PHP potente pero fácil de usar diseñado para ayudarte a construir aplicaciones web dinámicas y robustas - ¡rápidamente!
Flight se compara con Fat-Free en muchos aspectos y probablemente es el primo más cercano en términos de características y simplicidad. Fat-Free tiene muchas características que Flight no tiene, pero también tiene muchas características que Flight sí tiene. Fat-Free está empezando a mostrar su edad y no es tan popular como lo fue una vez.
Las actualizaciones se están volviendo menos frecuentes y la comunidad no es tan activa como lo fue antes. El código es lo suficientemente simple, pero a veces la falta de disciplina en la sintaxis puede hacerlo difícil de leer y entender. Funciona para PHP 8.3, pero el código en sí todavía parece vivir en PHP 5.3.
Pros comparado con Flight
- Fat-Free tiene algunas estrellas más en GitHub que Flight.
- Fat-Free tiene una documentación decente, pero carece de claridad en algunas áreas.
- Fat-Free tiene algunos recursos escasos como tutoriales de YouTube y artículos en línea que se pueden usar para aprender el framework.
- Fat-Free tiene algunos plugins útiles integrados que a veces son útiles.
- Fat-Free tiene un ORM integrado llamado Mapper que se puede usar para interactuar con tu base de datos. Flight tiene active-record.
- Fat-Free tiene Sessions, Caching y localización integrados. Flight requiere que uses bibliotecas de terceros, pero está cubierto en la documentación.
- Fat-Free tiene un pequeño grupo de plugins creados por la comunidad que se pueden usar para extender el framework. Flight tiene algunos cubiertos en las páginas de documentación y ejemplos.
- Fat-Free, al igual que Flight, no tiene dependencias.
- Fat-Free, al igual que Flight, está orientado a dar al desarrollador control sobre su aplicación y una experiencia de desarrollador simple.
- Fat-Free mantiene la compatibilidad hacia atrás como lo hace Flight (parcialmente porque las actualizaciones se están volviendo menos frecuentes).
- Fat-Free, al igual que Flight, está destinado a desarrolladores que se adentran en el mundo de los frameworks por primera vez.
- Fat-Free tiene un motor de plantillas integrado que es más robusto que el motor de plantillas de Flight. Flight recomienda Latte para lograr esto.
- Fat-Free tiene un comando único de tipo CLI "route" donde puedes construir aplicaciones CLI dentro de Fat-Free mismo y tratarlo mucho como una solicitud
GET
. Flight logra esto con runway.
Cons comparado con Flight
- Fat-Free tiene algunas pruebas de implementación e incluso tiene su propia clase de prueba que es muy básica. Sin embargo, no está 100% probado con pruebas unitarias como lo está Flight.
- Tienes que usar un motor de búsqueda como Google para buscar realmente en el sitio de documentación.
- Flight tiene modo oscuro en su sitio de documentación. (mic drop)
- Fat-Free tiene algunos módulos que están lamentablemente sin mantener.
- Flight tiene un PdoWrapper simple que es un poco más simple que la clase
DB\SQL
integrada de Fat-Free. - Flight tiene un plugin de permisos que se puede usar para asegurar tu aplicación. Fat-Free requiere que uses una biblioteca de terceros.
- Flight tiene un ORM llamado active-record que se siente más como un ORM que el Mapper de Fat-Free.
El beneficio adicional de
active-record
es que puedes definir relaciones entre registros para uniones automáticas donde el Mapper de Fat-Free requiere que crees vistas SQL. - Sorprendentemente, Fat-Free no tiene un espacio de nombres raíz. Flight está con espacios de nombres hasta el final para no colisionar con tu propio código.
la clase
Cache
es la mayor infractora aquí. - Fat-Free no tiene middleware. En su lugar, hay ganchos
beforeroute
yafterroute
que se pueden usar para filtrar solicitudes y respuestas en controladores. - Fat-Free no puede agrupar rutas.
- Fat-Free tiene un manejador de contenedor de inyección de dependencias, pero la documentación es increíblemente escasa sobre cómo usarlo.
- La depuración puede volverse un poco complicada ya que básicamente todo se almacena en lo que se llama el
HIVE
Learn/extending
Extendiendo
Resumen
Flight está diseñado para ser un framework extensible. El framework viene con un conjunto de métodos y componentes predeterminados, pero te permite mapear tus propios métodos, registrar tus propias clases o incluso sobrescribir clases y métodos existentes.
Entendiendo
Hay 2 formas en que puedes extender la funcionalidad de Flight:
- Mapeo de Métodos - Esto se usa para crear métodos personalizados simples que puedes llamar desde cualquier lugar de tu aplicación. Estos se usan típicamente para funciones de utilidad que quieres poder llamar desde cualquier parte de tu código.
- Registro de Clases - Esto se usa para registrar tus propias clases con Flight. Esto se usa típicamente para clases que tienen dependencias o requieren configuración.
También puedes sobrescribir métodos existentes del framework para alterar su comportamiento predeterminado y adaptarlo mejor a las necesidades de tu proyecto.
Si estás buscando un DIC (Contenedor de Inyección de Dependencias), ve a la página de Contenedor de Inyección de Dependencias.
Uso Básico
Sobrescribiendo Métodos del Framework
Flight te permite sobrescribir su funcionalidad predeterminada para adaptarla a tus propias necesidades, sin tener que modificar ningún código. Puedes ver todos los métodos que puedes sobrescribir a continuación.
Por ejemplo, cuando Flight no puede coincidir una URL con una ruta, invoca el método notFound
que envía una respuesta genérica HTTP 404
. Puedes sobrescribir este comportamiento
usando el método map
:
Flight::map('notFound', function() {
// Mostrar página personalizada 404
include 'errors/404.html';
});
Flight también te permite reemplazar componentes principales del framework. Por ejemplo, puedes reemplazar la clase Router predeterminada con tu propia clase personalizada:
// crear tu clase Router personalizada
class MyRouter extends \flight\net\Router {
// sobrescribir métodos aquí
// por ejemplo, un atajo para solicitudes GET para eliminar
// la característica de pasar ruta
public function get($pattern, $callback, $alias = '') {
return parent::get($pattern, $callback, false, $alias);
}
}
// Registrar tu clase personalizada
Flight::register('router', MyRouter::class);
// Cuando Flight carga la instancia de Router, cargará tu clase
$myRouter = Flight::router();
$myRouter->get('/hello', function() {
echo "Hello World!";
}, 'hello_alias');
Sin embargo, los métodos del framework como map
y register
no pueden ser sobrescritos. Obtendrás
un error si intentas hacerlo (nuevamente, ve a continuación para una lista de métodos).
Métodos del Framework Mapeables
A continuación se muestra el conjunto completo de métodos para el framework. Consiste en métodos principales, que son métodos estáticos regulares, y métodos extensibles, que son métodos mapeados que pueden ser filtrados o sobrescritos.
Métodos Principales
Estos métodos son principales para el framework y no pueden ser sobrescritos.
Flight::map(string $name, callable $callback, bool $pass_route = false) // Crea un método personalizado del framework.
Flight::register(string $name, string $class, array $params = [], ?callable $callback = null) // Registra una clase a un método del framework.
Flight::unregister(string $name) // Desregistra una clase de un método del framework.
Flight::before(string $name, callable $callback) // Agrega un filtro antes de un método del framework.
Flight::after(string $name, callable $callback) // Agrega un filtro después de un método del framework.
Flight::path(string $path) // Agrega una ruta para la carga automática de clases.
Flight::get(string $key) // Obtiene una variable establecida por Flight::set().
Flight::set(string $key, mixed $value) // Establece una variable dentro del motor de Flight.
Flight::has(string $key) // Verifica si una variable está establecida.
Flight::clear(array|string $key = []) // Limpia una variable.
Flight::init() // Inicializa el framework a sus configuraciones predeterminadas.
Flight::app() // Obtiene la instancia del objeto de aplicación
Flight::request() // Obtiene la instancia del objeto de solicitud
Flight::response() // Obtiene la instancia del objeto de respuesta
Flight::router() // Obtiene la instancia del objeto de enrutador
Flight::view() // Obtiene la instancia del objeto de vista
Métodos Extensibles
Flight::start() // Inicia el framework.
Flight::stop() // Detiene el framework y envía una respuesta.
Flight::halt(int $code = 200, string $message = '') // Detiene el framework con un código de estado y mensaje opcionales.
Flight::route(string $pattern, callable $callback, bool $pass_route = false, string $alias = '') // Mapea un patrón de URL a un callback.
Flight::post(string $pattern, callable $callback, bool $pass_route = false, string $alias = '') // Mapea un patrón de URL de solicitud POST a un callback.
Flight::put(string $pattern, callable $callback, bool $pass_route = false, string $alias = '') // Mapea un patrón de URL de solicitud PUT a un callback.
Flight::patch(string $pattern, callable $callback, bool $pass_route = false, string $alias = '') // Mapea un patrón de URL de solicitud PATCH a un callback.
Flight::delete(string $pattern, callable $callback, bool $pass_route = false, string $alias = '') // Mapea un patrón de URL de solicitud DELETE a un callback.
Flight::group(string $pattern, callable $callback) // Crea agrupación para URLs, el patrón debe ser una cadena.
Flight::getUrl(string $name, array $params = []) // Genera una URL basada en un alias de ruta.
Flight::redirect(string $url, int $code) // Redirige a otra URL.
Flight::download(string $filePath) // Descarga un archivo.
Flight::render(string $file, array $data, ?string $key = null) // Renderiza un archivo de plantilla.
Flight::error(Throwable $error) // Envía una respuesta HTTP 500.
Flight::notFound() // Envía una respuesta HTTP 404.
Flight::etag(string $id, string $type = 'string') // Realiza caché HTTP ETag.
Flight::lastModified(int $time) // Realiza caché HTTP de última modificación.
Flight::json(mixed $data, int $code = 200, bool $encode = true, string $charset = 'utf8', int $option) // Envía una respuesta JSON.
Flight::jsonp(mixed $data, string $param = 'jsonp', int $code = 200, bool $encode = true, string $charset = 'utf8', int $option) // Envía una respuesta JSONP.
Flight::jsonHalt(mixed $data, int $code = 200, bool $encode = true, string $charset = 'utf8', int $option) // Envía una respuesta JSON y detiene el framework.
Flight::onEvent(string $event, callable $callback) // Registra un oyente de eventos.
Flight::triggerEvent(string $event, ...$args) // Activa un evento.
Cualquier método personalizado agregado con map
y register
también puede ser filtrado. Para ejemplos sobre cómo filtrar estos métodos, ve la guía de Filtrado de Métodos.
Clases del Framework Extensibles
Hay varias clases en las que puedes sobrescribir funcionalidad extendiéndolas y registrando tu propia clase. Estas clases son:
Flight::app() // Clase de aplicación - extiende la clase flight\Engine
Flight::request() // Clase de solicitud - extiende la clase flight\net\Request
Flight::response() // Clase de respuesta - extiende la clase flight\net\Response
Flight::router() // Clase de enrutador - extiende la clase flight\net\Router
Flight::view() // Clase de vista - extiende la clase flight\template\View
Flight::eventDispatcher() // Clase de despachador de eventos - extiende la clase flight\core\Dispatcher
Mapeo de Métodos Personalizados
Para mapear tu propio método personalizado simple, usa la función map
:
// Mapear tu método
Flight::map('hello', function (string $name) {
echo "hello $name!";
});
// Llamar a tu método personalizado
Flight::hello('Bob');
Aunque es posible crear métodos personalizados simples, se recomienda solo crear funciones estándar en PHP. Esto tiene autocompletado en IDE y es más fácil de leer. El equivalente del código anterior sería:
function hello(string $name) {
echo "hello $name!";
}
hello('Bob');
Esto se usa más cuando necesitas pasar variables a tu método para obtener un valor
esperado. Usar el método register()
como a continuación es más para pasar configuración
y luego llamar a tu clase preconfigurada.
Registro de Clases Personalizadas
Para registrar tu propia clase y configurarla, usa la función register
. La ventaja que esto tiene sobre map() es que puedes reutilizar la misma clase cuando llamas a esta función (sería útil con Flight::db()
para compartir la misma instancia).
// Registrar tu clase
Flight::register('user', User::class);
// Obtener una instancia de tu clase
$user = Flight::user();
El método register también te permite pasar parámetros al constructor de tu clase. Entonces, cuando cargues tu clase personalizada, vendrá preinicializada. Puedes definir los parámetros del constructor pasando un array adicional. Aquí hay un ejemplo de carga de una conexión a la base de datos:
// Registrar clase con parámetros del constructor
Flight::register('db', PDO::class, ['mysql:host=localhost;dbname=test', 'user', 'pass']);
// Obtener una instancia de tu clase
// Esto creará un objeto con los parámetros definidos
//
// new PDO('mysql:host=localhost;dbname=test','user','pass');
//
$db = Flight::db();
// y si lo necesitas más adelante en tu código, solo llamas al mismo método nuevamente
class SomeController {
public function __construct() {
$this->db = Flight::db();
}
}
Si pasas un parámetro de callback adicional, se ejecutará inmediatamente después de la construcción de la clase. Esto te permite realizar cualquier procedimiento de configuración para tu nuevo objeto. La función de callback toma un parámetro, una instancia del nuevo objeto.
// El callback recibirá el objeto que fue construido
Flight::register(
'db',
PDO::class,
['mysql:host=localhost;dbname=test', 'user', 'pass'],
function (PDO $db) {
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
}
);
Por defecto, cada vez que cargues tu clase obtendrás una instancia compartida.
Para obtener una nueva instancia de una clase, simplemente pasa false
como parámetro:
// Instancia compartida de la clase
$shared = Flight::db();
// Nueva instancia de la clase
$new = Flight::db(false);
Nota: Ten en cuenta que los métodos mapeados tienen precedencia sobre las clases registradas. Si declaras ambos usando el mismo nombre, solo se invocará el método mapeado.
Ejemplos
Aquí hay algunos ejemplos de cómo puedes extender Flight con funcionalidad que no está incorporada en el núcleo.
Registro de Logs
Flight no tiene un sistema de registro de logs incorporado, sin embargo, es realmente fácil usar una biblioteca de registro con Flight. Aquí hay un ejemplo usando la biblioteca Monolog:
// services.php
// Registrar el logger con Flight
Flight::register('log', Monolog\Logger::class, [ 'name' ], function(Monolog\Logger $log) {
$log->pushHandler(new Monolog\Handler\StreamHandler('path/to/your.log', Monolog\Logger::WARNING));
});
Ahora que está registrado, puedes usarlo en tu aplicación:
// En tu controlador o ruta
Flight::log()->warning('This is a warning message');
Esto registrará un mensaje en el archivo de log que especificaste. ¿Qué pasa si quieres registrar algo cuando ocurre
un error? Puedes usar el método error
:
// En tu controlador o ruta
Flight::map('error', function(Throwable $ex) {
Flight::log()->error($ex->getMessage());
// Mostrar tu página de error personalizada
include 'errors/500.html';
});
También podrías crear un sistema básico de APM (Monitoreo de Rendimiento de Aplicación)
usando los métodos before
y after
:
// En tu archivo services.php
Flight::before('start', function() {
Flight::set('start_time', microtime(true));
});
Flight::after('start', function() {
$end = microtime(true);
$start = Flight::get('start_time');
Flight::log()->info('Request '.Flight::request()->url.' took ' . round($end - $start, 4) . ' seconds');
// También podrías agregar tus encabezados de solicitud o respuesta
// para registrarlos también (ten cuidado ya que esto sería mucho
// datos si tienes muchas solicitudes)
Flight::log()->info('Request Headers: ' . json_encode(Flight::request()->headers));
Flight::log()->info('Response Headers: ' . json_encode(Flight::response()->headers));
});
Caché
Flight no tiene un sistema de caché incorporado, sin embargo, es realmente fácil usar una biblioteca de caché con Flight. Aquí hay un ejemplo usando la biblioteca PHP File Cache:
// services.php
// Registrar el caché con Flight
Flight::register('cache', \flight\Cache::class, [ __DIR__ . '/../cache/' ], function(\flight\Cache $cache) {
$cache->setDevMode(ENVIRONMENT === 'development');
});
Ahora que está registrado, puedes usarlo en tu aplicación:
// En tu controlador o ruta
$data = Flight::cache()->get('my_cache_key');
if (empty($data)) {
// Realizar algún procesamiento para obtener los datos
$data = [ 'some' => 'data' ];
Flight::cache()->set('my_cache_key', $data, 3600); // caché por 1 hora
}
Instanciación Fácil de Objetos DIC
Si estás usando un DIC (Contenedor de Inyección de Dependencias) en tu aplicación, puedes usar Flight para ayudarte a instanciar tus objetos. Aquí hay un ejemplo usando la biblioteca Dice:
// services.php
// crear un nuevo contenedor
$container = new \Dice\Dice;
// no olvides reasignarlo a sí mismo como a continuación!
$container = $container->addRule('PDO', [
// shared significa que el mismo objeto se retornará cada vez
'shared' => true,
'constructParams' => ['mysql:host=localhost;dbname=test', 'user', 'pass' ]
]);
// ahora podemos crear un método mapeable para crear cualquier objeto.
Flight::map('make', function($class, $params = []) use ($container) {
return $container->create($class, $params);
});
// Esto registra el manejador del contenedor para que Flight sepa usarlo para controladores/middleware
Flight::registerContainerHandler(function($class, $params) {
Flight::make($class, $params);
});
// supongamos que tenemos la siguiente clase de ejemplo que toma un objeto PDO en el constructor
class EmailCron {
protected PDO $pdo;
public function __construct(PDO $pdo) {
$this->pdo = $pdo;
}
public function send() {
// código que envía un email
}
}
// Y finalmente puedes crear objetos usando inyección de dependencias
$emailCron = Flight::make(EmailCron::class);
$emailCron->send();
¿Genial, verdad?
Ver También
- Contenedor de Inyección de Dependencias - Cómo usar un DIC con Flight.
- Caché de Archivos - Ejemplo de uso de una biblioteca de caché con Flight.
Solución de Problemas
- Recuerda que los métodos mapeados tienen precedencia sobre las clases registradas. Si declaras ambos usando el mismo nombre, solo se invocará el método mapeado.
Registro de Cambios
- v2.0 - Lanzamiento Inicial.
Learn/json
Envoltorio JSON
Resumen
La clase Json
en Flight proporciona una manera simple y consistente de codificar y decodificar datos JSON en su aplicación. Envuelve las funciones JSON nativas de PHP con un mejor manejo de errores y algunos valores predeterminados útiles, haciendo que sea más fácil y seguro trabajar con JSON.
Entendiendo
Trabajar con JSON es extremadamente común en las aplicaciones PHP modernas, especialmente al construir APIs o manejar solicitudes AJAX. La clase Json
centraliza toda la codificación y decodificación de JSON, por lo que no tiene que preocuparse por casos extremos extraños o errores crípticos de las funciones integradas de PHP.
Características clave:
- Manejo consistente de errores (lanza excepciones en caso de fallo)
- Opciones predeterminadas para codificación/decodificación (como barras invertidas no escapadas)
- Métodos de utilidad para impresión legible y validación
Uso Básico
Codificando Datos a JSON
Para convertir datos PHP a una cadena JSON, use Json::encode()
:
use flight\util\Json;
$data = [
'framework' => 'Flight',
'version' => 3,
'features' => ['routing', 'views', 'extending']
];
$json = Json::encode($data);
echo $json;
// Salida: {"framework":"Flight","version":3,"features":["routing","views","extending"]}
Si la codificación falla, obtendrá una excepción con un mensaje de error útil.
Impresión Legible
¿Quiere que su JSON sea legible para humanos? Use prettyPrint()
:
echo Json::prettyPrint($data);
/*
{
"framework": "Flight",
"version": 3,
"features": [
"routing",
"views",
"extending"
]
}
*/
Decodificando Cadenas JSON
Para convertir una cadena JSON de vuelta a datos PHP, use Json::decode()
:
$json = '{"framework":"Flight","version":3}';
$data = Json::decode($json);
echo $data->framework; // Salida: Flight
Si desea un array asociativo en lugar de un objeto, pase true
como el segundo argumento:
$data = Json::decode($json, true);
echo $data['framework']; // Salida: Flight
Si la decodificación falla, obtendrá una excepción con un mensaje de error claro.
Validando JSON
Verifique si una cadena es JSON válido:
if (Json::isValid($json)) {
// ¡Es válido!
} else {
// No es JSON válido
}
Obteniendo el Último Error
Si desea verificar el último mensaje de error JSON (de las funciones nativas de PHP):
$error = Json::getLastError();
if ($error !== '') {
echo "Último error JSON: $error";
}
Uso Avanzado
Puede personalizar las opciones de codificación y decodificación si necesita más control (vea opciones de json_encode de PHP):
// Codificar con la opción JSON_HEX_TAG
$json = Json::encode($data, JSON_HEX_TAG);
// Decodificar con profundidad personalizada
$data = Json::decode($json, false, 1024);
Véase También
- Collections - Para trabajar con datos estructurados que se pueden convertir fácilmente a JSON.
- Configuration - Cómo configurar su aplicación Flight.
- Extending - Cómo agregar sus propias utilidades o sobrescribir clases principales.
Solución de Problemas
- Si la codificación o decodificación falla, se lanza una excepción—envuelva sus llamadas en try/catch si desea manejar los errores de manera elegante.
- Si obtiene resultados inesperados, verifique sus datos en busca de referencias circulares o caracteres no UTF-8.
- Use
Json::isValid()
para verificar si una cadena es JSON válido antes de decodificar.
Registro de Cambios
- v3.16.0 - Agregada la clase de utilidad de envoltorio JSON.
Learn/flight_vs_slim
Flight vs Slim
¿Qué es Slim?
Slim es un micro framework de PHP que te ayuda a escribir rápidamente aplicaciones web y APIs simples pero potentes.
Mucho de la inspiración para algunas de las características de la versión 3 de Flight en realidad provino de Slim. Agrupar rutas y ejecutar middleware en un orden específico son dos características que fueron inspiradas por Slim. Slim v3 salió orientado a la simplicidad, pero ha habido reseñas mixtas respecto a v4.
Pros en comparación con Flight
- Slim tiene una comunidad más grande de desarrolladores, quienes a su vez crean módulos útiles para ayudarte a no reinventar la rueda.
- Slim sigue muchas interfaces y estándares que son comunes en la comunidad de PHP, lo que aumenta la interoperabilidad.
- Slim tiene documentación decente y tutoriales que se pueden usar para aprender el framework (nada comparable a Laravel o Symfony, sin embargo).
- Slim tiene varios recursos como tutoriales de YouTube y artículos en línea que se pueden usar para aprender el framework.
- Slim te permite usar los componentes que quieras para manejar las características principales de enrutamiento, ya que es compatible con PSR-7.
Cons en comparación con Flight
- Sorprendentemente, Slim no es tan rápido como podrías pensar para un micro-framework. Consulta los benchmarks de TechEmpower para obtener más información.
- Flight está orientado a un desarrollador que busca construir una aplicación web ligera, rápida y fácil de usar.
- Flight no tiene dependencias, mientras que Slim tiene algunas dependencias que debes instalar.
- Flight está orientado a la simplicidad y facilidad de uso.
- Una de las características principales de Flight es que hace lo posible por mantener la compatibilidad hacia atrás. El cambio de Slim v3 a v4 fue un cambio rompiente.
- Flight está destinado a desarrolladores que se adentran por primera vez en el mundo de los frameworks.
- Flight también puede manejar aplicaciones a nivel empresarial, pero no tiene tantos ejemplos y tutoriales como Slim. También requerirá más disciplina por parte del desarrollador para mantener las cosas organizadas y bien estructuradas.
- Flight da al desarrollador más control sobre la aplicación, mientras que Slim puede introducir algo de magia detrás de escena.
- Flight tiene un PdoWrapper simple que se puede usar para interactuar con tu base de datos. Slim requiere que uses una biblioteca de terceros.
- Flight tiene un plugin de permisos que se puede usar para asegurar tu aplicación. Slim requiere que uses una biblioteca de terceros.
- Flight tiene un ORM llamado active-record que se puede usar para interactuar con tu base de datos. Slim requiere que uses una biblioteca de terceros.
- Flight tiene una aplicación CLI llamada runway que se puede usar para ejecutar tu aplicación desde la línea de comandos. Slim no la tiene.
Learn/autoloading
Autocarga
Resumen
La autocarga es un concepto en PHP donde especificas un directorio o directorios para cargar clases. Esto es mucho más beneficioso que usar require
o include
para cargar clases. También es un requisito para usar paquetes de Composer.
Comprensión
Por defecto, cualquier clase de Flight
se autocarga automáticamente para ti gracias a Composer. Sin embargo, si quieres autocargar tus propias clases, puedes usar el método Flight::path()
para especificar un directorio desde el cual cargar clases.
Usar un autocargador puede ayudar a simplificar tu código de manera significativa. En lugar de tener archivos que comiencen con una multitud de declaraciones include
o require
al principio para capturar todas las clases que se usan en ese archivo, puedes en su lugar llamar dinámicamente a tus clases y se incluirán automáticamente.
Uso Básico
Supongamos que tenemos un árbol de directorios como el siguiente:
# Ruta de ejemplo
/home/user/project/my-flight-project/
├── app
│ ├── cache
│ ├── config
│ ├── controllers - contiene los controladores para este proyecto
│ ├── translations
│ ├── UTILS - contiene clases solo para esta aplicación (esto está en mayúsculas a propósito para un ejemplo posterior)
│ └── views
└── public
└── css
└── js
└── index.php
Puede que hayas notado que esta es la misma estructura de archivos que el sitio de documentación.
Puedes especificar cada directorio desde el cual cargar de esta manera:
/**
* public/index.php
*/
// Agregar una ruta al autocargador
Flight::path(__DIR__.'/../app/controllers/');
Flight::path(__DIR__.'/../app/utils/');
/**
* app/controllers/MyController.php
*/
// no se requiere nombres de espacio
// Se recomienda que todas las clases autocargadas estén en Pascal Case (cada palabra capitalizada, sin espacios)
class MyController {
public function index() {
// hacer algo
}
}
Espacios de Nombres
Si tienes espacios de nombres, en realidad se vuelve muy fácil implementarlo. Debes usar el método Flight::path()
para especificar el directorio raíz (no la raíz del documento o la carpeta public/
) de tu aplicación.
/**
* public/index.php
*/
// Agregar una ruta al autocargador
Flight::path(__DIR__.'/../');
Ahora es así como podría verse tu controlador. Mira el ejemplo a continuación, pero presta atención a los comentarios para información importante.
/**
* app/controllers/MyController.php
*/
// los espacios de nombres son requeridos
// los espacios de nombres son los mismos que la estructura de directorios
// los espacios de nombres deben seguir el mismo caso que la estructura de directorios
// los espacios de nombres y directorios no pueden tener guiones bajos (a menos que Loader::setV2ClassLoading(false) esté configurado)
namespace app\controllers;
// Se recomienda que todas las clases autocargadas estén en Pascal Case (cada palabra capitalizada, sin espacios)
// A partir de 3.7.2, puedes usar Pascal_Snake_Case para los nombres de tus clases ejecutando Loader::setV2ClassLoading(false);
class MyController {
public function index() {
// hacer algo
}
}
Y si quisieras autocargar una clase en tu directorio utils, harías básicamente lo mismo:
/**
* app/UTILS/ArrayHelperUtil.php
*/
// el espacio de nombres debe coincidir con la estructura de directorios y el caso (nota que el directorio UTILS está en mayúsculas
// como en el árbol de archivos arriba)
namespace app\UTILS;
class ArrayHelperUtil {
public function changeArrayCase(array $array) {
// hacer algo
}
}
Guiones Bajos en Nombres de Clases
A partir de 3.7.2, puedes usar Pascal_Snake_Case para los nombres de tus clases ejecutando Loader::setV2ClassLoading(false);
.
Esto te permitirá usar guiones bajos en los nombres de tus clases.
No se recomienda, pero está disponible para aquellos que lo necesiten.
use flight\core\Loader;
/**
* public/index.php
*/
// Agregar una ruta al autocargador
Flight::path(__DIR__.'/../app/controllers/');
Flight::path(__DIR__.'/../app/utils/');
Loader::setV2ClassLoading(false);
/**
* app/controllers/My_Controller.php
*/
// no se requiere nombres de espacio
class My_Controller {
public function index() {
// hacer algo
}
}
Ver También
- Enrutamiento - Cómo mapear rutas a controladores y renderizar vistas.
- ¿Por qué un Framework? - Entendiendo los beneficios de usar un framework como Flight.
Solución de Problemas
- Si no puedes averiguar por qué no se encuentran tus clases con espacios de nombres, recuerda usar
Flight::path()
al directorio raíz en tu proyecto, no a tu directorioapp/
osrc/
o equivalente.
Clase No Encontrada (autocarga no funciona)
Podría haber un par de razones para que esto no suceda. A continuación hay algunos ejemplos, pero asegúrate de revisar también la sección de autocarga.
Nombre de Archivo Incorrecto
El más común es que el nombre de la clase no coincida con el nombre del archivo.
Si tienes una clase llamada MyClass
, entonces el archivo debería llamarse MyClass.php
. Si tienes una clase llamada MyClass
y el archivo se llama myclass.php
entonces el autocargador no podrá encontrarla.
Espacio de Nombres Incorrecto
Si estás usando espacios de nombres, entonces el espacio de nombres debería coincidir con la estructura de directorios.
// ...código...
// si tu MyController está en el directorio app/controllers y está con espacio de nombres
// esto no funcionará.
Flight::route('/hello', 'MyController->hello');
// necesitarás elegir una de estas opciones
Flight::route('/hello', 'app\controllers\MyController->hello');
// o si tienes una declaración use arriba
use app\controllers\MyController;
Flight::route('/hello', [ MyController::class, 'hello' ]);
// también puede escribirse
Flight::route('/hello', MyController::class.'->hello');
// también...
Flight::route('/hello', [ 'app\controllers\MyController', 'hello' ]);
path()
no definido
En la aplicación esqueleto, esto se define dentro del archivo config.php
, pero para que se encuentren tus clases, necesitas asegurarte de que el método path()
esté definido (probablemente a la raíz de tu directorio) antes de intentar usarlo.
// Agregar una ruta al autocargador
Flight::path(__DIR__.'/../');
Registro de Cambios
- v3.7.2 - Puedes usar Pascal_Snake_Case para los nombres de tus clases ejecutando
Loader::setV2ClassLoading(false);
- v2.0 - Funcionalidad de autocarga agregada.
Learn/uploaded_file
Manejador de Archivos Subidos
Resumen
La clase UploadedFile
en Flight facilita y asegura el manejo de subidas de archivos en su aplicación. Envuelve los detalles del proceso de subida de archivos de PHP, proporcionando una forma simple y orientada a objetos para acceder a la información de los archivos y mover los archivos subidos.
Comprensión
Cuando un usuario sube un archivo a través de un formulario, PHP almacena la información sobre el archivo en el superglobal $_FILES
. En Flight, rara vez interactúa directamente con $_FILES
. En su lugar, el objeto Request
de Flight (accesible a través de Flight::request()
) proporciona un método getUploadedFiles()
que devuelve un array de objetos UploadedFile
, haciendo que el manejo de archivos sea mucho más conveniente y robusto.
La clase UploadedFile
proporciona métodos para:
- Obtener el nombre original del archivo, el tipo MIME, el tamaño y la ubicación temporal
- Verificar errores de subida
- Mover el archivo subido a una ubicación permanente
Esta clase le ayuda a evitar errores comunes con las subidas de archivos, como el manejo de errores o la movimiento de archivos de manera segura.
Uso Básico
Acceso a Archivos Subidos desde una Solicitud
La forma recomendada de acceder a los archivos subidos es a través del objeto de solicitud:
Flight::route('POST /upload', function() {
// Para un campo de formulario llamado <input type="file" name="myFile">
$uploadedFiles = Flight::request()->getUploadedFiles();
$file = $uploadedFiles['myFile'];
// Ahora puede usar los métodos de UploadedFile
if ($file->getError() === UPLOAD_ERR_OK) {
$file->moveTo('/path/to/uploads/' . $file->getClientFilename());
echo "File uploaded successfully!";
} else {
echo "Upload failed: " . $file->getError();
}
});
Manejo de Subidas Múltiples de Archivos
Si su formulario usa name="myFiles[]"
para subidas múltiples, obtendrá un array de objetos UploadedFile
:
Flight::route('POST /upload', function() {
// Para un campo de formulario llamado <input type="file" name="myFiles[]">
$uploadedFiles = Flight::request()->getUploadedFiles();
foreach ($uploadedFiles['myFiles'] as $file) {
if ($file->getError() === UPLOAD_ERR_OK) {
$file->moveTo('/path/to/uploads/' . $file->getClientFilename());
echo "Uploaded: " . $file->getClientFilename() . "<br>";
} else {
echo "Failed to upload: " . $file->getClientFilename() . "<br>";
}
}
});
Creación Manual de una Instancia de UploadedFile
Normalmente, no creará un UploadedFile
manualmente, pero puede hacerlo si es necesario:
use flight\net\UploadedFile;
$file = new UploadedFile(
$_FILES['myfile']['name'],
$_FILES['myfile']['type'],
$_FILES['myfile']['size'],
$_FILES['myfile']['tmp_name'],
$_FILES['myfile']['error']
);
Acceso a la Información del Archivo
Puede obtener fácilmente detalles sobre el archivo subido:
echo $file->getClientFilename(); // Nombre original del archivo desde la computadora del usuario
echo $file->getClientMediaType(); // Tipo MIME (por ejemplo, image/png)
echo $file->getSize(); // Tamaño del archivo en bytes
echo $file->getTempName(); // Ruta temporal del archivo en el servidor
echo $file->getError(); // Código de error de subida (0 significa sin error)
Mover el Archivo Subido
Después de validar el archivo, muévelo a una ubicación permanente:
try {
$file->moveTo('/path/to/uploads/' . $file->getClientFilename());
echo "File uploaded successfully!";
} catch (Exception $e) {
echo "Upload failed: " . $e->getMessage();
}
El método moveTo()
lanzará una excepción si algo sale mal (como un error de subida o un problema de permisos).
Manejo de Errores de Subida
Si hubo un problema durante la subida, puede obtener un mensaje de error legible por humanos:
if ($file->getError() !== UPLOAD_ERR_OK) {
// Puede usar el código de error o capturar la excepción de moveTo()
echo "There was an error uploading the file.";
}
Ver También
- Requests - Aprenda cómo acceder a archivos subidos desde solicitudes HTTP y vea más ejemplos de subida de archivos.
- Configuration - Cómo configurar límites de subida y directorios en PHP.
- Extending - Cómo personalizar o extender las clases principales de Flight.
Solución de Problemas
- Siempre verifique
$file->getError()
antes de mover el archivo. - Asegúrese de que su directorio de subida sea escribible por el servidor web.
- Si
moveTo()
falla, verifique el mensaje de excepción para obtener detalles. - Las configuraciones de PHP
upload_max_filesize
ypost_max_size
pueden limitar las subidas de archivos. - Para subidas múltiples de archivos, siempre itere a través del array de objetos
UploadedFile
.
Registro de Cambios
- v3.12.0 - Se agregó la clase
UploadedFile
al objeto de solicitud para un manejo de archivos más fácil.
Guides/unit_testing
Pruebas Unitarias en Flight PHP con PHPUnit
Esta guía introduce las pruebas unitarias en Flight PHP utilizando PHPUnit, dirigida a principiantes que desean entender por qué las pruebas unitarias son importantes y cómo aplicarlas de manera práctica. Nos enfocaremos en probar comportamientos—asegurando que tu aplicación haga lo que esperas, como enviar un correo electrónico o guardar un registro—en lugar de cálculos triviales. Comenzaremos con un simple manejador de rutas y avanzaremos a un controlador más complejo, incorporando inyección de dependencias (DI) y simulando servicios de terceros.
¿Por qué realizar pruebas unitarias?
Las pruebas unitarias aseguran que tu código se comporte como se espera, detectando errores antes de que lleguen a producción. Es especialmente valioso en Flight, donde el enrutamiento ligero y la flexibilidad pueden llevar a interacciones complejas. Para desarrolladores individuales o equipos, las pruebas unitarias actúan como una red de seguridad, documentando el comportamiento esperado y previniendo regresiones cuando revisitas el código más tarde. También mejoran el diseño: el código difícil de probar a menudo indica clases excesivamente complejas o fuertemente acopladas.
A diferencia de ejemplos simplistas (por ejemplo, probar x * y = z
), nos enfocaremos en comportamientos del mundo real, como validar entradas, guardar datos o activar acciones como correos electrónicos. Nuestro objetivo es hacer que las pruebas sean accesibles y significativas.
Principios Guía Generales
- Probar Comportamiento, No Implementación: Enfócate en resultados (por ejemplo, “correo enviado” o “registro guardado”) en lugar de detalles internos. Esto hace que las pruebas sean robustas frente a refactorizaciones.
- Deja de usar
Flight::
: Los métodos estáticos de Flight son terriblemente convenientes, pero hacen que las pruebas sean difíciles. Debes acostumbrarte a usar la variable$app
de$app = Flight::app();
.$app
tiene todos los mismos métodos queFlight::
. Todavía podrás usar$app->route()
o$this->app->json()
en tu controlador, etc. También debes usar el enrutador real de Flight con$router = $app->router()
y luego podrás usar$router->get()
,$router->post()
,$router->group()
, etc. Ver Enrutamiento. - Mantén las Pruebas Rápidas: Las pruebas rápidas fomentan ejecuciones frecuentes. Evita operaciones lentas como llamadas a bases de datos en pruebas unitarias. Si tienes una prueba lenta, es una señal de que estás escribiendo una prueba de integración, no una prueba unitaria. Las pruebas de integración son cuando realmente involucras bases de datos reales, llamadas HTTP reales, envío de correos reales, etc. Tienen su lugar, pero son lentas y pueden ser inestables, lo que significa que a veces fallan por una razón desconocida.
- Usa Nombres Descriptivos: Los nombres de las pruebas deben describir claramente el comportamiento que se está probando. Esto mejora la legibilidad y el mantenimiento.
- Evita Globales Como la Peste: Minimiza el uso de
$app->set()
y$app->get()
, ya que actúan como estado global, requiriendo simulaciones en cada prueba. Prefiere DI o un contenedor DI (ver Contenedor de Inyección de Dependencias). Incluso usar el método$app->map()
es técnicamente un "global" y debe evitarse en favor de DI. Usa una biblioteca de sesiones como flightphp/session para que puedas simular el objeto de sesión en tus pruebas. No llames a$_SESSION
directamente en tu código, ya que eso inyecta una variable global en tu código, haciendo que sea difícil de probar. - Usa Inyección de Dependencias: Inyecta dependencias (por ejemplo,
PDO
, remitentes de correo) en los controladores para aislar la lógica y simplificar la simulación. Si tienes una clase con demasiadas dependencias, considera refactorizarla en clases más pequeñas que cada una tenga una sola responsabilidad siguiendo los principios SOLID. - Simula Servicios de Terceros: Simula bases de datos, clientes HTTP (cURL) o servicios de correo para evitar llamadas externas. Prueba una o dos capas de profundidad, pero deja que tu lógica principal se ejecute. Por ejemplo, si tu aplicación envía un mensaje de texto, NO quieres enviar realmente un mensaje de texto cada vez que ejecutes tus pruebas porque esos cargos se acumularán (y será más lento). En su lugar, simula el servicio de mensaje de texto y solo verifica que tu código llamó al servicio de mensaje de texto con los parámetros correctos.
- Apunta a una Alta Cobertura, No a la Perfección: 100% de cobertura de líneas es bueno, pero no significa realmente que todo en tu código se pruebe de la manera que debería (adelante, investiga cobertura de rama/camino en PHPUnit). Prioriza comportamientos críticos (por ejemplo, registro de usuarios, respuestas de API y capturar respuestas fallidas).
- Usa Controladores para Rutas: En tus definiciones de rutas, usa controladores no closures. La instancia
flight\Engine $app
se inyecta en cada controlador a través del constructor por defecto. En las pruebas, usa$app = new Flight\Engine()
para instanciar Flight dentro de una prueba, inyectarla en tu controlador y llamar métodos directamente (por ejemplo,$controller->register()
). Ver Extensión de Flight y Enrutamiento. - Elige un estilo de simulación y mantente con él: PHPUnit soporta varios estilos de simulación (por ejemplo, prophecy, simulaciones integradas), o puedes usar clases anónimas que tienen sus propios beneficios como completado de código, romper si cambias la definición del método, etc. Solo sé consistente en todas tus pruebas. Ver Objetos Mock de PHPUnit.
- Usa visibilidad
protected
para métodos/propiedades que quieras probar en subclases: Esto te permite sobrescribirlos en subclases de prueba sin hacerlos públicos, esto es especialmente útil para simulaciones de clases anónimas.
Configurando PHPUnit
Primero, configura PHPUnit en tu proyecto Flight PHP usando Composer para pruebas fáciles. Ver la guía de inicio de PHPUnit para más detalles.
-
En el directorio de tu proyecto, ejecuta:
composer require --dev phpunit/phpunit
Esto instala la última versión de PHPUnit como una dependencia de desarrollo.
-
Crea un directorio
tests
en la raíz de tu proyecto para archivos de prueba. -
Agrega un script de prueba a
composer.json
para mayor comodidad:// otro contenido de composer.json "scripts": { "test": "phpunit --configuration phpunit.xml" }
-
Crea un archivo
phpunit.xml
en la raíz:<?xml version="1.0" encoding="UTF-8"?> <phpunit bootstrap="vendor/autoload.php"> <testsuites> <testsuite name="Flight Tests"> <directory>tests</directory> </testsuite> </testsuites> </phpunit>
Ahora, cuando tus pruebas estén construidas, puedes ejecutar composer test
para ejecutar las pruebas.
Probando un Manejador de Ruta Simple
Comencemos con una ruta básica ruta que valida la entrada de correo electrónico de un usuario. Probaremos su comportamiento: devolviendo un mensaje de éxito para correos válidos y un error para los inválidos. Para la validación de correo, usamos filter_var
.
// index.php
$app->route('POST /register', [ UserController::class, 'register' ]);
// UserController.php
class UserController {
protected $app;
public function __construct(flight\Engine $app) {
$this->app = $app;
}
public function register() {
$email = $this->app->request()->data->email;
$responseArray = [];
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
$responseArray = ['status' => 'error', 'message' => 'Invalid email'];
} else {
$responseArray = ['status' => 'success', 'message' => 'Valid email'];
}
$this->app->json($responseArray);
}
}
Para probar esto, crea un archivo de prueba. Ver Pruebas Unitarias y Principios SOLID para más sobre estructuración de pruebas:
// tests/UserControllerTest.php
use PHPUnit\Framework\TestCase;
use Flight;
use flight\Engine;
class UserControllerTest extends TestCase {
public function testValidEmailReturnsSuccess() {
$app = new Engine();
$request = $app->request();
$request->data->email = 'test@example.com'; // Simulate POST data
$UserController = new UserController($app);
$UserController->register($request->data->email);
$response = $app->response()->getBody();
$output = json_decode($response, true);
$this->assertEquals('success', $output['status']);
$this->assertEquals('Valid email', $output['message']);
}
public function testInvalidEmailReturnsError() {
$app = new Engine();
$request = $app->request();
$request->data->email = 'invalid-email'; // Simulate POST data
$UserController = new UserController($app);
$UserController->register($request->data->email);
$response = $app->response()->getBody();
$output = json_decode($response, true);
$this->assertEquals('error', $output['status']);
$this->assertEquals('Invalid email', $output['message']);
}
}
Puntos Clave:
- Simulamos datos POST usando la clase request. No uses globales como
$_POST
,$_GET
, etc., ya que hace que las pruebas sean más complicadas (tienes que resetear siempre esos valores o otras pruebas podrían fallar). - Todos los controladores por defecto tendrán la instancia
flight\Engine
inyectada en ellos incluso sin configurar un contenedor DIC. Esto hace que sea mucho más fácil probar controladores directamente. - No hay uso de
Flight::
en absoluto, haciendo que el código sea más fácil de probar. - Las pruebas verifican comportamiento: estado y mensaje correctos para correos válidos/inválidos.
Ejecuta composer test
para verificar que la ruta se comporte como se espera. Para más sobre requests y responses en Flight, ver la documentación relevante.
Usando Inyección de Dependencias para Controladores Probables
Para escenarios más complejos, usa inyección de dependencias (DI) para hacer controladores probables. Evita los globales de Flight (por ejemplo, Flight::set()
, Flight::map()
, Flight::register()
) ya que actúan como estado global, requiriendo simulaciones para cada prueba. En su lugar, usa el contenedor DI de Flight, DICE, PHP-DI o DI manual.
Usemos flight\database\PdoWrapper
en lugar de PDO crudo. ¡Este wrapper es mucho más fácil de simular y probar unitariamente!
Aquí hay un controlador que guarda un usuario en una base de datos y envía un correo de bienvenida:
use flight\database\PdoWrapper;
class UserController {
protected $app;
protected $db;
protected $mailer;
public function __construct(Engine $app, PdoWrapper $db, MailerInterface $mailer) {
$this->app = $app;
$this->db = $db;
$this->mailer = $mailer;
}
public function register() {
$email = $this->app->request()->data->email;
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
// adding the return here helps unit testing to stop execution
return $this->app->jsonHalt(['status' => 'error', 'message' => 'Invalid email']);
}
$this->db->runQuery('INSERT INTO users (email) VALUES (?)', [$email]);
$this->mailer->sendWelcome($email);
return $this->app->json(['status' => 'success', 'message' => 'User registered']);
}
}
Puntos Clave:
- El controlador depende de una instancia
PdoWrapper
y unaMailerInterface
(un servicio de correo de terceros ficticio). - Las dependencias se inyectan a través del constructor, evitando globales.
Probando el Controlador con Simulaciones
Ahora, probemos el comportamiento de UserController
: validando correos, guardando en la base de datos y enviando correos. Simularemos la base de datos y el remitente para aislar el controlador.
// tests/UserControllerDICTest.php
use PHPUnit\Framework\TestCase;
class UserControllerDICTest extends TestCase {
public function testValidEmailSavesAndSendsEmail() {
// Sometimes mixing mocking styles is necessary
// Here we use PHPUnit's built-in mock for PDOStatement
$statementMock = $this->createMock(PDOStatement::class);
$statementMock->method('execute')->willReturn(true);
// Using an anonymous class to mock PdoWrapper
$mockDb = new class($statementMock) extends PdoWrapper {
protected $statementMock;
public function __construct($statementMock) {
$this->statementMock = $statementMock;
}
// When we mock it this way, we are not really making a database call.
// We can further setup this to alter the PDOStatement mock to simulate failures, etc.
public function runQuery(string $sql, array $params = []): PDOStatement {
return $this->statementMock;
}
};
$mockMailer = new class implements MailerInterface {
public $sentEmail = null;
public function sendWelcome($email): bool {
$this->sentEmail = $email;
return true;
}
};
$app = new Engine();
$app->request()->data->email = 'test@example.com';
$controller = new UserControllerDIC($app, $mockDb, $mockMailer);
$controller->register();
$response = $app->response()->getBody();
$result = json_decode($response, true);
$this->assertEquals('success', $result['status']);
$this->assertEquals('User registered', $result['message']);
$this->assertEquals('test@example.com', $mockMailer->sentEmail);
}
public function testInvalidEmailSkipsSaveAndEmail() {
$mockDb = new class() extends PdoWrapper {
// An empty constructor bypasses the parent constructor
public function __construct() {}
public function runQuery(string $sql, array $params = []): PDOStatement {
throw new Exception('Should not be called');
}
};
$mockMailer = new class implements MailerInterface {
public $sentEmail = null;
public function sendWelcome($email): bool {
throw new Exception('Should not be called');
}
};
$app = new Engine();
$app->request()->data->email = 'invalid-email';
// Need to map jsonHalt to avoid exiting
$app->map('jsonHalt', function($data) use ($app) {
$app->json($data, 400);
});
$controller = new UserControllerDIC($app, $mockDb, $mockMailer);
$controller->register();
$response = $app->response()->getBody();
$result = json_decode($response, true);
$this->assertEquals('error', $result['status']);
$this->assertEquals('Invalid email', $result['message']);
}
}
Puntos Clave:
- Simulamos
PdoWrapper
yMailerInterface
para evitar llamadas reales a base de datos o correos. - Las pruebas verifican comportamiento: correos válidos activan inserciones en base de datos y envíos de correo; correos inválidos saltan ambos.
- Simula dependencias de terceros (por ejemplo,
PdoWrapper
,MailerInterface
), dejando que la lógica del controlador se ejecute.
Simulando Demasiado
Ten cuidado de no simular demasiado de tu código. Déjame darte un ejemplo abajo sobre por qué esto podría ser algo malo usando nuestro UserController
. Cambiaremos esa verificación en un método llamado isEmailValid
(usando filter_var
) y las otras nuevas adiciones en un método separado llamado registerUser
.
use flight\database\PdoWrapper;
use flight\Engine;
// UserControllerDICV2.php
class UserControllerDICV2 {
protected $app;
protected $db;
protected $mailer;
public function __construct(Engine $app, PdoWrapper $db, MailerInterface $mailer) {
$this->app = $app;
$this->db = $db;
$this->mailer = $mailer;
}
public function register() {
$email = $this->app->request()->data->email;
if (!$this->isEmailValid($email)) {
// adding the return here helps unit testing to stop execution
return $this->app->jsonHalt(['status' => 'error', 'message' => 'Invalid email']);
}
$this->registerUser($email);
$this->app->json(['status' => 'success', 'message' => 'User registered']);
}
protected function isEmailValid($email) {
return filter_var($email, FILTER_VALIDATE_EMAIL) !== false;
}
protected function registerUser($email) {
$this->db->runQuery('INSERT INTO users (email) VALUES (?)', [$email]);
$this->mailer->sendWelcome($email);
}
}
Y ahora la prueba unitaria sobremapeada que no prueba realmente nada:
use PHPUnit\Framework\TestCase;
class UserControllerTest extends TestCase {
public function testValidEmailSavesAndSendsEmail() {
$app = new Engine();
$app->request()->data->email = 'test@example.com';
// we are skipping the extra dependency injection here cause it's "easy"
$controller = new class($app) extends UserControllerDICV2 {
protected $app;
// Bypass the deps in the construct
public function __construct($app) {
$this->app = $app;
}
// We'll just force this to be valid.
protected function isEmailValid($email) {
return true; // Always return true, bypassing real validation
}
// Bypass the actual DB and mailer calls
protected function registerUser($email) {
return false;
}
};
$controller->register();
$response = $app->response()->getBody();
$result = json_decode($response, true);
$this->assertEquals('success', $result['status']);
$this->assertEquals('User registered', $result['message']);
}
}
¡Hurra, tenemos pruebas unitarias y están pasando! Pero espera, ¿qué pasa si realmente cambio el funcionamiento interno de isEmailValid
o registerUser
? Mis pruebas seguirán pasando porque he simulado toda la funcionalidad. Déjame mostrarte lo que quiero decir.
// UserControllerDICV2.php
class UserControllerDICV2 {
// ... other methods ...
protected function isEmailValid($email) {
// Changed logic
$validEmail = filter_var($email, FILTER_VALIDATE_EMAIL) !== false;
// Now it should only have a specific domain
$validDomain = strpos($email, '@example.com') !== false;
return $validEmail && $validDomain;
}
}
Si ejecuto mis pruebas unitarias anteriores, ¡todavía pasan! Pero porque no estaba probando por comportamiento (dejando que algo del código se ejecute realmente), he codificado potencialmente un error esperando ocurrir en producción. La prueba debería modificarse para tener en cuenta el nuevo comportamiento, y también lo opuesto de cuando el comportamiento no es lo que esperamos.
Ejemplo Completo
Puedes encontrar un ejemplo completo de un proyecto Flight PHP con pruebas unitarias en GitHub: n0nag0n/flight-unit-tests-guide. Para una comprensión más profunda, ver Pruebas Unitarias y Principios SOLID.
Errores Comunes
- Sobremapeo: No simules cada dependencia; deja que algo de lógica (por ejemplo, validación de controlador) se ejecute para probar comportamiento real. Ver Pruebas Unitarias y Principios SOLID.
- Estado Global: Usar variables PHP globales (por ejemplo,
$_SESSION
,$_COOKIE
) de manera intensiva hace que las pruebas sean frágiles. Lo mismo conFlight::
. Refactoriza para pasar dependencias explícitamente. - Configuración Compleja: Si la configuración de la prueba es engorrosa, tu clase puede tener demasiadas dependencias o responsabilidades violando los principios SOLID.
Escalando con Pruebas Unitarias
Las pruebas unitarias brillan en proyectos más grandes o cuando revisitas código después de meses. Documentan comportamiento y detectan regresiones, ahorrándote de re-aprender tu aplicación. Para desarrolladores individuales, prueba rutas críticas (por ejemplo, registro de usuarios, procesamiento de pagos). Para equipos, las pruebas aseguran comportamiento consistente a través de contribuciones. Ver ¿Por qué Frameworks? para más sobre los beneficios de usar frameworks y pruebas.
¡Contribuye con tus propios consejos de pruebas al repositorio de documentación de Flight PHP!
Escrito por n0nag0n 2025
Guides/blog
Construyendo un Blog Simple con Flight PHP
Esta guía te guía a través de la creación de un blog básico utilizando el framework Flight PHP. Configurarás un proyecto, definirás rutas, administrarás publicaciones con JSON y las renderizarás con el motor de plantillas Latte, todo mostrando la simplicidad y flexibilidad de Flight. Al final, tendrás un blog funcional con una página de inicio, páginas de publicaciones individuales y un formulario de creación.
Requisitos Previos
- PHP 7.4+: Instalado en tu sistema.
- Composer: Para la gestión de dependencias.
- Editor de Texto: Cualquier editor como VS Code o PHPStorm.
- Conocimientos básicos de PHP y desarrollo web.
Paso 1: Configura Tu Proyecto
Comienza creando un nuevo directorio de proyecto e instalando Flight a través de Composer.
-
Crear un Directorio:
mkdir flight-blog cd flight-blog
-
Instalar Flight:
composer require flightphp/core
-
Crear un Directorio Público: Flight utiliza un único punto de entrada (
index.php
). Crea una carpetapublic/
para ello:mkdir public
-
Básico
index.php
: Creapublic/index.php
con una ruta simple de “hola mundo”:<?php require '../vendor/autoload.php'; Flight::route('/', function () { echo '¡Hola, Flight!'; }); Flight::start();
-
Ejecutar el Servidor Integrado: Prueba tu configuración con el servidor de desarrollo de PHP:
php -S localhost:8000 -t public/
Visita
http://localhost:8000
para ver “¡Hola, Flight!”.
Paso 2: Organizar la Estructura de Tu Proyecto
Para una configuración limpia, estructura tu proyecto así:
flight-blog/
├── app/
│ ├── config/
│ └── views/
├── data/
├── public/
│ └── index.php
├── vendor/
└── composer.json
app/config/
: Archivos de configuración (por ejemplo, eventos, rutas).app/views/
: Plantillas para renderizar páginas.data/
: Archivo JSON para almacenar publicaciones de blog.public/
: Raíz web conindex.php
.
Paso 3: Instalar y Configurar Latte
Latte es un motor de plantillas ligero que se integra bien con Flight.
-
Instalar Latte:
composer require latte/latte
-
Configurar Latte en Flight: Actualiza
public/index.php
para registrar Latte como el motor de vista:<?php require '../vendor/autoload.php'; use Latte\Engine; Flight::register('view', Engine::class, [], function ($latte) { $latte->setTempDirectory(__DIR__ . '/../cache/'); $latte->setLoader(new \Latte\Loaders\FileLoader(__DIR__ . '/../app/views/')); }); Flight::route('/', function () { Flight::view()->render('home.latte', ['title' => 'Mi Blog']); }); Flight::start();
-
Crear una Plantilla de Diseño: En
app/views/layout.latte
:<!DOCTYPE html> <html> <head> <title>{$title}</title> </head> <body> <header> <h1>Mi Blog</h1> <nav> <a href="/">Inicio</a> | <a href="/create">Crear una Publicación</a> </nav> </header> <main> {block content}{/block} </main> <footer> <p>© {date('Y')} Blog Flight</p> </footer> </body> </html>
-
Crear una Plantilla de Inicio: En
app/views/home.latte
:{extends 'layout.latte'} {block content} <h2>{$title}</h2> <ul> {foreach $posts as $post} <li><a href="/post/{$post['slug']}">{$post['title']}</a></li> {/foreach} </ul> {/block}
Reinicia el servidor si te has salido de él y visita
http://localhost:8000
para ver la página renderizada. -
Crear un Archivo de Datos:
Utiliza un archivo JSON para simular una base de datos por simplicidad.
En
data/posts.json
:[ { "slug": "primer-post", "title": "Mi Primer Post", "content": "¡Esta es mi primera publicación de blog con Flight PHP!" } ]
Paso 4: Definir Rutas
Separa tus rutas en un archivo de configuración para mejor organización.
-
Crear
routes.php
: Enapp/config/routes.php
:<?php Flight::route('/', function () { Flight::view()->render('home.latte', ['title' => 'Mi Blog']); }); Flight::route('/post/@slug', function ($slug) { Flight::view()->render('post.latte', ['title' => 'Publicación: ' . $slug, 'slug' => $slug]); }); Flight::route('GET /create', function () { Flight::view()->render('create.latte', ['title' => 'Crear una Publicación']); });
-
Actualizar
index.php
: Incluye el archivo de rutas:<?php require '../vendor/autoload.php'; use Latte\Engine; Flight::register('view', Engine::class, [], function ($latte) { $latte->setTempDirectory(__DIR__ . '/../cache/'); $latte->setLoader(new \Latte\Loaders\FileLoader(__DIR__ . '/../app/views/')); }); require '../app/config/routes.php'; Flight::start();
Paso 5: Almacenar y Recuperar Publicaciones de Blog
Agrega los métodos para cargar y guardar publicaciones.
-
Agregar un Método de Publicaciones: En
index.php
, agrega un método para cargar publicaciones:Flight::map('posts', function () { $file = __DIR__ . '/../data/posts.json'; return json_decode(file_get_contents($file), true); });
-
Actualizar Rutas: Modifica
app/config/routes.php
para usar publicaciones:<?php Flight::route('/', function () { $posts = Flight::posts(); Flight::view()->render('home.latte', [ 'title' => 'Mi Blog', 'posts' => $posts ]); }); Flight::route('/post/@slug', function ($slug) { $posts = Flight::posts(); $post = array_filter($posts, fn($p) => $p['slug'] === $slug); $post = reset($post) ?: null; if (!$post) { Flight::notFound(); return; } Flight::view()->render('post.latte', [ 'title' => $post['title'], 'post' => $post ]); }); Flight::route('GET /create', function () { Flight::view()->render('create.latte', ['title' => 'Crear una Publicación']); });
Paso 6: Crear Plantillas
Actualiza tus plantillas para mostrar publicaciones.
-
Página de Publicación (
app/views/post.latte
):{extends 'layout.latte'} {block content} <h2>{$post['title']}</h2> <div class="post-content"> <p>{$post['content']}</p> </div> {/block}
Paso 7: Agregar Creación de Publicación
Manejar la presentación del formulario para agregar nuevas publicaciones.
-
Crear Formulario (
app/views/create.latte
):{extends 'layout.latte'} {block content} <h2>{$title}</h2> <form method="POST" action="/create"> <div class="form-group"> <label for="title">Título:</label> <input type="text" name="title" id="title" required> </div> <div class="form-group"> <label for="content">Contenido:</label> <textarea name="content" id="content" required></textarea> </div> <button type="submit">Guardar Publicación</button> </form> {/block}
-
Agregar Ruta POST: En
app/config/routes.php
:Flight::route('POST /create', function () { $request = Flight::request(); $title = $request->data['title']; $content = $request->data['content']; $slug = strtolower(str_replace(' ', '-', $title)); $posts = Flight::posts(); $posts[] = ['slug' => $slug, 'title' => $title, 'content' => $content]; file_put_contents(__DIR__ . '/../../data/posts.json', json_encode($posts, JSON_PRETTY_PRINT)); Flight::redirect('/'); });
-
Prueba:
- Visita
http://localhost:8000/create
. - Envía una nueva publicación (por ejemplo, “Segundo Post” con algo de contenido).
- Verifica la página de inicio para verlo listado.
- Visita
Paso 8: Mejorar con Manejo de Errores
Sobrescribe el método notFound
para una mejor experiencia 404.
En index.php
:
Flight::map('notFound', function () {
Flight::view()->render('404.latte', ['title' => 'Página No Encontrada']);
});
Crea app/views/404.latte
:
{extends 'layout.latte'}
{block content}
<h2>404 - {$title}</h2>
<p>¡Lo siento, esa página no existe!</p>
{/block}
Próximos Pasos
- Agregar Estilo: Utiliza CSS en tus plantillas para un mejor aspecto.
- Base de Datos: Reemplaza
posts.json
con una base de datos como SQLite utilizandoPdoWrapper
. - Validación: Agrega verificaciones para slugs duplicados o entradas vacías.
- Middleware: Implementa autenticación para la creación de publicaciones.
Conclusión
¡Has construido un blog simple con Flight PHP! Esta guía demuestra características básicas como enrutamiento, templating con Latte y manejo de envíos de formularios, todo mientras mantienes las cosas ligeras. ¡Explora la documentación de Flight para más características avanzadas que lleven tu blog más lejos!
License
Licencia MIT (MIT)
==================
Derechos de autor © `2024` `@mikecao, @n0nag0n`
Se concede permiso, de forma gratuita, a cualquier persona que obtenga
una copia de este software y archivos de documentación asociados
(el “Software”), para utilizar el Software sin restricciones,
incluidos, entre otros, los derechos de usar, copiar, modificar,
fusionar, publicar, distribuir, sublicenciar y/o vender copias del
Software, y permitir a las personas a las que se les haya provisto
el Software que lo hagan, sujeto a las siguientes condiciones:
El aviso de derechos de autor anterior y este aviso de permiso deben
estar incluidos en todas las copias o partes sustanciales del Software.
EL SOFTWARE SE PROPORCIONA "TAL CUAL", SIN GARANTÍA DE NINGÚN TIPO,
EXPRESA O IMPLÍCITA, INCLUIDAS, ENTRE OTRAS, LAS GARANTÍAS DE
COMERCIABILIDAD, IDONEIDAD PARA UN PROPÓSITO PARTICULAR Y NO
INFRACCIÓN. EN NINGÚN CASO LOS AUTORES O TITULARES DE LOS DERECHOS
DE AUTOR SERÁN RESPONSABLES DE NINGUNA RECLAMACIÓN, DAÑOS U OTRA
RESPONSABILIDAD, YA SEA EN UNA ACCIÓN DE CONTRATO, AGRAVIO O DE CUALQUIER
OTRA ÍNDOLE, DERIVADA DE, FUERA DE O EN RELACIÓN CON EL SOFTWARE O EL USO
U OTRO TIPO DE TRATAMIENTO EN EL SOFTWARE.
About
Marco de trabajo PHP Flight
Flight es un framework rápido, simple y extensible para PHP, diseñado para desarrolladores que quieren hacer las cosas rápidamente, sin complicaciones. Ya sea que estés construyendo una aplicación web clásica, una API ultrarrápida o experimentando con las últimas herramientas impulsadas por IA, la huella baja de Flight y su diseño directo lo convierten en una opción perfecta. Flight está destinado a ser ligero, pero también puede manejar requisitos de arquitectura empresarial.
¿Por qué elegir Flight?
- Amigable para principiantes: Flight es un gran punto de partida para nuevos desarrolladores de PHP. Su estructura clara y sintaxis simple te ayudan a aprender desarrollo web sin perderte en código innecesario.
- Amado por los profesionales: Los desarrolladores experimentados aman Flight por su flexibilidad y control. Puedes escalar desde un prototipo pequeño hasta una aplicación completa sin cambiar de framework.
- Amigable con la IA: La sobrecarga mínima y la arquitectura limpia de Flight lo hacen ideal para integrar herramientas y APIs de IA. Ya sea que estés construyendo chatbots inteligentes, tableros impulsados por IA o simplemente quieras experimentar, Flight se quita de en medio para que te enfoques en lo que importa. ¡La aplicación esqueleto viene con archivos de instrucciones precompilados para los principales asistentes de codificación de IA desde el principio! Aprende más sobre el uso de IA con Flight
Resumen en video
Inicio rápido
Para hacer una instalación básica y rápida, instálalo con Composer:
composer require flightphp/core
O puedes descargar un zip del repositorio aquí. Luego, tendrías un archivo básico index.php
como el siguiente:
<?php
// si se instaló con composer
require 'vendor/autoload.php';
// o si se instaló manualmente por archivo zip
// require 'flight/Flight.php';
Flight::route('/', function() {
echo 'hello world!';
});
Flight::route('/json', function() {
Flight::json([
'hello' => 'world'
]);
});
Flight::start();
¡Eso es todo! Tienes una aplicación básica de Flight. Ahora puedes ejecutar este archivo con php -S localhost:8000
y visitar http://localhost:8000
en tu navegador para ver la salida.
Aplicación esqueleto/plantilla
Hay una aplicación de ejemplo para ayudarte a comenzar tu proyecto con Flight. Tiene una estructura organizada, configuraciones básicas listas y maneja scripts de composer desde el principio. Echa un vistazo a flightphp/skeleton para un proyecto listo para usar, o visita la página de ejemplos para inspiración. ¿Quieres ver cómo encaja la IA? Explora ejemplos impulsados por IA.
Instalando la aplicación esqueleto
¡Fácil!
# Crea el nuevo proyecto
composer create-project flightphp/skeleton my-project/
# Entra en el directorio de tu nuevo proyecto
cd my-project/
# Inicia el servidor de desarrollo local para comenzar de inmediato
composer start
¡Creará la estructura del proyecto, configurará los archivos que necesitas y estarás listo para comenzar!
Alto rendimiento
Flight es uno de los frameworks de PHP más rápidos disponibles. Su núcleo ligero significa menos sobrecarga y más velocidad, perfecto para aplicaciones tradicionales y proyectos modernos impulsados por IA. Puedes ver todos los benchmarks en TechEmpower.
Ve el benchmark a continuación con algunos otros frameworks de PHP populares.
Framework | Reqs/sec en texto plano | Reqs/sec en JSON |
---|---|---|
Flight | 190,421 | 182,491 |
Yii | 145,749 | 131,434 |
Fat-Free | 139,238 | 133,952 |
Slim | 89,588 | 87,348 |
Phalcon | 95,911 | 87,675 |
Symfony | 65,053 | 63,237 |
Lumen | 40,572 | 39,700 |
Laravel | 26,657 | 26,901 |
CodeIgniter | 20,628 | 19,901 |
Flight y la IA
¿Curioso de cómo maneja la IA? Descubre cómo Flight facilita trabajar con tu LLM de codificación favorito.
Comunidad
Estamos en Matrix Chat
Y Discord
Contribuyendo
Hay dos formas en que puedes contribuir a Flight:
- Contribuye al framework principal visitando el repositorio principal.
- ¡Ayuda a mejorar los documentos! Este sitio web de documentación se aloja en Github. Si encuentras un error o quieres mejorar algo, no dudes en enviar una solicitud de extracción. ¡Amamos las actualizaciones y nuevas ideas, especialmente alrededor de la IA y las nuevas tecnologías!
Requisitos
Flight requiere PHP 7.4 o superior.
Nota: PHP 7.4 es compatible porque, en el momento de escribir esto (2024), PHP 7.4 es la versión predeterminada para algunas distribuciones LTS de Linux. Forzar un cambio a PHP >8 causaría muchos problemas para esos usuarios. El framework también soporta PHP >8.
Licencia
Flight se libera bajo la licencia MIT.
Awesome-plugins/php_cookie
Cookies
overclokk/cookie es una biblioteca sencilla para administrar cookies dentro de su aplicación.
Instalación
La instalación es sencilla con composer.
composer require overclokk/cookie
Uso
El uso es tan simple como registrar un nuevo método en la clase Flight.
use Overclokk\Cookie\Cookie;
/*
* Establezca en su archivo bootstrap o public/index.php
*/
Flight::register('cookie', Cookie::class);
/**
* ExampleController.php
*/
class ExampleController {
public function login() {
// Establecer una cookie
// querrás que esto sea falso para obtener una nueva instancia
// usa el comentario a continuación si deseas el autocompletado
/** @var \Overclokk\Cookie\Cookie $cookie */
$cookie = Flight::cookie(false);
$cookie->set(
'stay_logged_in', // nombre de la cookie
'1', // el valor que deseas establecer
86400, // número de segundos que la cookie debe durar
'/', // ruta en la que estará disponible la cookie
'example.com', // dominio en el que estará disponible la cookie
true, // la cookie solo se transmitirá a través de una conexión segura HTTPS
true // la cookie solo estará disponible a través del protocolo HTTP
);
// opcionalmente, si deseas mantener los valores predeterminados
// y tener una forma rápida de establecer una cookie por mucho tiempo
$cookie->forever('stay_logged_in', '1');
}
public function home() {
// Verifica si tienes la cookie
if (Flight::cookie()->has('stay_logged_in')) {
// ponlos en el área del panel, por ejemplo.
Flight::redirect('/dashboard');
}
}
}
Awesome-plugins/php_encryption
Cifrado en PHP
defuse/php-encryption es una biblioteca que se puede utilizar para cifrar y descifrar datos. Ponerse en marcha es bastante simple para comenzar a cifrar y descifrar datos. Tienen un excelente tutorial que ayuda a explicar los conceptos básicos sobre cómo utilizar la biblioteca, así como las importantes implicaciones de seguridad relacionadas con el cifrado.
Instalación
La instalación es sencilla con composer.
composer require defuse/php-encryption
Configuración
Luego necesitarás generar una clave de cifrado.
vendor/bin/generate-defuse-key
Esto generará una clave que deberás mantener segura. Podrías guardar la clave en tu archivo app/config/config.php
en el array al final del archivo. Aunque no es el lugar perfecto, al menos es algo.
Uso
Ahora que tienes la biblioteca y una clave de cifrado, puedes empezar a cifrar y descifrar datos.
use Defuse\Crypto\Crypto;
use Defuse\Crypto\Key;
/*
* Establecerlo en tu archivo de inicio (bootstrap) o public/index.php
*/
// Método de cifrado
Flight::map('encrypt', function($datos_crudos) {
$clave_cifrado = /* $config['clave_cifrado'] o un file_get_contents de dónde pusiste la clave */;
return Crypto::encrypt($datos_crudos, Key::loadFromAsciiSafeString($clave_cifrado));
});
// Método de descifrado
Flight::map('decrypt', function($datos_cifrados) {
$clave_cifrado = /* $config['clave_cifrado'] o un file_get_contents de dónde pusiste la clave */;
try {
$datos_crudos = Crypto::decrypt($datos_cifrados, Key::loadFromAsciiSafeString($clave_cifrado));
} catch (Defuse\Crypto\Exception\WrongKeyOrModifiedCiphertextException $ex) {
// ¡Un ataque! Se cargó la clave incorrecta o el texto cifrado
// ha cambiado desde que fue creado, ya sea corrompido en la base de datos o modificado intencionalmente por Eve tratando de llevar a cabo un ataque.
// ... manejar este caso de una manera adecuada para tu aplicación ...
}
return $datos_crudos;
});
Flight::route('/cifrar', function() {
$datos_cifrados = Flight::encrypt('Esto es un secreto');
echo $datos_cifrados;
});
Flight::route('/descifrar', function() {
$datos_cifrados = '...'; // Obtener los datos cifrados de algún lugar
$datos_descifrados = Flight::decrypt($datos_cifrados);
echo $datos_descifrados;
});
Awesome-plugins/php_file_cache
flightphp/cache
Clase ligera, simple y autónoma de caché en archivo PHP bifurcada de Wruczek/PHP-File-Cache
Ventajas
- Ligera, autónoma y simple
- Todo el código en un solo archivo - sin controladores innecesarios.
- Segura - cada archivo de caché generado tiene un encabezado PHP con die, haciendo imposible el acceso directo incluso si alguien conoce la ruta y tu servidor no está configurado correctamente
- Bien documentada y probada
- Maneja la concurrencia correctamente mediante flock
- Soporta PHP 7.4+
- Gratuita bajo una licencia MIT
¡Este sitio de documentación está utilizando esta biblioteca para almacenar en caché cada una de las páginas!
Haz clic aquí para ver el código.
Instalación
Instala mediante composer:
composer require flightphp/cache
Uso
El uso es bastante directo. Esto guarda un archivo de caché en el directorio de caché.
use flight\Cache;
$app = Flight::app();
// Pasas el directorio donde se almacenará el caché al constructor
$app->register('cache', Cache::class, [ __DIR__ . '/../cache/' ], function(Cache $cache) {
// Esto asegura que el caché solo se use en modo producción
// ENVIRONMENT es una constante que se establece en tu archivo de bootstrap o en otra parte de tu app
$cache->setDevMode(ENVIRONMENT === 'development');
});
Obtener un Valor de Caché
Usas el método get()
para obtener un valor en caché. Si quieres un método conveniente que actualice el caché si ha expirado, puedes usar refreshIfExpired()
.
// Obtener instancia de caché
$cache = Flight::cache();
$data = $cache->refreshIfExpired('simple-cache-test', function () {
return date("H:i:s"); // devolver datos para ser almacenados en caché
}, 10); // 10 segundos
// o
$data = $cache->get('simple-cache-test');
if(empty($data)) {
$data = date("H:i:s");
$cache->set('simple-cache-test', $data, 10); // 10 segundos
}
Almacenar un Valor de Caché
Usas el método set()
para almacenar un valor en el caché.
Flight::cache()->set('simple-cache-test', 'my cached data', 10); // 10 segundos
Borrar un Valor de Caché
Usas el método delete()
para borrar un valor en el caché.
Flight::cache()->delete('simple-cache-test');
Verificar si Existe un Valor de Caché
Usas el método exists()
para verificar si un valor existe en el caché.
if(Flight::cache()->exists('simple-cache-test')) {
// hacer algo
}
Limpiar el Caché
Usas el método flush()
para limpiar todo el caché.
Flight::cache()->flush();
Extraer metadatos con caché
Si quieres extraer marcas de tiempo y otros metadatos sobre una entrada de caché, asegúrate de pasar true
como el parámetro correcto.
$data = $cache->refreshIfExpired("simple-cache-meta-test", function () {
echo "Refreshing data!" . PHP_EOL;
return date("H:i:s"); // devolver datos para ser almacenados en caché
}, 10, true); // true = devolver con metadatos
// o
$data = $cache->get("simple-cache-meta-test", true); // true = devolver con metadatos
/*
Ejemplo de elemento en caché recuperado con metadatos:
{
"time":1511667506, <-- marca de tiempo unix de guardado
"expire":10, <-- tiempo de expiración en segundos
"data":"04:38:26", <-- datos deserializados
"permanent":false
}
Usando metadatos, podemos, por ejemplo, calcular cuándo se guardó el elemento o cuándo expira
También podemos acceder a los datos en sí con la clave "data"
*/
$expiresin = ($data["time"] + $data["expire"]) - time(); // obtener marca de tiempo unix cuando expiran los datos y restar la marca de tiempo actual
$cacheddate = $data["data"]; // accedemos a los datos en sí con la clave "data"
echo "Último guardado de caché: $cacheddate, expira en $expiresin segundos";
Documentación
Visita https://github.com/flightphp/cache para ver el código. Asegúrate de ver la carpeta examples para formas adicionales de usar el caché.
Awesome-plugins/permissions
FlightPHP/Permisos
Se trata de un módulo de permisos que se puede utilizar en tus proyectos si tienes múltiples roles en tu aplicación y cada rol tiene una funcionalidad un poco diferente. Este módulo te permite definir permisos para cada rol y luego verificar si el usuario actual tiene el permiso para acceder a una página específica o realizar una acción determinada.
Haz clic aquí para acceder al repositorio en GitHub.
Instalación
¡Ejecuta composer require flightphp/permissions
y estás listo/a!
Uso
Primero necesitas configurar tus permisos, luego le indicas a tu aplicación lo que significan los permisos. Finalmente verificarás tus permisos con $Permissions->has()
, ->can()
, o is()
. has()
y can()
tienen la misma funcionalidad, pero están nombrados de manera diferente para que tu código sea más legible.
Ejemplo Básico
Imaginemos que tienes una función en tu aplicación que verifica si un usuario ha iniciado sesión. Puedes crear un objeto de permisos de esta manera:
// index.php
require 'vendor/autoload.php';
// algún código
// luego probablemente tengas algo que te diga cuál es el rol actual de la persona
// probablemente tengas algo donde obtienes el rol actual
// de una variable de sesión que lo define
// después de que alguien inicie sesión, de lo contrario tendrán un rol de 'invitado' o 'público'.
$current_role = 'admin';
// configurar permisos
$permiso = new \flight\Permiso($current_role);
$permiso->defineRegla('iniciadoSesion', function($current_role) {
return $current_role !== 'invitado';
});
// Probablemente querrás persistir este objeto en Flight en algún lugar
Flight::set('permiso', $permiso);
Luego en un controlador en algún lugar, podrías tener algo como esto.
<?php
// algún controlador
class AlgunControlador {
public function algunaAccion() {
$permiso = Flight::get('permiso');
if ($permiso->has('iniciadoSesion')) {
// hacer algo
} else {
// hacer algo más
}
}
}
También puedes usar esto para rastrear si tienen permiso para hacer algo en tu aplicación. Por ejemplo, si tienes una forma en la que los usuarios pueden interactuar con la publicación en tu software, puedes verificar si tienen permiso para realizar ciertas acciones.
$current_role = 'admin';
// configurar permisos
$permiso = new \flight\Permiso($current_role);
$permiso->defineRegla('publicación', function($current_role) {
if($current_role === 'admin') {
$permisos = ['crear', 'leer', 'actualizar', 'eliminar'];
} else if($current_role === 'editor') {
$permisos = ['crear', 'leer', 'actualizar'];
} else if($current_role === 'autor') {
$permisos = ['crear', 'leer'];
} else if($current_role === 'colaborador') {
$permisos = ['crear'];
} else {
$permisos = [];
}
return $permisos;
});
Flight::set('permiso', $permiso);
Luego en un controlador en algún lugar...
class ControladorPublicación {
public function crear() {
$permiso = Flight::get('permiso');
if ($permiso->can('publicación.crear')) {
// hacer algo
} else {
// hacer algo más
}
}
}
Inyectar dependencias
Puedes inyectar dependencias en el cierre que define los permisos. Esto es útil si tienes algún tipo de interruptor, identificación, u otro punto de datos que deseas verificar. Lo mismo funciona para llamadas de tipo Clase->Método, excepto que defines los argumentos en el método.
Cierres
$Permiso->defineRegla('orden', function(string $current_role, MiDependencia $MiDependencia = null) {
// ... código
});
// en tu archivo de controlador
public function crearOrden() {
$MiDependencia = Flight::miDependencia();
$permiso = Flight::get('permiso');
if ($permiso->can('orden.crear', $MiDependencia)) {
// hacer algo
} else {
// hacer algo más
}
}
Clases
namespace MiApp;
class Permisos {
public function orden(string $current_role, MiDependencia $MiDependencia = null) {
// ... código
}
}
Atajo para establecer permisos con clases
También puedes usar clases para definir tus permisos. Esto es útil si tienes muchos permisos y deseas mantener tu código limpio. Puedes hacer algo como esto:
<?php
// código de inicio
$Permisos = new \flight\Permiso($current_role);
$Permisos->defineRegla('orden', 'MiApp\Permisos->orden');
// myapp/Permisos.php
namespace MiApp;
class Permisos {
public function orden(string $current_role, int $user_id) {
// Suponiendo que configuraste esto de antemano
/** @var \flight\database\PdoWrapper $db */
$db = Flight::db();
$permisos_permitidos = [ 'leer' ]; // todos pueden ver una orden
if($current_role === 'gerente') {
$permisos_permitidos[] = 'crear'; // los gerentes pueden crear órdenes
}
$algún_interruptor_especial_de_db = $db->fetchField('SELECT algún_interruptor_especial FROM ajustes WHERE id = ?', [ $user_id ]);
if($algún_interruptor_especial_de_db) {
$permisos_permitidos[] = 'actualizar'; // si el usuario tiene un interruptor especial, pueden actualizar órdenes
}
if($current_role === 'admin') {
$permisos_permitidos[] = 'eliminar'; // los administradores pueden eliminar órdenes
}
return $permisos_permitidos;
}
}
La parte genial es que también hay un atajo que puedes usar (¡también puede ser almacenado en caché!) donde simplemente indicas a la clase de permisos que mapee todos los métodos de una clase en permisos. Por lo tanto, si tienes un método llamado orden()
y un método llamado empresa()
, se mapearán automáticamente para que puedas simplemente ejecutar $Permisos->has('orden.leer')
o $Permisos->has('empresa.leer')
y funcionará. Definir esto es muy complejo, así que quédate conmigo aquí. Solo necesitas hacer esto:
Crea la clase de permisos que deseas agrupar.
class MisPermisos {
public function orden(string $current_role, int $orden_id = 0): array {
// código para determinar permisos
return $arreglo_permisos;
}
public function empresa(string $current_role, int $empresa_id): array {
// código para determinar permisos
return $arreglo_permisos;
}
}
Luego haz que los permisos sean descubribles usando esta biblioteca.
$Permisos = new \flight\Permiso($current_role);
$Permisos->defineReglasDesdeMétodosDeClase(MiApp\Permisos::class);
Flight::set('permisos', $Permisos);
Finalmente, llama al permiso en tu base de código para verificar si el usuario tiene permiso para realizar un permiso dado.
class AlgunControlador {
public function crearOrden() {
if(Flight::get('permisos')->can('orden.crear') === false) {
die('¡No puedes crear una orden. ¡Lo siento!');
}
}
}
Caché
Para habilitar la caché, consulta la sencilla biblioteca wruczak/phpfilecache. Un ejemplo de cómo habilitar esto se muestra a continuación.
// esta variable $app puede ser parte de tu código, o
// simplemente puedes pasar null y se
// obtendrá de Flight::app() en el constructor
$app = Flight::app();
// Por ahora solo acepta esto como una caché de archivos. Otros pueden agregarse fácilmente en el futuro.
$Caché = new Wruczek\PhpFileCaché\PhpFileCaché;
$Permisos = new \flight\Permiso($current_role, $app, $Caché);
$Permisos->defineReglasDesdeMétodosDeClase(MiApp\Permisos::class, 3600); // 3600 indica cuántos segundos almacenar en caché. Omite esto para no usar la caché
¡Y listo!
Awesome-plugins/simple_job_queue
Cola de Trabajos Simple
La Cola de Trabajos Simple es una biblioteca que se puede utilizar para procesar trabajos de forma asíncrona. Se puede usar con beanstalkd, MySQL/MariaDB, SQLite y PostgreSQL.
Instalar
composer require n0nag0n/simple-job-queue
Uso
Para que esto funcione, necesitas una manera de agregar trabajos a la cola y una manera de procesar los trabajos (un trabajador). A continuación se presentan ejemplos de cómo agregar un trabajo a la cola y cómo procesar el trabajo.
Agregando a Flight
Agregar esto a Flight es simple y se hace utilizando el método register()
. A continuación se muestra un ejemplo de cómo agregar esto a Flight.
<?php
require 'vendor/autoload.php';
// Cambia ['mysql'] a ['beanstalkd'] si deseas usar beanstalkd
Flight::register('queue', n0nag0n\Job_Queue::class, ['mysql'], function($Job_Queue) {
// si ya tienes una conexión PDO en Flight::db();
$Job_Queue->addQueueConnection(Flight::db());
// o si estás usando beanstalkd/Pheanstalk
$pheanstalk = Pheanstalk\Pheanstalk::create('127.0.0.1');
$Job_Queue->addQueueConnection($pheanstalk);
});
Agregando un nuevo trabajo
Cuando agregas un trabajo, necesitas especificar un pipeline (cola). Esto es comparable a un canal en RabbitMQ o un tubo en beanstalkd.
<?php
Flight::queue()->selectPipeline('send_important_emails');
Flight::queue()->addJob(json_encode([ 'something' => 'that', 'ends' => 'up', 'a' => 'string' ]));
Ejecutando un trabajador
Aquí hay un archivo de ejemplo de cómo ejecutar un trabajador.
<?php
require 'vendor/autoload.php';
$Job_Queue = new n0nag0n\Job_Queue('mysql');
// Conexión PDO
$PDO = new PDO('mysql:dbname=testdb;host=127.0.0.1', 'user', 'pass');
$Job_Queue->addQueueConnection($PDO);
// o si estás usando beanstalkd/Pheanstalk
$pheanstalk = Pheanstalk\Pheanstalk::create('127.0.0.1');
$Job_Queue->addQueueConnection($pheanstalk);
$Job_Queue->watchPipeline('send_important_emails');
while(true) {
$job = $Job_Queue->getNextJobAndReserve();
// ajusta lo que te haga dormir mejor por la noche (solo para colas de base de datos, beanstalkd no necesita esta declaración if)
if(empty($job)) {
usleep(500000);
continue;
}
echo "Procesando {$job['id']}\n";
$payload = json_decode($job['payload'], true);
try {
$result = doSomethingThatDoesSomething($payload);
if($result === true) {
$Job_Queue->deleteJob($job);
} else {
// esto lo saca de la cola lista y lo coloca en otra cola que puede ser recogida y "patada" más tarde.
$Job_Queue->buryJob($job);
}
} catch(Exception $e) {
$Job_Queue->buryJob($job);
}
}
Manejo de Procesos Largos con Supervisord
Supervisord es un sistema de control de procesos que garantiza que tus procesos de trabajo sigan funcionando continuamente. Aquí hay una guía más completa sobre cómo configurarlo con tu trabajador de Cola de Trabajos Simple:
Instalando Supervisord
# En Ubuntu/Debian
sudo apt-get install supervisor
# En CentOS/RHEL
sudo yum install supervisor
# En macOS con Homebrew
brew install supervisor
Creando un Script de Trabajador
Primero, guarda tu código de trabajador en un archivo PHP dedicado:
<?php
require 'vendor/autoload.php';
$Job_Queue = new n0nag0n\Job_Queue('mysql');
// Conexión PDO
$PDO = new PDO('mysql:dbname=your_database;host=127.0.0.1', 'username', 'password');
$Job_Queue->addQueueConnection($PDO);
// Establecer el pipeline a observar
$Job_Queue->watchPipeline('send_important_emails');
// Registrar inicio del trabajador
echo date('Y-m-d H:i:s') . " - Trabajador iniciado\n";
while(true) {
$job = $Job_Queue->getNextJobAndReserve();
if(empty($job)) {
usleep(500000); // Dormir durante 0.5 segundos
continue;
}
echo date('Y-m-d H:i:s') . " - Procesando trabajo {$job['id']}\n";
$payload = json_decode($job['payload'], true);
try {
$result = doSomethingThatDoesSomething($payload);
if($result === true) {
$Job_Queue->deleteJob($job);
echo date('Y-m-d H:i:s') . " - Trabajo {$job['id']} completado con éxito\n";
} else {
$Job_Queue->buryJob($job);
echo date('Y-m-d H:i:s') . " - Trabajo {$job['id']} falló, enterrado\n";
}
} catch(Exception $e) {
$Job_Queue->buryJob($job);
echo date('Y-m-d H:i:s') . " - Excepción al procesar el trabajo {$job['id']}: {$e->getMessage()}\n";
}
}
Configurando Supervisord
Crea un archivo de configuración para tu trabajador:
[program:email_worker]
command=php /path/to/worker.php
directory=/path/to/project
autostart=true
autorestart=true
startretries=3
stderr_logfile=/var/log/simple_job_queue_err.log
stdout_logfile=/var/log/simple_job_queue.log
user=www-data
numprocs=2
process_name=%(program_name)s_%(process_num)02d
Opciones Clave de Configuración:
command
: El comando para ejecutar tu trabajadordirectory
: Directorio de trabajo para el trabajadorautostart
: Iniciar automáticamente cuando supervisord iniciaautorestart
: Reiniciar automáticamente si el proceso salestartretries
: Número de veces para intentar iniciar si fallastderr_logfile
/stdout_logfile
: Ubicaciones de los archivos de registrouser
: Usuario del sistema para ejecutar el procesonumprocs
: Número de instancias de trabajador a ejecutarprocess_name
: Formato de nombre para múltiples procesos de trabajador
Gestionando Trabajadores con Supervisorctl
Después de crear o modificar la configuración:
# Recargar configuración de supervisor
sudo supervisorctl reread
sudo supervisorctl update
# Controlar procesos de trabajadores específicos
sudo supervisorctl start email_worker:*
sudo supervisorctl stop email_worker:*
sudo supervisorctl restart email_worker:*
sudo supervisorctl status email_worker:*
Ejecutando Múltiples Pipelines
Para múltiples pipelines, crea archivos y configuraciones de trabajador separados:
[program:email_worker]
command=php /path/to/email_worker.php
# ... otras configuraciones ...
[program:notification_worker]
command=php /path/to/notification_worker.php
# ... otras configuraciones ...
Monitoreo y Registros
Verifica los registros para monitorear la actividad del trabajador:
# Ver registros
sudo tail -f /var/log/simple_job_queue.log
# Verificar estado
sudo supervisorctl status
Esta configuración asegura que tus trabajadores de trabajos continúen funcionando incluso después de fallos, reinicios del servidor u otros problemas, haciendo que tu sistema de colas sea confiable para entornos de producción.
Awesome-plugins/n0nag0n_wordpress
Integración de WordPress: n0nag0n/wordpress-integration-for-flight-framework
¿Quieres usar Flight PHP dentro de tu sitio de WordPress? ¡Este plugin lo hace muy fácil! Con n0nag0n/wordpress-integration-for-flight-framework
, puedes ejecutar una aplicación completa de Flight junto con tu instalación de WordPress, perfecto para construir APIs personalizadas, microservicios o incluso aplicaciones completas sin salir de la comodidad de WordPress.
¿Qué hace?
- Integra de manera perfecta Flight PHP con WordPress
- Enruta solicitudes a Flight o WordPress según patrones de URL
- Organiza tu código con controladores, modelos y vistas (MVC)
- Configura fácilmente la estructura de carpetas recomendada de Flight
- Usa la conexión de base de datos de WordPress o la tuya propia
- Ajusta finamente cómo interactúan Flight y WordPress
- Interfaz de administración simple para la configuración
Instalación
- Sube la carpeta
flight-integration
a tu directorio/wp-content/plugins/
. - Activa el plugin en la administración de WordPress (menú de Plugins).
- Ve a Settings > Flight Framework para configurar el plugin.
- Establece la ruta del proveedor a tu instalación de Flight (o usa Composer para instalar Flight).
- Configura la ruta de tu carpeta de aplicación y crea la estructura de carpetas (¡el plugin puede ayudarte con esto!).
- ¡Comienza a construir tu aplicación de Flight!
Ejemplos de Uso
Ejemplo Básico de Ruta
En tu archivo app/config/routes.php
:
Flight::route('GET /api/hello', function() {
Flight::json(['message' => 'Hello World!']);
});
Ejemplo de Controlador
Crea un controlador en app/controllers/ApiController.php
:
namespace app\controllers;
use Flight;
class ApiController {
public function getUsers() {
// ¡Puedes usar funciones de WordPress dentro de Flight!
$users = get_users();
$result = [];
foreach($users as $user) {
$result[] = [
'id' => $user->ID,
'name' => $user->display_name,
'email' => $user->user_email
];
}
Flight::json($result);
}
}
Luego, en tu routes.php
:
Flight::route('GET /api/users', [app\controllers\ApiController::class, 'getUsers']);
Preguntas Frecuentes
P: ¿Necesito conocer Flight para usar este plugin?
R: Sí, esto es para desarrolladores que quieran usar Flight dentro de WordPress. Se recomienda un conocimiento básico de enrutamiento y manejo de solicitudes de Flight.
P: ¿Esto ralentizará mi sitio de WordPress?
R: ¡No! El plugin solo procesa solicitudes que coincidan con tus rutas de Flight. Todas las demás solicitudes van a WordPress como de costumbre.
P: ¿Puedo usar funciones de WordPress en mi aplicación de Flight?
R: ¡Absolutamente! Tienes acceso completo a todas las funciones, hooks y globales de WordPress desde dentro de tus rutas y controladores de Flight.
P: ¿Cómo creo rutas personalizadas?
R: Define tus rutas en el archivo config/routes.php
en tu carpeta de aplicación. Consulta el archivo de muestra creado por el generador de estructura de carpetas para ejemplos.
Registro de Cambios
1.0.0
Lanzamiento inicial.
Para más información, consulta el GitHub repo.
Awesome-plugins/ghost_session
Ghostff/Session
Administrador de sesiones PHP (no bloqueante, flash, segment, encriptación de sesión). Usa PHP open_ssl para encriptación/desencriptación opcional de datos de sesión. Admite File, MySQL, Redis y Memcached.
Haz clic aquí para ver el código.
Instalación
Instala con composer.
composer require ghostff/session
Configuración Básica
No es necesario pasar nada para usar la configuración predeterminada con tu sesión. Puedes leer sobre más configuraciones en el Github Readme.
use Ghostff\Session\Session;
require 'vendor/autoload.php';
$app = Flight::app();
$app->register('session', Session::class);
// una cosa a recordar es que debes confirmar tu sesión en cada carga de página
// o tendrás que ejecutar auto_commit en tu configuración.
Ejemplo Simple
Aquí hay un ejemplo simple de cómo podrías usar esto.
Flight::route('POST /login', function() {
$session = Flight::session();
// haz tu lógica de inicio de sesión aquí
// valida la contraseña, etc.
// si el inicio de sesión es exitoso
$session->set('is_logged_in', true);
$session->set('user', $user);
// cualquier vez que escribas en la sesión, debes confirmarla deliberadamente.
$session->commit();
});
// Esta verificación podría estar en la lógica de la página restringida, o envuelta con middleware.
Flight::route('/some-restricted-page', function() {
$session = Flight::session();
if(!$session->get('is_logged_in')) {
Flight::redirect('/login');
}
// haz tu lógica de página restringida aquí
});
// la versión con middleware
Flight::route('/some-restricted-page', function() {
// lógica de página regular
})->addMiddleware(function() {
$session = Flight::session();
if(!$session->get('is_logged_in')) {
Flight::redirect('/login');
}
});
Ejemplo Más Complejo
Aquí hay un ejemplo más complejo de cómo podrías usar esto.
use Ghostff\Session\Session;
require 'vendor/autoload.php';
$app = Flight::app();
// establece una ruta personalizada a tu archivo de configuración de sesión como el primer argumento
// o dale el arreglo personalizado
$app->register('session', Session::class, [
[
// si quieres almacenar tus datos de sesión en una base de datos (bueno si quieres algo como, "cerrar sesión en todos los dispositivos" funcionalidad)
Session::CONFIG_DRIVER => Ghostff\Session\Drivers\MySql::class,
Session::CONFIG_ENCRYPT_DATA => true,
Session::CONFIG_SALT_KEY => hash('sha256', 'my-super-S3CR3T-salt'), // por favor cambia esto a algo más
Session::CONFIG_AUTO_COMMIT => true, // solo haz esto si es necesario y/o es difícil de confirmar() tu sesión.
// adicionalmente podrías hacer Flight::after('start', function() { Flight::session()->commit(); });
Session::CONFIG_MYSQL_DS => [
'driver' => 'mysql', # Controlador de base de datos para PDO dns ej(mysql:host=...;dbname=...)
'host' => '127.0.0.1', # Host de la base de datos
'db_name' => 'my_app_database', # Nombre de la base de datos
'db_table' => 'sessions', # Tabla de la base de datos
'db_user' => 'root', # Nombre de usuario de la base de datos
'db_pass' => '', # Contraseña de la base de datos
'persistent_conn'=> false, # Evita el costo de establecer una nueva conexión cada vez que un script necesita hablar con una base de datos, lo que resulta en una aplicación web más rápida. ENCUENTRA EL LADO NEGATIVO TÚ MISMO
]
]
]);
¡Ayuda! ¡Mis Datos de Sesión No Se Están Manteniendo!
¿Estás estableciendo tus datos de sesión y no se mantienen entre solicitudes? Quizás olvidaste confirmar tus datos de sesión. Puedes hacer esto llamando a $session->commit()
después de haber establecido tus datos de sesión.
Flight::route('POST /login', function() {
$session = Flight::session();
// haz tu lógica de inicio de sesión aquí
// valida la contraseña, etc.
// si el inicio de sesión es exitoso
$session->set('is_logged_in', true);
$session->set('user', $user);
// cualquier vez que escribas en la sesión, debes confirmarla deliberadamente.
$session->commit();
});
La otra forma de manejar esto es cuando configuras tu servicio de sesión, debes establecer auto_commit
en true
en tu configuración. Esto confirmará automáticamente tus datos de sesión después de cada solicitud.
$app->register('session', Session::class, [ 'path/to/session_config.php', bin2hex(random_bytes(32)) ], function(Session $session) {
$session->updateConfiguration([
Session::CONFIG_AUTO_COMMIT => true,
]);
}
);
Adicionalmente, podrías hacer Flight::after('start', function() { Flight::session()->commit(); });
para confirmar tus datos de sesión después de cada solicitud.
Documentación
Visita el Github Readme para la documentación completa. Las opciones de configuración están bien documentadas en el archivo default_config.php mismo. El código es simple de entender si quieres explorar este paquete tú mismo.
Awesome-plugins/async
Async
Async es un paquete pequeño para el framework Flight que te permite ejecutar tus aplicaciones Flight dentro de servidores y entornos asíncronos como Swoole, AdapterMan, ReactPHP, Amp, RoadRunner, Workerman, etc. De fábrica incluye adaptadores para Swoole y AdapterMan.
El objetivo: desarrollar y depurar con PHP-FPM (o el servidor integrado) y cambiar a Swoole (u otro controlador asíncrono) para producción con cambios mínimos.
Requisitos
- PHP 7.4 o superior
- Framework Flight 3.16.1 o superior
- Extensión Swoole
Instalación
Instala vía composer:
composer require flightphp/async
Si planeas ejecutar con Swoole, instala la extensión:
# usando pecl
pecl install swoole
# o openswoole
pecl install openswoole
# o con un administrador de paquetes (ejemplo Debian/Ubuntu)
sudo apt-get install php-swoole
Ejemplo rápido de Swoole
A continuación se muestra una configuración mínima que ilustra cómo soportar tanto PHP-FPM (o servidor integrado) como Swoole utilizando el mismo código base.
Archivos que necesitarás en tu proyecto:
- index.php
- swoole_server.php
- SwooleServerDriver.php
index.php
Este archivo es un simple interruptor que fuerza a la aplicación a ejecutarse en modo PHP para desarrollo.
// index.php
<?php
define('NOT_SWOOLE', true);
include 'swoole_server.php';
swoole_server.php
Este archivo inicializa tu aplicación Flight y comenzará el controlador Swoole cuando NOT_SWOOLE no esté definido.
// swoole_server.php
<?php
require_once __DIR__ . '/vendor/autoload.php';
$app = Flight::app();
$app->route('/', function() use ($app) {
$app->json(['hello' => 'world']);
});
if (!defined('NOT_SWOOLE')) {
// Require la clase SwooleServerDriver cuando se ejecute en modo Swoole.
require_once __DIR__ . '/SwooleServerDriver.php';
Swoole\Runtime::enableCoroutine();
$Swoole_Server = new SwooleServerDriver('127.0.0.1', 9501, $app);
$Swoole_Server->start();
} else {
$app->start();
}
SwooleServerDriver.php
Un controlador conciso que muestra cómo conectar solicitudes Swoole a Flight utilizando el AsyncBridge y los adaptadores de Swoole.
// SwooleServerDriver.php
<?php
use flight\adapter\SwooleAsyncRequest;
use flight\adapter\SwooleAsyncResponse;
use flight\AsyncBridge;
use flight\Engine;
use Swoole\HTTP\Server as SwooleServer;
use Swoole\HTTP\Request as SwooleRequest;
use Swoole\HTTP\Response as SwooleResponse;
class SwooleServerDriver {
protected $Swoole;
protected $app;
public function __construct(string $host, int $port, Engine $app) {
$this->Swoole = new SwooleServer($host, $port);
$this->app = $app;
$this->setDefault();
$this->bindWorkerEvents();
$this->bindHttpEvent();
}
protected function setDefault() {
$this->Swoole->set([
'daemonize' => false,
'dispatch_mode' => 1,
'max_request' => 8000,
'open_tcp_nodelay' => true,
'reload_async' => true,
'max_wait_time' => 60,
'enable_reuse_port' => true,
'enable_coroutine' => true,
'http_compression' => false,
'enable_static_handler' => true,
'document_root' => __DIR__,
'static_handler_locations' => ['/css', '/js', '/images', '/.well-known'],
'buffer_output_size' => 4 * 1024 * 1024,
'worker_num' => 4,
]);
$app = $this->app;
$app->map('stop', function (?int $code = null) use ($app) {
if ($code !== null) {
$app->response()->status($code);
}
});
}
protected function bindHttpEvent() {
$app = $this->app;
$AsyncBridge = new AsyncBridge($app);
$this->Swoole->on('Start', function(SwooleServer $server) {
echo "Swoole http server is started at http://127.0.0.1:9501\n";
});
$this->Swoole->on('Request', function (SwooleRequest $request, SwooleResponse $response) use ($AsyncBridge) {
$SwooleAsyncRequest = new SwooleAsyncRequest($request);
$SwooleAsyncResponse = new SwooleAsyncResponse($response);
$AsyncBridge->processRequest($SwooleAsyncRequest, $SwooleAsyncResponse);
$response->end();
gc_collect_cycles();
});
}
protected function bindWorkerEvents() {
$createPools = function() {
// create worker-specific connection pools here
};
$closePools = function() {
// close pools / cleanup here
};
$this->Swoole->on('WorkerStart', $createPools);
$this->Swoole->on('WorkerStop', $closePools);
$this->Swoole->on('WorkerError', $closePools);
}
public function start() {
$this->Swoole->start();
}
}
Ejecutando el servidor
- Desarrollo (servidor integrado de PHP / PHP-FPM):
- php -S localhost:8000 (o agrega -t public/ si tu index está en public/)
- Producción (Swoole):
- php swoole_server.php
Consejo: Para producción, usa un proxy inverso (Nginx) delante de Swoole para manejar TLS, archivos estáticos y balanceo de carga.
Notas de configuración
El controlador Swoole expone varias opciones de configuración:
- worker_num: número de procesos de worker
- max_request: solicitudes por worker antes del reinicio
- enable_coroutine: usar corutinas para concurrencia
- buffer_output_size: tamaño del búfer de salida
Ajusta estos para adaptarlos a los recursos de tu host y patrones de tráfico.
Manejo de errores
AsyncBridge traduce los errores de Flight en respuestas HTTP adecuadas. También puedes agregar manejo de errores a nivel de ruta:
$app->route('/*', function() use ($app) {
try {
// lógica de la ruta
} catch (Exception $e) {
$app->response()->status(500);
$app->json(['error' => $e->getMessage()]);
}
});
AdapterMan y otros entornos
AdapterMan está soportado como un adaptador de entorno alternativo. El paquete está diseñado para ser adaptable — agregar o usar otros adaptadores generalmente sigue el mismo patrón: convertir la solicitud/respuesta del servidor en la solicitud/respuesta de Flight a través del AsyncBridge y los adaptadores específicos del entorno.
Awesome-plugins/migrations
Migraciones
Una migración para tu proyecto es el seguimiento de todos los cambios de base de datos involucrados en tu proyecto. byjg/php-migration es una biblioteca central muy útil para comenzar.
Instalación
Biblioteca PHP
Si deseas usar solo la Biblioteca PHP en tu proyecto:
composer require "byjg/migration"
Interfaz de Línea de Comando
La interfaz de línea de comando es independiente y no requiere que la instales con tu proyecto.
Puedes instalarlo globalmente y crear un enlace simbólico.
composer require "byjg/migration-cli"
Por favor visita byjg/migration-cli para obtener más información sobre Migration CLI.
Bases de datos soportadas
Base de datos | Controlador | Cadena de conexión |
---|---|---|
Sqlite | pdo_sqlite | sqlite:///path/to/file |
MySql/MariaDb | pdo_mysql | mysql://username:password@hostname:port/database |
Postgres | pdo_pgsql | pgsql://username:password@hostname:port/database |
Sql Server | pdo_dblib, pdo_sysbase Linux | dblib://username:password@hostname:port/database |
Sql Server | pdo_sqlsrv Windows | sqlsrv://username:password@hostname:port/database |
¿Cómo funciona?
La Migración de Base de Datos utiliza SQL PURO para gestionar la versión de la base de datos. Para que funcione, necesitas:
- Crear los Scripts SQL
- Gestionar usando la Línea de Comando o la API.
Los Scripts SQL
Los scripts se dividen en tres conjuntos de scripts:
- El script BASE contiene TODOS los comandos SQL para crear una nueva base de datos;
- Los scripts UP contienen todos los comandos de migración SQL para "subir" la versión de la base de datos;
- Los scripts DOWN contienen todos los comandos de migración SQL para "bajar" o revertir la versión de la base de datos;
El directorio de scripts es:
<root dir>
|
+-- base.sql
|
+-- /migrations
|
+-- /up
|
+-- 00001.sql
+-- 00002.sql
+-- /down
|
+-- 00000.sql
+-- 00001.sql
- "base.sql" es el script base
- La carpeta "up" contiene los scripts para migrar la versión hacia arriba. Por ejemplo: 00002.sql es el script para mover la base de datos de la versión '1' a '2'.
- La carpeta "down" contiene los scripts para migrar la versión hacia abajo. Por ejemplo: 00001.sql es el script para mover la base de datos de la versión '2' a '1'. La carpeta "down" es opcional.
Entorno de Desarrollo Múltiple
Si trabajas con múltiples desarrolladores y múltiples ramas, es difícil determinar cuál es el siguiente número.
En ese caso, tienes el sufijo "-dev" después del número de versión.
Veamos el escenario:
- El desarrollador 1 crea una rama y la versión más reciente es, por ejemplo, 42.
- El desarrollador 2 crea una rama al mismo tiempo y tiene el mismo número de versión de base de datos.
En ambos casos, los desarrolladores crearán un archivo llamado 43-dev.sql. Ambos desarrolladores migrarán HACIA ARRIBA y HACIA ABAJO sin problemas y tu versión local será 43.
Pero el desarrollador 1 fusionó tus cambios y creó una versión final 43.sql (git mv 43-dev.sql 43.sql
). Si el desarrollador 2 actualiza su rama local, tendrá un archivo 43.sql (del dev 1) y su archivo 43-dev.sql. Si intenta migrar HACIA ARRIBA o HACIA ABAJO, el script de migración fallará y le alertará que hay DOS versiones 43. En ese caso, el desarrollador 2 tendrá que actualizar su archivo a 44-dev.sql y continuar trabajando hasta fusionar tus cambios y generar una versión final.
Usando la API PHP e integrándola en tus proyectos
El uso básico es
- Crear una conexión con un objeto ConnectionManagement. Para más información, consulta el componente "byjg/anydataset".
- Crear un objeto de Migración con esta conexión y la carpeta donde se encuentran los scripts SQL.
- Usar el comando apropiado para "resetear", "subir" o "bajar" los scripts de migración.
Veamos un ejemplo:
<?php
// Crear la URI de conexión
// Ver más: https://github.com/byjg/anydataset#connection-based-on-uri
$connectionUri = new \ByJG\Util\Uri('mysql://migrateuser:migratepwd@localhost/migratedatabase');
// Registrar la base de datos o bases de datos que pueden manejar esa URI:
\ByJG\DbMigration\Migration::registerDatabase(\ByJG\DbMigration\Database\MySqlDatabase::class);
// Crear la instancia de Migración
$migration = new \ByJG\DbMigration\Migration($connectionUri, '.');
// Agregar una función de progreso de devolución de llamada para recibir información de la ejecución
$migration->addCallbackProgress(function ($action, $currentVersion, $fileInfo) {
echo "$action, $currentVersion, ${fileInfo['description']}\n";
});
// Restaurar la base de datos usando el script "base.sql"
// y ejecutar TODOS los scripts existentes para subir la versión de la base de datos a la última versión
$migration->reset();
// Ejecutar TODOS los scripts existentes para subir o bajar la versión de la base de datos
// desde la versión actual hasta el número $version;
// Si el número de versión no está especificado, migrar hasta la última versión de la base de datos
$migration->update($version = null);
El objeto de Migración controla la versión de la base de datos.
Creando un control de versión en tu proyecto
<?php
// Registrar la base de datos o bases de datos que pueden manejar esa URI:
\ByJG\DbMigration\Migration::registerDatabase(\ByJG\DbMigration\Database\MySqlDatabase::class);
// Crear la instancia de Migración
$migration = new \ByJG\DbMigration\Migration($connectionUri, '.');
// Este comando creará la tabla de versiones en tu base de datos
$migration->createVersion();
Obteniendo la versión actual
<?php
$migration->getCurrentVersion();
Agregar Callback para controlar el progreso
<?php
$migration->addCallbackProgress(function ($command, $version, $fileInfo) {
echo "Ejecutando Comando: $command en la versión $version - ${fileInfo['description']}, ${fileInfo['exists']}, ${fileInfo['file']}, ${fileInfo['checksum']}\n";
});
Obteniendo la instancia del controlador de base de datos
<?php
$migration->getDbDriver();
Para usarlo, por favor visita: https://github.com/byjg/anydataset-db
Evitando Migraciones Parciales (no disponible para MySQL)
Una migración parcial es cuando el script de migración se interrumpe en medio del proceso debido a un error o una interrupción manual.
La tabla de migración tendrá el estado partial up
o partial down
y debe ser corregido manualmente antes de poder migrar nuevamente.
Para evitar esta situación, puedes especificar que la migración se ejecute en un contexto transaccional.
Si el script de migración falla, la transacción se revertirá y la tabla de migración se marcará como complete
y
la versión será la versión anterior inmediata antes del script que causó el error.
Para habilitar esta función, debes llamar al método withTransactionEnabled
pasando true
como parámetro:
<?php
$migration->withTransactionEnabled(true);
NOTA: Esta característica no está disponible para MySQL, ya que no admite comandos DDL dentro de una transacción. Si utilizas este método con MySQL, la Migración lo ignorará en silencio. Más info: https://dev.mysql.com/doc/refman/8.0/en/cannot-roll-back.html
Consejos sobre cómo escribir migraciones SQL para Postgres
Al crear triggers y funciones SQL
-- HACER
CREATE FUNCTION emp_stamp() RETURNS trigger AS $emp_stamp$
BEGIN
-- Comprobar que empname y salary están dados
IF NEW.empname IS NULL THEN
RAISE EXCEPTION 'empname no puede ser nulo'; -- no importa si estos comentarios están vacíos o no
END IF; --
IF NEW.salary IS NULL THEN
RAISE EXCEPTION '% no puede tener salary nulo', NEW.empname; --
END IF; --
-- ¿Quién trabaja para nosotros cuando tienen que pagarlo?
IF NEW.salary < 0 THEN
RAISE EXCEPTION '% no puede tener un salary negativo', NEW.empname; --
END IF; --
-- Recuerda quién cambió la nómina y cuándo
NEW.last_date := current_timestamp; --
NEW.last_user := current_user; --
RETURN NEW; --
END; --
$emp_stamp$ LANGUAGE plpgsql;
-- NO HACER
CREATE FUNCTION emp_stamp() RETURNS trigger AS $emp_stamp$
BEGIN
-- Comprobar que empname y salary están dados
IF NEW.empname IS NULL THEN
RAISE EXCEPTION 'empname no puede ser nulo';
END IF;
IF NEW.salary IS NULL THEN
RAISE EXCEPTION '% no puede tener salary nulo', NEW.empname;
END IF;
-- ¿Quién trabaja para nosotros cuando tienen que pagarlo?
IF NEW.salary < 0 THEN
RAISE EXCEPTION '% no puede tener un salary negativo', NEW.empname;
END IF;
-- Recuerda quién cambió la nómina y cuándo
NEW.last_date := current_timestamp;
NEW.last_user := current_user;
RETURN NEW;
END;
$emp_stamp$ LANGUAGE plpgsql;
Dado que la capa de abstracción de base de datos PDO
no puede ejecutar lotes de declaraciones SQL, al leer un archivo de migración, byjg/migration
tiene que dividir todo el contenido del archivo SQL en los puntos y comas, y ejecutar las declaraciones una por una. Sin embargo, hay un tipo de declaración que puede tener múltiples puntos y comas en su interior: funciones.
Con el fin de poder analizar correctamente las funciones, byjg/migration
2.1.0 comenzó a dividir los archivos de migración en la secuencia de punto y coma + EOL
en lugar de solo el punto y coma. De esta manera, si agregas un comentario vacío después de cada punto y coma interno de una definición de función, byjg/migration
podrá analizarlo.
Desafortunadamente, si olvidas agregar alguno de estos comentarios, la biblioteca dividirá la declaración CREATE FUNCTION
en múltiples partes y la migración fallará.
Evitar el carácter de dos puntos (:
)
-- HACER
CREATE TABLE bookings (
booking_id UUID PRIMARY KEY,
booked_at TIMESTAMPTZ NOT NULL CHECK (CAST(booked_at AS DATE) <= check_in),
check_in DATE NOT NULL
);
-- NO HACER
CREATE TABLE bookings (
booking_id UUID PRIMARY KEY,
booked_at TIMESTAMPTZ NOT NULL CHECK (booked_at::DATE <= check_in),
check_in DATE NOT NULL
);
Dado que PDO
utiliza el carácter de dos puntos para prefijar parámetros nombrados en declaraciones preparadas, su uso puede causar problemas en otros contextos.
Por ejemplo, las declaraciones de PostgreSQL pueden usar ::
para convertir valores entre tipos. Por otro lado, PDO
leerá esto como un parámetro nombrado inválido en un contexto inválido y fallará cuando intente ejecutarlo.
La única forma de solucionar esta inconsistencia es evitar los dos puntos por completo (en este caso, PostgreSQL también tiene una sintaxis alternativa: CAST(value AS type)
).
Usar un editor SQL
Finalmente, escribir migraciones SQL manuales puede ser agotador, pero es significativamente más fácil si usas un editor capaz de entender la sintaxis SQL, proporcionando autocompletado, introspección de tu esquema de base de datos actual y/o autoformateo de tu código.
Manejo de diferentes migraciones dentro de un esquema
Si necesitas crear diferentes scripts de migración y versiones dentro del mismo esquema, es posible, pero es demasiado arriesgado y no lo recomiendo en absoluto.
Para hacerlo, necesitas crear diferentes "tablas de migración" pasando el parámetro al constructor.
<?php
$migration = new \ByJG\DbMigration\Migration("db:/uri", "/path", true, "NEW_MIGRATION_TABLE_NAME");
Por razones de seguridad, esta función no está disponible en la línea de comandos, pero puedes usar la variable de entorno MIGRATION_VERSION
para almacenar el nombre.
Recomendamos encarecidamente no utilizar esta función. La recomendación es una migración para un esquema.
Ejecución de pruebas unitarias
Las pruebas unitarias básicas se pueden ejecutar con:
vendor/bin/phpunit
Ejecución de pruebas de base de datos
Ejecutar pruebas de integración requiere que las bases de datos estén activas y en funcionamiento. Proporcionamos un docker-compose.yml
básico que puedes usar para iniciar las bases de datos para pruebas.
Ejecutando las bases de datos
docker-compose up -d postgres mysql mssql
Ejecutar las pruebas
vendor/bin/phpunit
vendor/bin/phpunit tests/SqliteDatabase*
vendor/bin/phpunit tests/MysqlDatabase*
vendor/bin/phpunit tests/PostgresDatabase*
vendor/bin/phpunit tests/SqlServerDblibDatabase*
vendor/bin/phpunit tests/SqlServerSqlsrvDatabase*
Opcionalmente puedes establecer el host y la contraseña utilizados por las pruebas unitarias
export MYSQL_TEST_HOST=localhost # predeterminado a localhost
export MYSQL_PASSWORD=newpassword # usa '.' si quieres tener una contraseña nula
export PSQL_TEST_HOST=localhost # predeterminado a localhost
export PSQL_PASSWORD=newpassword # usa '.' si quieres tener una contraseña nula
export MSSQL_TEST_HOST=localhost # predeterminado a localhost
export MSSQL_PASSWORD=Pa55word
export SQLITE_TEST_HOST=/tmp/test.db # predeterminado a /tmp/test.db
Awesome-plugins/session
FlightPHP Sesión - Manejador de Sesiones Ligero Basado en Archivos
Esto es un plugin ligero, basado en archivos, para manejar sesiones en el Flight PHP Framework. Proporciona una solución simple pero potente para gestionar sesiones, con características como lecturas de sesiones no bloqueantes, cifrado opcional, funcionalidad de auto-commit y un modo de prueba para desarrollo. Los datos de sesión se almacenan en archivos, lo que lo hace ideal para aplicaciones que no requieren una base de datos.
Si deseas usar una base de datos, consulta el plugin ghostff/session que incluye muchas de estas mismas características pero con un backend de base de datos.
Visita el repositorio de Github para el código fuente completo y detalles.
Instalación
Instala el plugin a través de Composer:
composer require flightphp/session
Uso Básico
Aquí hay un ejemplo simple de cómo usar el plugin flightphp/session
en tu aplicación Flight:
require 'vendor/autoload.php';
use flight\Session;
$app = Flight::app();
// Registra el servicio de sesión
$app->register('session', Session::class);
// Ejemplo de ruta con uso de sesión
Flight::route('/login', function() {
$session = Flight::session();
$session->set('user_id', 123);
$session->set('username', 'johndoe');
$session->set('is_admin', false);
echo $session->get('username'); // Esto imprime: johndoe
echo $session->get('preferences', 'default_theme'); // Esto imprime: default_theme
if ($session->get('user_id')) {
Flight::json(['message' => '¡El usuario está logueado!', 'user_id' => $session->get('user_id')]);
}
});
Flight::route('/logout', function() {
$session = Flight::session();
$session->clear(); // Limpia todos los datos de sesión
Flight::json(['message' => 'Cerrado de sesión exitoso']);
});
Flight::start();
Puntos Clave
- No Bloqueante: Usa
read_and_close
para iniciar la sesión por defecto, previniendo problemas de bloqueo de sesiones. - Auto-Commit: Habilitado por defecto, por lo que los cambios se guardan automáticamente al cerrar a menos que se desactive.
- Almacenamiento en Archivos: Las sesiones se almacenan en el directorio temporal del sistema bajo
/flight_sessions
por defecto.
Configuración
Puedes personalizar el manejador de sesiones pasando un arreglo de opciones al registrar:
// Sí, es un arreglo doble :)
$app->register('session', Session::class, [ [
'save_path' => '/custom/path/to/sessions', // Directorio para los archivos de sesión
'prefix' => 'myapp_', // Prefijo para los archivos de sesión
'encryption_key' => 'a-secure-32-byte-key-here', // Habilita el cifrado (se recomienda 32 bytes para AES-256-CBC)
'auto_commit' => false, // Desactiva el auto-commit para control manual
'start_session' => true, // Inicia la sesión automáticamente (por defecto: true)
'test_mode' => false, // Habilita el modo de prueba para desarrollo
'serialization' => 'json', // Método de serialización: 'json' (por defecto) o 'php' (heredado)
] ]);
Opciones de Configuración
Opción | Descripción | Valor por Defecto |
---|---|---|
save_path |
Directorio donde se almacenan los archivos de sesión | sys_get_temp_dir() . '/flight_sessions' |
prefix |
Prefijo para el archivo de sesión guardado | sess_ |
encryption_key |
Clave para el cifrado AES-256-CBC (opcional) | null (sin cifrado) |
auto_commit |
Guarda automáticamente los datos de sesión al cerrar | true |
start_session |
Inicia la sesión automáticamente | true |
test_mode |
Ejecuta en modo de prueba sin afectar las sesiones de PHP | false |
test_session_id |
ID de sesión personalizado para el modo de prueba (opcional) | Generado aleatoriamente si no se establece |
serialization |
Método de serialización: 'json' (por defecto, seguro) o 'php' (heredado, permite objetos) | 'json' |
Modos de Serialización
Por defecto, esta biblioteca usa serialización JSON para los datos de sesión, lo que es seguro y evita vulnerabilidades de inyección de objetos PHP. Si necesitas almacenar objetos PHP en la sesión (no recomendado para la mayoría de las aplicaciones), puedes optar por la serialización PHP heredada:
'serialization' => 'json'
(por defecto):- Solo se permiten arreglos y primitivos en los datos de sesión.
- Más seguro: inmune a la inyección de objetos PHP.
- Los archivos se prefijan con
J
(JSON plano) oF
(JSON cifrado).
'serialization' => 'php'
:- Permite almacenar objetos PHP (usa con precaución).
- Los archivos se prefijan con
P
(serialización PHP plana) oE
(serialización PHP cifrada).
Nota: Si usas serialización JSON, intentar almacenar un objeto lanzará una excepción.
Uso Avanzado
Commit Manual
Si desactivas el auto-commit, debes confirmar los cambios manualmente:
$app->register('session', Session::class, ['auto_commit' => false]);
Flight::route('/update', function() {
$session = Flight::session();
$session->set('key', 'value');
$session->commit(); // Guarda explícitamente los cambios
});
Seguridad de Sesión con Cifrado
Habilita el cifrado para datos sensibles:
$app->register('session', Session::class, [
'encryption_key' => 'your-32-byte-secret-key-here'
]);
Flight::route('/secure', function() {
$session = Flight::session();
$session->set('credit_card', '4111-1111-1111-1111'); // Se cifra automáticamente
echo $session->get('credit_card'); // Se descifra al recuperar
});
Regeneración de Sesión
Regenera el ID de sesión por seguridad (por ejemplo, después de iniciar sesión):
Flight::route('/post-login', function() {
$session = Flight::session();
$session->regenerate(); // Nuevo ID, mantiene los datos
// O
$session->regenerate(true); // Nuevo ID, elimina los datos antiguos
});
Ejemplo de Middleware
Protege rutas con autenticación basada en sesiones:
Flight::route('/admin', function() {
Flight::json(['message' => 'Bienvenido al panel de administración']);
})->addMiddleware(function() {
$session = Flight::session();
if (!$session->get('is_admin')) {
Flight::halt(403, 'Acceso denegado');
}
// Esto es solo un ejemplo simple de cómo usarlo en middleware. Para un ejemplo más detallado, consulta la documentación de [middleware](/learn/middleware).
});
Métodos
La clase Session
proporciona estos métodos:
set(string $key, $value)
: Almacena un valor en la sesión.get(string $key, $default = null)
: Recupera un valor, con un valor predeterminado opcional si la clave no existe.delete(string $key)
: Elimina una clave específica de la sesión.clear()
: Elimina todos los datos de sesión, pero mantiene el mismo nombre de archivo para la sesión.commit()
: Guarda los datos de sesión actuales en el sistema de archivos.id()
: Devuelve el ID de sesión actual.regenerate(bool $deleteOldFile = false)
: Regenera el ID de sesión, incluyendo la creación de un nuevo archivo de sesión, manteniendo todos los datos antiguos y el archivo antiguo permanece en el sistema. Si$deleteOldFile
estrue
, se elimina el archivo de sesión antiguo.destroy(string $id)
: Destruye una sesión por ID y elimina el archivo de sesión del sistema. Esto forma parte de laSessionHandlerInterface
y$id
es requerido. El uso típico sería$session->destroy($session->id())
.getAll()
: Devuelve todos los datos de la sesión actual.
Todos los métodos excepto get()
y id()
devuelven la instancia de Session
para encadenamiento.
¿Por Qué Usar Este Plugin?
- Ligero: Sin dependencias externas, solo archivos.
- No Bloqueante: Evita el bloqueo de sesiones con
read_and_close
por defecto. - Seguro: Soporta cifrado AES-256-CBC para datos sensibles.
- Flexible: Opciones de auto-commit, modo de prueba y control manual.
- Nativo de Flight: Construido específicamente para el framework Flight.
Detalles Técnicos
- Formato de Almacenamiento: Los archivos de sesión se prefijan con
sess_
y se almacenan en elsave_path
configurado. Prefijos de contenido de archivo:J
: JSON plano (por defecto, sin cifrado)F
: JSON cifrado (por defecto con cifrado)P
: Serialización PHP plana (heredada, sin cifrado)E
: Serialización PHP cifrada (heredada con cifrado)
- Cifrado: Usa AES-256-CBC con un IV aleatorio por escritura de sesión cuando se proporciona una
encryption_key
. El cifrado funciona para ambos modos de serialización JSON y PHP. - Serialización: JSON es el método por defecto y más seguro. La serialización PHP está disponible para usos heredados/avanzados, pero es menos segura.
- Recolección de Basura: Implementa
SessionHandlerInterface::gc()
de PHP para limpiar sesiones expiradas.
Contribuciones
¡Las contribuciones son bienvenidas! Bifurca el repositorio, realiza tus cambios y envía una solicitud de extracción. Reporta errores o sugiere características a través del rastreador de problemas de Github.
Licencia
Este plugin está licenciado bajo la Licencia MIT. Consulta el repositorio de Github para detalles.
Awesome-plugins/runway
Pista
Pista es una aplicación CLI que te ayuda a gestionar tus aplicaciones Flight. Puede generar controladores, mostrar todas las rutas y más. Está basado en la excelente biblioteca adhocore/php-cli.
Haz clic aquí para ver el código.
Instalación
Instala con composer.
composer require flightphp/runway
Configuración Básica
La primera vez que ejecutes Pista, te guiará a través de un proceso de configuración y creará un archivo de configuración .runway.json
en la raíz de tu proyecto. Este archivo contendrá algunas configuraciones necesarias para que Pista funcione correctamente.
Uso
Pista tiene varios comandos que puedes usar para gestionar tu aplicación Flight. Hay dos formas fáciles de usar Pista.
- Si estás usando el proyecto esqueleto, puedes ejecutar
php runway [comando]
desde la raíz de tu proyecto. - Si estás usando Pista como un paquete instalado a través de composer, puedes ejecutar
vendor/bin/runway [comando]
desde la raíz de tu proyecto.
Para cualquier comando, puedes agregar la bandera --help
para obtener más información sobre cómo usar el comando.
php runway routes --help
Aquí tienes algunos ejemplos:
Generar un Controlador
Basado en la configuración en tu archivo .runway.json
, la ubicación predeterminada generará un controlador para ti en el directorio app/controllers/
.
php runway make:controller MiControlador
Generar un Modelo de Active Record
Basado en la configuración en tu archivo .runway.json
, la ubicación predeterminada generará un modelo de Active Record para ti en el directorio app/records/
.
php runway make:record usuarios
Si por ejemplo tienes la tabla usuarios
con el siguiente esquema: id
, nombre
, correo
, creado_en
, actualizado_en
, se creará un archivo similar al siguiente en el archivo app/records/UserRecord.php
:
<?php
declare(strict_types=1);
namespace app\records;
/**
* Clase ActiveRecord para la tabla de usuarios.
* @link https://docs.flightphp.com/awesome-plugins/active-record
*
* @property int $id
* @property string $nombre
* @property string $correo
* @property string $creado_en
* @property string $actualizado_en
* // también puedes añadir relaciones aquí una vez las definas en el array $relations
* @property CompanyRecord $company Ejemplo de una relación
*/
class UserRecord extends \flight\ActiveRecord
{
/**
* @var array $relations Establece las relaciones para el modelo
* https://docs.flightphp.com/awesome-plugins/active-record#relationships
*/
protected array $relations = [];
/**
* Constructor
* @param mixed $conexionBaseDeDatos La conexión a la base de datos
*/
public function __construct($conexionBaseDeDatos)
{
parent::__construct($conexionBaseDeDatos, 'usuarios');
}
}
Mostrar Todas las Rutas
Esto mostrará todas las rutas que están actualmente registradas en Flight.
php runway routes
Si deseas ver solo rutas específicas, puedes agregar una bandera para filtrar las rutas.
# Mostrar solo rutas GET
php runway routes --get
# Mostrar solo rutas POST
php runway routes --post
# etc.
Personalización de Pista
Si estás creando un paquete para Flight, o deseas añadir tus propios comandos personalizados a tu proyecto, puedes hacerlo creando un directorio src/commands/
, flight/commands/
, app/commands/
o commands/
para tu proyecto/paquete.
Para crear un comando, simplemente extiende la clase AbstractBaseCommand
e implementa como mínimo un método __construct
y un método execute
.
<?php
declare(strict_types=1);
namespace flight\commands;
class ExampleCommand extends AbstractBaseCommand
{
/**
* Constructor
*
* @param array<string,mixed> $config JSON config de .runway-config.json
*/
public function __construct(array $config)
{
parent::__construct('make:example', 'Crear un ejemplo para la documentación', $config);
$this->argument('<gif-divertido>', 'El nombre del gif divertido');
}
/**
* Ejecuta la función
*
* @return void
*/
public function execute(string $controlador)
{
$io = $this->app()->io();
$io->info('Creando ejemplo...');
// Haz algo aquí
$io->ok('¡Ejemplo creado!');
}
}
Consulta la Documentación de adhocore/php-cli para obtener más información sobre cómo crear tus propios comandos personalizados en tu aplicación Flight.
Awesome-plugins/tracy_extensions
Tracy Flight Panel Extensions
Este es un conjunto de extensiones para hacer que trabajar con Flight sea un poco más completo.
- Flight - Analiza todas las variables de Flight.
- Database - Analiza todas las consultas que se han ejecutado en la página (si inicias correctamente la conexión a la base de datos)
- Request - Analiza todas las variables
$_SERVER
y examina todos los payloads globales ($_GET
,$_POST
,$_FILES
) - Session - Analiza todas las variables
$_SESSION
si las sesiones están activas.
Este es el Panel
¡Y cada panel muestra información muy útil sobre tu aplicación!
Haz clic aquí para ver el código.
Installation
Ejecuta composer require flightphp/tracy-extensions --dev
y estarás listo para empezar!
Configuration
Hay muy poca configuración que necesites hacer para comenzar. Deberás iniciar el depurador de Tracy antes de usar esto https://tracy.nette.org/en/guide:
<?php
use Tracy\Debugger;
use flight\debug\tracy\TracyExtensionLoader;
// bootstrap code
require __DIR__ . '/vendor/autoload.php';
Debugger::enable();
// Es posible que necesites especificar tu entorno con Debugger::enable(Debugger::DEVELOPMENT)
// si usas conexiones a la base de datos en tu app, hay un
// wrapper PDO requerido para usar SOLO EN DESARROLLO (¡no en producción por favor!)
// Tiene los mismos parámetros que una conexión PDO regular
$pdo = new PdoQueryCapture('sqlite:test.db', 'user', 'pass');
// o si lo adjuntas al framework Flight
Flight::register('db', PdoQueryCapture::class, ['sqlite:test.db', 'user', 'pass']);
// ahora cada vez que hagas una consulta capturará el tiempo, la consulta y los parámetros
// Esto conecta los puntos
if(Debugger::$showBar === true) {
// Esto necesita ser false o Tracy no podrá renderizar :(
Flight::set('flight.content_length', false);
new TracyExtensionLoader(Flight::app());
}
// more code
Flight::start();
Additional Configuration
Session Data
Si tienes un manejador de sesiones personalizado (como ghostff/session), puedes pasar cualquier array de datos de sesión a Tracy y lo mostrará automáticamente por ti. Lo pasas con la clave session_data
en el segundo parámetro del constructor de TracyExtensionLoader
.
use Ghostff\Session\Session;
// o use flight\Session;
require 'vendor/autoload.php';
$app = Flight::app();
$app->register('session', Session::class);
if(Debugger::$showBar === true) {
// Esto necesita ser false o Tracy no podrá renderizar :(
Flight::set('flight.content_length', false);
new TracyExtensionLoader(Flight::app(), [ 'session_data' => Flight::session()->getAll() ]);
}
// routes and other things...
Flight::start();
Latte
Se requiere PHP 8.1+ para esta sección.
Si tienes Latte instalado en tu proyecto, Tracy tiene una integración nativa con Latte para analizar tus plantillas. Simplemente registra la extensión con tu instancia de Latte.
require 'vendor/autoload.php';
$app = Flight::app();
$app->map('render', function($template, $data, $block = null) {
$latte = new Latte\Engine;
// other configurations...
// solo agrega la extensión si la Barra de Depuración de Tracy está habilitada
if(Debugger::$showBar === true) {
// aquí es donde agregas el Panel de Latte a Tracy
$latte->addExtension(new Latte\Bridges\Tracy\TracyExtension);
}
$latte->render($template, $data, $block);
});
Awesome-plugins/apm
Documentación de APM de FlightPHP
¡Bienvenido a FlightPHP APM—el entrenador personal de rendimiento de tu aplicación! Esta guía es tu mapa de ruta para configurar, usar y dominar el Monitoreo de Rendimiento de Aplicaciones (APM) con FlightPHP. Ya sea que estés cazando solicitudes lentas o solo quieras geekearte con gráficos de latencia, te tenemos cubierto. ¡Hagamos que tu app sea más rápida, tus usuarios más felices y tus sesiones de depuración una brisa!
Mira una demo del dashboard para el sitio de Flight Docs.
Por qué APM importa
Imagina esto: tu app es un restaurante concurrido. Sin una forma de rastrear cuánto tiempo tardan los pedidos o dónde se atasca la cocina, estás adivinando por qué los clientes se van gruñendo. APM es tu sous-chef—vigila cada paso, desde las solicitudes entrantes hasta las consultas de base de datos, y marca cualquier cosa que te esté ralentizando. Las páginas lentas pierden usuarios (¡los estudios dicen que el 53% rebota si un sitio tarda más de 3 segundos en cargar!), y APM te ayuda a capturar esos problemas antes de que duelan. Es una paz de mente proactiva—menos momentos de “¿por qué esto está roto?”, más victorias de “¡mira qué fluido corre esto!”.
Instalación
Comienza con Composer:
composer require flightphp/apm
Necesitarás:
- PHP 7.4+: Nos mantiene compatibles con distribuciones Linux LTS mientras soporta PHP moderno.
- FlightPHP Core v3.15+: El framework ligero que estamos impulsando.
Bases de datos compatibles
FlightPHP APM actualmente soporta las siguientes bases de datos para almacenar métricas:
- SQLite3: Simple, basada en archivos, y genial para desarrollo local o apps pequeñas. Opción predeterminada en la mayoría de las configuraciones.
- MySQL/MariaDB: Ideal para proyectos más grandes o entornos de producción donde necesitas almacenamiento robusto y escalable.
Puedes elegir el tipo de base de datos durante el paso de configuración (ver abajo). Asegúrate de que tu entorno PHP tenga las extensiones necesarias instaladas (p.ej., pdo_sqlite
o pdo_mysql
).
Comenzando
Aquí tienes tu guía paso a paso hacia la genialidad de APM:
1. Registrar el APM
Coloca esto en tu index.php
o un archivo services.php
para comenzar el rastreo:
use flight\apm\logger\LoggerFactory;
use flight\Apm;
$ApmLogger = LoggerFactory::create(__DIR__ . '/../../.runway-config.json');
$Apm = new Apm($ApmLogger);
$Apm->bindEventsToFlightInstance($app);
// Si estás agregando una conexión de base de datos
// Debe ser PdoWrapper o PdoQueryCapture de Tracy Extensions
$pdo = new PdoWrapper('mysql:host=localhost;dbname=example', 'user', 'pass', null, true); // <-- True requerido para habilitar el rastreo en el APM.
$Apm->addPdoConnection($pdo);
¿Qué está pasando aquí?
LoggerFactory::create()
agarra tu configuración (más sobre eso pronto) y configura un logger—SQLite por defecto.Apm
es la estrella—escucha los eventos de Flight (solicitudes, rutas, errores, etc.) y recolecta métricas.bindEventsToFlightInstance($app)
lo une todo a tu app de Flight.
Consejo Pro: Muestreo Si tu app está ocupada, registrar cada solicitud podría sobrecargar las cosas. Usa una tasa de muestreo (0.0 a 1.0):
$Apm = new Apm($ApmLogger, 0.1); // Registra el 10% de las solicitudes
Esto mantiene el rendimiento ágil mientras aún te da datos sólidos.
2. Configurarlo
Ejecuta esto para crear tu .runway-config.json
:
php vendor/bin/runway apm:init
¿Qué hace esto?
- Lanza un asistente que pregunta de dónde vienen las métricas crudas (fuente) y dónde va la data procesada (destino).
- Predeterminado es SQLite—p.ej.,
sqlite:/tmp/apm_metrics.sqlite
para la fuente, otra para el destino. - Terminarás con una config como:
{ "apm": { "source_type": "sqlite", "source_db_dsn": "sqlite:/tmp/apm_metrics.sqlite", "storage_type": "sqlite", "dest_db_dsn": "sqlite:/tmp/apm_metrics_processed.sqlite" } }
Este proceso también preguntará si quieres ejecutar las migraciones para esta configuración. Si estás configurándolo por primera vez, la respuesta es sí.
¿Por qué dos ubicaciones? Las métricas crudas se acumulan rápido (piensa en logs sin filtrar). El worker las procesa en un destino estructurado para el dashboard. ¡Mantiene las cosas ordenadas!
3. Procesar métricas con el Worker
El worker convierte las métricas crudas en data lista para el dashboard. Ejecútalo una vez:
php vendor/bin/runway apm:worker
¿Qué está haciendo?
- Lee de tu fuente (p.ej.,
apm_metrics.sqlite
). - Procesa hasta 100 métricas (tamaño de lote predeterminado) en tu destino.
- Se detiene cuando termina o si no quedan métricas.
Mantenerlo ejecutándose Para apps en vivo, querrás procesamiento continuo. Aquí tienes tus opciones:
-
Modo Daemon:
php vendor/bin/runway apm:worker --daemon
Se ejecuta para siempre, procesando métricas a medida que llegan. Genial para dev o configuraciones pequeñas.
-
Crontab: Agrega esto a tu crontab (
crontab -e
):* * * * * php /path/to/project/vendor/bin/runway apm:worker
Se dispara cada minuto—perfecto para producción.
-
Tmux/Screen: Inicia una sesión desmontable:
tmux new -s apm-worker php vendor/bin/runway apm:worker --daemon # Ctrl+B, luego D para desmontar; `tmux attach -t apm-worker` para reconectar
Lo mantiene ejecutándose incluso si cierras sesión.
-
Ajustes personalizados:
php vendor/bin/runway apm:worker --batch_size 50 --max_messages 1000 --timeout 300
--batch_size 50
: Procesa 50 métricas a la vez.--max_messages 1000
: Se detiene después de 1000 métricas.--timeout 300
: Sale después de 5 minutos.
¿Por qué molestarse? Sin el worker, tu dashboard está vacío. Es el puente entre logs crudos e insights accionables.
4. Lanzar el Dashboard
Ve los vitales de tu app:
php vendor/bin/runway apm:dashboard
¿Qué es esto?
- Inicia un servidor PHP en
http://localhost:8001/apm/dashboard
. - Muestra logs de solicitudes, rutas lentas, tasas de error y más.
Personalízalo:
php vendor/bin/runway apm:dashboard --host 0.0.0.0 --port 8080 --php-path=/usr/local/bin/php
--host 0.0.0.0
: Accesible desde cualquier IP (útil para visualización remota).--port 8080
: Usa un puerto diferente si 8001 está ocupado.--php-path
: Apunta a PHP si no está en tu PATH.
¡Abre la URL en tu navegador y explora!
Modo Producción
Para producción, podrías tener que probar unas técnicas para hacer que el dashboard funcione, ya que probablemente hay firewalls y otras medidas de seguridad en su lugar. Aquí hay unas opciones:
- Usa un Proxy Inverso: Configura Nginx o Apache para reenviar solicitudes al dashboard.
- Túnel SSH: Si puedes SSH al servidor, usa
ssh -L 8080:localhost:8001 youruser@yourserver
para tunelizar el dashboard a tu máquina local. - VPN: Si tu servidor está detrás de una VPN, conéctate a ella y accede al dashboard directamente.
- Configura Firewall: Abre el puerto 8001 para tu IP o la red del servidor. (o el puerto que hayas configurado).
- Configura Apache/Nginx: Si tienes un servidor web frente a tu aplicación, puedes configurarlo para un dominio o subdominio. Si haces esto, configurarás el document root a
/path/to/your/project/vendor/flightphp/apm/dashboard
.
¿Quieres un dashboard diferente?
¡Puedes construir tu propio dashboard si quieres! Mira el directorio vendor/flightphp/apm/src/apm/presenter para ideas sobre cómo presentar la data para tu propio dashboard.
Características del Dashboard
El dashboard es tu HQ de APM—aquí está lo que verás:
- Log de Solicitudes: Cada solicitud con timestamp, URL, código de respuesta y tiempo total. Haz clic en “Detalles” para middleware, consultas y errores.
- Solicitudes Más Lentas: Top 5 solicitudes que consumen tiempo (p.ej., “/api/heavy” en 2.5s).
- Rutas Más Lentas: Top 5 rutas por tiempo promedio—genial para detectar patrones.
- Tasa de Error: Porcentaje de solicitudes que fallan (p.ej., 2.3% 500s).
- Percentiles de Latencia: 95th (p95) y 99th (p99) tiempos de respuesta—conoce tus escenarios de peor caso.
- Gráfico de Código de Respuesta: Visualiza 200s, 404s, 500s a lo largo del tiempo.
- Consultas/Middleware Largas: Top 5 llamadas de base de datos lentas y capas de middleware.
- Cache Hit/Miss: Cuánto salva tu cache el día.
Extras:
- Filtra por “Última Hora”, “Último Día” o “Última Semana”.
- Cambia a modo oscuro para esas sesiones nocturnas.
Ejemplo:
Una solicitud a /users
podría mostrar:
- Tiempo Total: 150ms
- Middleware:
AuthMiddleware->handle
(50ms) - Consulta:
SELECT * FROM users
(80ms) - Cache: Hit en
user_list
(5ms)
Agregando Eventos Personalizados
Rastrea cualquier cosa—como una llamada API o proceso de pago:
use flight\apm\CustomEvent;
$app->eventDispatcher()->trigger('apm.custom', new CustomEvent('api_call', [
'endpoint' => 'https://api.example.com/users',
'response_time' => 0.25,
'status' => 200
]));
¿Dónde aparece? En los detalles de solicitud del dashboard bajo “Eventos Personalizados”—expandible con formato JSON bonito.
Caso de Uso:
$start = microtime(true);
$apiResponse = file_get_contents('https://api.example.com/data');
$app->eventDispatcher()->trigger('apm.custom', new CustomEvent('external_api', [
'url' => 'https://api.example.com/data',
'time' => microtime(true) - $start,
'success' => $apiResponse !== false
]));
¡Ahora verás si esa API está arrastrando tu app hacia abajo!
Monitoreo de Base de Datos
Rastrea consultas PDO así:
use flight\database\PdoWrapper;
$pdo = new PdoWrapper('sqlite:/path/to/db.sqlite', null, null, null, true); // <-- True requerido para habilitar el rastreo en el APM.
$Apm->addPdoConnection($pdo);
Lo que Obtienes:
- Texto de consulta (p.ej.,
SELECT * FROM users WHERE id = ?
) - Tiempo de ejecución (p.ej., 0.015s)
- Conteo de filas (p.ej., 42)
Atención:
- Opcional: Sáltate esto si no necesitas rastreo de DB.
- Solo PdoWrapper: PDO core no está enganchado aún—¡mantente atento!
- Advertencia de Rendimiento: Registrar cada consulta en un sitio pesado en DB puede ralentizar las cosas. Usa muestreo (
$Apm = new Apm($ApmLogger, 0.1)
) para aligerar la carga.
Salida de Ejemplo:
- Consulta:
SELECT name FROM products WHERE price > 100
- Tiempo: 0.023s
- Filas: 15
Opciones del Worker
Ajusta el worker a tu gusto:
--timeout 300
: Se detiene después de 5 minutos—bueno para pruebas.--max_messages 500
: Limita a 500 métricas—lo mantiene finito.--batch_size 200
: Procesa 200 a la vez—equilibra velocidad y memoria.--daemon
: Se ejecuta sin parar—ideal para monitoreo en vivo.
Ejemplo:
php vendor/bin/runway apm:worker --daemon --batch_size 100 --timeout 3600
Se ejecuta por una hora, procesando 100 métricas a la vez.
ID de Solicitud en la App
Cada solicitud tiene un ID de solicitud único para rastreo. Puedes usar este ID en tu app para correlacionar logs y métricas. Por instancia, puedes agregar el ID de solicitud a una página de error:
Flight::map('error', function($message) {
// Obtén el ID de solicitud del header de respuesta X-Flight-Request-Id
$requestId = Flight::response()->getHeader('X-Flight-Request-Id');
// Adicionalmente podrías obtenerlo de la variable de Flight
// Este método no funcionará bien en swoole u otras plataformas async.
// $requestId = Flight::get('apm.request_id');
echo "Error: $message (Request ID: $requestId)";
});
Actualizando
Si estás actualizando a una versión más nueva del APM, hay una posibilidad de que haya migraciones de base de datos que necesiten ejecutarse. Puedes hacerlo ejecutando el siguiente comando:
php vendor/bin/runway apm:migrate
Esto ejecutará cualquier migración que sea necesaria para actualizar el esquema de la base de datos a la versión más reciente.
Nota: Si tu base de datos de APM es grande en tamaño, estas migraciones pueden tardar algo de tiempo en ejecutarse. Podrías querer ejecutar este comando durante horas de bajo pico.
Purgando Datos Antiguos
Para mantener tu base de datos ordenada, puedes purgar datos antiguos. Esto es especialmente útil si estás ejecutando una app ocupada y quieres mantener el tamaño de la base de datos manejable. Puedes hacerlo ejecutando el siguiente comando:
php vendor/bin/runway apm:purge
Esto eliminará todos los datos más antiguos que 30 días de la base de datos. Puedes ajustar el número de días pasando un valor diferente a la opción --days
:
php vendor/bin/runway apm:purge --days 7
Esto eliminará todos los datos más antiguos que 7 días de la base de datos.
Solución de Problemas
¿Atascado? Prueba estos:
-
¿No hay datos en el Dashboard?
- ¿Está el worker ejecutándose? Verifica
ps aux | grep apm:worker
. - ¿Las rutas de config coinciden? Verifica que los DSNs en
.runway-config.json
apunten a archivos reales. - Ejecuta
php vendor/bin/runway apm:worker
manualmente para procesar métricas pendientes.
- ¿Está el worker ejecutándose? Verifica
-
¿Errores en el Worker?
- Mira tus archivos SQLite (p.ej.,
sqlite3 /tmp/apm_metrics.sqlite "SELECT * FROM apm_metrics_log LIMIT 5"
). - Verifica los logs de PHP para stack traces.
- Mira tus archivos SQLite (p.ej.,
-
¿El Dashboard no Inicia?
- ¿Puerto 8001 en uso? Usa
--port 8080
. - ¿PHP no encontrado? Usa
--php-path /usr/bin/php
. - ¿Firewall bloqueando? Abre el puerto o usa
--host localhost
.
- ¿Puerto 8001 en uso? Usa
-
¿Demasiado Lento?
- Baja la tasa de muestreo:
$Apm = new Apm($ApmLogger, 0.05)
(5%). - Reduce el tamaño de lote:
--batch_size 20
.
- Baja la tasa de muestreo:
-
¿No Rastrea Excepciones/Errores?
- Si tienes Tracy habilitado para tu proyecto, sobrescribirá el manejo de errores de Flight. Necesitarás deshabilitar Tracy y luego asegurarte de que
Flight::set('flight.handle_errors', true);
esté configurado.
- Si tienes Tracy habilitado para tu proyecto, sobrescribirá el manejo de errores de Flight. Necesitarás deshabilitar Tracy y luego asegurarte de que
-
¿No Rastrea Consultas de Base de Datos?
- Asegúrate de estar usando
PdoWrapper
para tus conexiones de base de datos. - Asegúrate de hacer el último argumento en el constructor
true
.
- Asegúrate de estar usando
Awesome-plugins/tracy
Tracy
Tracy es un increíble manejador de errores que se puede utilizar con Flight. Tiene una serie de paneles que pueden ayudarte a depurar tu aplicación. También es muy fácil de extender y agregar tus propios paneles. El equipo de Flight ha creado algunos paneles específicamente para proyectos de Flight con el complemento flightphp/tracy-extensions.
Instalación
Instala con composer. Y realmente querrás instalar esto sin la versión de desarrollo ya que Tracy viene con un componente de manejo de errores de producción.
composer require tracy/tracy
Configuración Básica
Hay algunas opciones de configuración básicas para comenzar. Puedes leer más sobre ellas en la Documentación de Tracy.
require 'vendor/autoload.php';
use Tracy\Debugger;
// Habilitar Tracy
Debugger::enable();
// Debugger::enable(Debugger::DEVELOPMENT) // a veces tienes que ser explícito (también Debugger::PRODUCTION)
// Debugger::enable('23.75.345.200'); // también puedes proporcionar una matriz de direcciones IP
// Aquí es donde se registrarán los errores y excepciones. Asegúrate de que este directorio exista y sea escribible.
Debugger::$logDirectory = __DIR__ . '/../log/';
Debugger::$strictMode = true; // mostrar todos los errores
// Debugger::$strictMode = E_ALL & ~E_DEPRECATED & ~E_USER_DEPRECATED; // todos los errores excepto avisos obsoletos
if (Debugger::$showBar) {
$app->set('flight.content_length', false); // si la barra Debugger es visible, entonces la longitud del contenido no puede ser establecida por Flight
// Esto es específico para la Extensión de Tracy para Flight si la has incluido
// de lo contrario, comenta esto.
new TracyExtensionLoader($app);
}
Consejos Útiles
Cuando estés depurando tu código, hay algunas funciones muy útiles para mostrar datos para ti.
bdump($var)
- Esto volcará la variable en la Barra de Tracy en un panel separado.dumpe($var)
- Esto volcará la variable y luego morirá inmediatamente.
Awesome-plugins/active_record
Flight Active Record
Un registro activo es el mapeo de una entidad de base de datos a un objeto PHP. Hablando claramente, si tienes una tabla de usuarios en tu base de datos, puedes "traducir" una fila en esa tabla a una clase User
y un objeto $user
en tu base de código. Consulta ejemplo básico.
Haz clic aquí para el repositorio en GitHub.
Ejemplo Básico
Supongamos que tienes la siguiente tabla:
CREATE TABLE users (
id INTEGER PRIMARY KEY,
name TEXT,
password TEXT
);
Ahora puedes configurar una nueva clase para representar esta tabla:
/**
* Una clase ActiveRecord suele ser singular
*
* Se recomienda encarecidamente agregar las propiedades de la tabla como comentarios aquí
*
* @property int $id
* @property string $name
* @property string $password
*/
class User extends flight\ActiveRecord {
public function __construct($database_connection)
{
// puedes configurarlo de esta manera
parent::__construct($database_connection, 'users');
// o de esta manera
parent::__construct($database_connection, null, [ 'table' => 'users']);
}
}
¡Ahora mira cómo sucede la magia!
// para sqlite
$database_connection = new PDO('sqlite:test.db'); // esto es solo un ejemplo, probablemente uses una conexión a base de datos real
// para mysql
$database_connection = new PDO('mysql:host=localhost;dbname=test_db&charset=utf8bm4', 'username', 'password');
// o mysqli
$database_connection = new mysqli('localhost', 'username', 'password', 'test_db');
// o mysqli con creación no basada en objetos
$database_connection = mysqli_connect('localhost', 'username', 'password', 'test_db');
$user = new User($database_connection);
$user->name = 'Bobby Tables';
$user->password = password_hash('some cool password');
$user->insert();
// o $user->save();
echo $user->id; // 1
$user->name = 'Joseph Mamma';
$user->password = password_hash('some cool password again!!!');
$user->insert();
// ¡no se puede usar $user->save() aquí o pensará que es una actualización!
echo $user->id; // 2
¡Y así de fácil fue agregar un nuevo usuario! Ahora que hay una fila de usuario en la base de datos, ¿cómo la extraes?
$user->find(1); // encuentra id = 1 en la base de datos y lo retorna.
echo $user->name; // 'Bobby Tables'
¿Y si quieres encontrar todos los usuarios?
$users = $user->findAll();
¿Qué tal con una cierta condición?
$users = $user->like('name', '%mamma%')->findAll();
¿Ves cuánto puede ser divertido esto? ¡Instalémoslo y comencemos!
Instalación
Simplemente instala con Composer
composer require flightphp/active-record
Uso
Esto puede ser utilizado como una biblioteca independiente o junto con el Flight PHP Framework. Depende completamente de ti.
Independiente
Solo asegúrate de pasar una conexión PDO al constructor.
$pdo_connection = new PDO('sqlite:test.db'); // esto es solo un ejemplo, probablemente uses una conexión a base de datos real
$User = new User($pdo_connection);
¿No quieres siempre establecer tu conexión a la base de datos en el constructor? Consulta Gestión de Conexiones a la Base de Datos para más ideas.
Registrar como un método en Flight
Si estás utilizando el Flight PHP Framework, puedes registrar la clase ActiveRecord como un servicio, pero honestamente no tienes que hacerlo.
Flight::register('user', 'User', [ $pdo_connection ]);
// entonces puedes usarlo así en un controlador, una función, etc.
Flight::user()->find(1);
Métodos runway
runway es una herramienta CLI para Flight que tiene un comando personalizado para esta biblioteca.
# Uso
php runway make:record database_table_name [class_name]
# Ejemplo
php runway make:record users
Esto creará una nueva clase en el directorio app/records/
como UserRecord.php
con el siguiente contenido:
<?php
declare(strict_types=1);
namespace app\records;
/**
* Clase ActiveRecord para la tabla users.
* @link https://docs.flightphp.com/awesome-plugins/active-record
*
* @property int $id
* @property string $username
* @property string $email
* @property string $password_hash
* @property string $created_dt
*/
class UserRecord extends \flight\ActiveRecord
{
/**
* @var array $relations Establece las relaciones para el modelo
* https://docs.flightphp.com/awesome-plugins/active-record#relationships
*/
protected array $relations = [
// 'relation_name' => [ self::HAS_MANY, 'RelatedClass', 'foreign_key' ],
];
/**
* Constructor
* @param mixed $databaseConnection La conexión a la base de datos
*/
public function __construct($databaseConnection)
{
parent::__construct($databaseConnection, 'users');
}
}
Funciones CRUD
find($id = null) : boolean|ActiveRecord
Encuentra un registro y asígnalo al objeto actual. Si pasas un $id
de algún tipo, realizará una búsqueda en la clave primaria con ese valor. Si no se pasa nada, simplemente encontrará el primer registro en la tabla.
Además, puedes pasarlo otros métodos auxiliares para consultar tu tabla.
// encontrar un registro con algunas condiciones previamente
$user->notNull('password')->orderBy('id DESC')->find();
// encontrar un registro por un id específico
$id = 123;
$user->find($id);
findAll(): array<int,ActiveRecord>
Encuentra todos los registros en la tabla que especifiques.
$user->findAll();
isHydrated(): boolean
(v0.4.0)
Retorna true
si el registro actual ha sido hidratado (obtenido de la base de datos).
$user->find(1);
// si se encuentra un registro con datos...
$user->isHydrated(); // true
insert(): boolean|ActiveRecord
Inserta el registro actual en la base de datos.
$user = new User($pdo_connection);
$user->name = 'demo';
$user->password = md5('demo');
$user->insert();
Claves Primarias Basadas en Texto
Si tienes una clave primaria basada en texto (como un UUID), puedes establecer el valor de la clave primaria antes de insertar de dos maneras.
$user = new User($pdo_connection, [ 'primaryKey' => 'uuid' ]);
$user->uuid = 'some-uuid';
$user->name = 'demo';
$user->password = md5('demo');
$user->insert(); // o $user->save();
o puedes hacer que la clave primaria se genere automáticamente para ti a través de eventos.
class User extends flight\ActiveRecord {
public function __construct($database_connection)
{
parent::__construct($database_connection, 'users', [ 'primaryKey' => 'uuid' ]);
// también puedes establecer la primaryKey de esta manera en lugar de la matriz anterior.
$this->primaryKey = 'uuid';
}
protected function beforeInsert(self $self) {
$self->uuid = uniqid(); // o como sea necesario para generar tus ids únicos
}
}
Si no estableces la clave primaria antes de insertar, se establecerá en rowid
y la base de datos la generará para ti, pero no persistirá porque ese campo puede no existir en tu tabla. Por eso se recomienda utilizar el evento para manejar esto automáticamente.
update(): boolean|ActiveRecord
Actualiza el registro actual en la base de datos.
$user->greaterThan('id', 0)->orderBy('id desc')->find();
$user->email = 'test@example.com';
$user->update();
save(): boolean|ActiveRecord
Inserta o actualiza el registro actual en la base de datos. Si el registro tiene un id, actualizará, de lo contrario, insertará.
$user = new User($pdo_connection);
$user->name = 'demo';
$user->password = md5('demo');
$user->save();
Nota: Si tienes relaciones definidas en la clase, también se guardarán recursivamente esas relaciones si han sido definidas, instanciadas y tienen datos modificados para actualizar. (v0.4.0 y superiores)
delete(): boolean
Elimina el registro actual de la base de datos.
$user->gt('id', 0)->orderBy('id desc')->find();
$user->delete();
También puedes eliminar múltiples registros ejecutando una búsqueda previamente.
$user->like('name', 'Bob%')->delete();
dirty(array $dirty = []): ActiveRecord
Los datos "sucios" se refieren a los datos que han sido cambiados en un registro.
$user->greaterThan('id', 0)->orderBy('id desc')->find();
// nada está "sucio" hasta este punto.
$user->email = 'test@example.com'; // ahora el email se considera "sucio" ya que ha cambiado.
$user->update();
// ahora no hay datos sucios porque han sido actualizados y persistidos en la base de datos
$user->password = password_hash('newpassword'); // ahora esto está sucio
$user->dirty(); // pasar nada limpiará todas las entradas sucias.
$user->update(); // no se actualizará nada porque nada fue capturado como sucio.
$user->dirty([ 'name' => 'something', 'password' => password_hash('a different password') ]);
$user->update(); // tanto el nombre como la contraseña son actualizados.
copyFrom(array $data): ActiveRecord
(v0.4.0)
Este es un alias para el método dirty()
. Es un poco más claro lo que estás haciendo.
$user->copyFrom([ 'name' => 'something', 'password' => password_hash('a different password') ]);
$user->update(); // tanto el nombre como la contraseña son actualizados.
isDirty(): boolean
(v0.4.0)
Retorna true
si el registro actual ha sido cambiado.
$user->greaterThan('id', 0)->orderBy('id desc')->find();
$user->email = 'test@email.com';
$user->isDirty(); // true
reset(bool $include_query_data = true): ActiveRecord
Restablece el registro actual a su estado inicial. Esto es realmente bueno para usar en comportamientos de tipo bucle. Si pasas true
, también restablecerá los datos de consulta que se utilizaron para encontrar el objeto actual (comportamiento predeterminado).
$users = $user->greaterThan('id', 0)->orderBy('id desc')->find();
$user_company = new UserCompany($pdo_connection);
foreach($users as $user) {
$user_company->reset(); // empezar con un lienzo limpio
$user_company->user_id = $user->id;
$user_company->company_id = $some_company_id;
$user_company->insert();
}
getBuiltSql(): string
(v0.4.1)
Después de ejecutar un método find()
, findAll()
, insert()
, update()
o save()
, puedes obtener el SQL que se construyó y usarlo para fines de depuración.
Métodos de Consulta SQL
select(string $field1 [, string $field2 ... ])
Puedes seleccionar solo unas pocas de las columnas en una tabla si lo deseas (es más eficiente en tablas muy amplias con muchas columnas).
$user->select('id', 'name')->find();
from(string $table)
¡Técnicamente puedes elegir otra tabla también! ¿Por qué no hacerlo?!
$user->select('id', 'name')->from('user')->find();
join(string $table_name, string $join_condition)
Incluso puedes unirte a otra tabla en la base de datos.
$user->join('contacts', 'contacts.user_id = users.id')->find();
where(string $where_conditions)
Puedes establecer algunos argumentos where personalizados (no puedes establecer parámetros en esta declaración where).
$user->where('id=1 AND name="demo"')->find();
Nota de Seguridad - Podrías sentirte tentado a hacer algo como $user->where("id = '{$id}' AND name = '{$name}'")->find();
. ¡Por favor, NO HAGAS ESTO! Esto es susceptible a lo que se conoce como ataques de inyección SQL. Hay muchos artículos en línea, por favor busca "sql injection attacks php" y encontrarás muchos artículos sobre este tema. La manera correcta de manejar esto con esta biblioteca es en lugar de este método where()
, harías algo más como $user->eq('id', $id)->eq('name', $name)->find();
Si absolutamente tienes que hacer esto, la biblioteca PDO
tiene $pdo->quote($var)
para escapar de ello por ti. Solo después de usar quote()
, puedes usarlo en una declaración where()
.
group(string $group_by_statement)/groupBy(string $group_by_statement)
Agrupa tus resultados por una condición particular.
$user->select('COUNT(*) as count')->groupBy('name')->findAll();
order(string $order_by_statement)/orderBy(string $order_by_statement)
Ordena la consulta devuelta de cierta manera.
$user->orderBy('name DESC')->find();
limit(string $limit)/limit(int $offset, int $limit)
Limita la cantidad de registros devueltos. Si se da un segundo int, se desplazará, limitará justo como en SQL.
$user->orderby('name DESC')->limit(0, 10)->findAll();
Condiciones WHERE
equal(string $field, mixed $value) / eq(string $field, mixed $value)
Donde field = $value
$user->eq('id', 1)->find();
notEqual(string $field, mixed $value) / ne(string $field, mixed $value)
Donde field <> $value
$user->ne('id', 1)->find();
isNull(string $field)
Donde field IS NULL
$user->isNull('id')->find();
isNotNull(string $field) / notNull(string $field)
Donde field IS NOT NULL
$user->isNotNull('id')->find();
greaterThan(string $field, mixed $value) / gt(string $field, mixed $value)
Donde field > $value
$user->gt('id', 1)->find();
lessThan(string $field, mixed $value) / lt(string $field, mixed $value)
Donde field < $value
$user->lt('id', 1)->find();
greaterThanOrEqual(string $field, mixed $value) / ge(string $field, mixed $value) / gte(string $field, mixed $value)
Donde field >= $value
$user->ge('id', 1)->find();
lessThanOrEqual(string $field, mixed $value) / le(string $field, mixed $value) / lte(string $field, mixed $value)
Donde field <= $value
$user->le('id', 1)->find();
like(string $field, mixed $value) / notLike(string $field, mixed $value)
Donde field LIKE $value
o field NOT LIKE $value
$user->like('name', 'de')->find();
in(string $field, array $values) / notIn(string $field, array $values)
Donde field IN($value)
o field NOT IN($value)
$user->in('id', [1, 2])->find();
between(string $field, array $values)
Donde field BETWEEN $value AND $value1
$user->between('id', [1, 2])->find();
Condiciones OR
Es posible envolver tus condiciones en una declaración OR. Esto se realiza con el método startWrap()
y endWrap()
o completando el tercer parámetro de la condición después del campo y el valor.
// Método 1
$user->eq('id', 1)->startWrap()->eq('name', 'demo')->or()->eq('name', 'test')->endWrap('OR')->find();
// Esto evaluará a `id = 1 AND (name = 'demo' OR name = 'test')`
// Método 2
$user->eq('id', 1)->eq('name', 'demo', 'OR')->find();
// Esto evaluará a `id = 1 OR name = 'demo'`
Relaciones
Puedes establecer varios tipos de relaciones utilizando esta biblioteca. Puedes establecer relaciones uno->muchos y uno->uno entre tablas. Esto requiere un poco de configuración adicional en la clase de antemano.
Establecer la matriz $relations
no es difícil, pero adivinar la sintaxis correcta puede ser confuso.
protected array $relations = [
// puedes nombrar la clave como desees. El nombre del ActiveRecord es probablemente bueno. Ej: user, contact, client
'user' => [
// requerido
// self::HAS_MANY, self::HAS_ONE, self::BELONGS_TO
self::HAS_ONE, // este es el tipo de relación
// requerido
'Some_Class', // esta es la clase ActiveRecord "otra" a la que hará referencia
// requerido
// dependiendo del tipo de relación
// self::HAS_ONE = la clave externa que referencia la unión
// self::HAS_MANY = la clave externa que referencia la unión
// self::BELONGS_TO = la clave local que referencia la unión
'local_or_foreign_key',
// solo FYI, esto también se une solo a la clave primaria del "otro" modelo
// opcional
[ 'eq' => [ 'client_id', 5 ], 'select' => 'COUNT(*) as count', 'limit' 5 ], // condiciones adicionales que deseas al unir la relación
// $record->eq('client_id', 5)->select('COUNT(*) as count')->limit(5))
// opcional
'back_reference_name' // esto es si deseas referenciar esta relación de vuelta a sí misma Ej: $user->contact->user;
];
]
class User extends ActiveRecord{
protected array $relations = [
'contacts' => [ self::HAS_MANY, Contact::class, 'user_id' ],
'contact' => [ self::HAS_ONE, Contact::class, 'user_id' ],
];
public function __construct($database_connection)
{
parent::__construct($database_connection, 'users');
}
}
class Contact extends ActiveRecord{
protected array $relations = [
'user' => [ self::BELONGS_TO, User::class, 'user_id' ],
'user_with_backref' => [ self::BELONGS_TO, User::class, 'user_id', [], 'contact' ],
];
public function __construct($database_connection)
{
parent::__construct($database_connection, 'contacts');
}
}
¡Ahora tenemos las referencias configuradas para que podamos usarlas muy fácilmente!
$user = new User($pdo_connection);
// encuentra el usuario más reciente.
$user->notNull('id')->orderBy('id desc')->find();
// obtener contactos usando la relación:
foreach($user->contacts as $contact) {
echo $contact->id;
}
// o podemos ir en la otra dirección.
$contact = new Contact();
// encuentra un contacto
$contact->find();
// obtener usuario usando la relación:
echo $contact->user->name; // este es el nombre del usuario
¿Bastante genial, eh?
Estableciendo Datos Personalizados
A veces puedes necesitar adjuntar algo único a tu ActiveRecord, como un cálculo personalizado que podría ser más fácil simplemente adjuntar al objeto que luego se pasaría a decir un template.
setCustomData(string $field, mixed $value)
Adjuntas los datos personalizados con el método setCustomData()
.
$user->setCustomData('page_view_count', $page_view_count);
Y luego simplemente lo referencias como una propiedad normal del objeto.
echo $user->page_view_count;
Eventos
Una característica más super increíble de esta biblioteca es sobre los eventos. Los eventos se activan en ciertos momentos basados en ciertos métodos que llamas. Son muy útiles para configurar datos automáticamente para ti.
onConstruct(ActiveRecord $ActiveRecord, array &config)
Esto es realmente útil si necesitas establecer una conexión predeterminada o algo así.
// index.php o bootstrap.php
Flight::register('db', 'PDO', [ 'sqlite:test.db' ]);
//
//
//
// User.php
class User extends flight\ActiveRecord {
protected function onConstruct(self $self, array &$config) { // no olvides la referencia &
// podrías hacer esto para establecer automáticamente la conexión
$config['connection'] = Flight::db();
// o esto
$self->transformAndPersistConnection(Flight::db());
// También puedes establecer el nombre de la tabla de esta manera.
$config['table'] = 'users';
}
}
beforeFind(ActiveRecord $ActiveRecord)
Esto probablemente solo sea útil si necesitas manipular la consulta cada vez.
class User extends flight\ActiveRecord {
public function __construct($database_connection)
{
parent::__construct($database_connection, 'users');
}
protected function beforeFind(self $self) {
// siempre ejecutar id >= 0 si eso es lo que prefieres
$self->gte('id', 0);
}
}
afterFind(ActiveRecord $ActiveRecord)
Este probablemente sea más útil si siempre necesitas ejecutar alguna lógica cada vez que se recoge este registro. ¿Necesitas desencriptar algo? ¿Necesitas ejecutar una consulta personalizada cada vez (no muy eficiente, pero bueno)?
class User extends flight\ActiveRecord {
public function __construct($database_connection)
{
parent::__construct($database_connection, 'users');
}
protected function afterFind(self $self) {
// desencriptando algo
$self->secret = yourDecryptFunction($self->secret, $some_key);
// tal vez almacenando algo personalizado como una consulta???
$self->setCustomData('view_count', $self->select('COUNT(*) count')->from('user_views')->eq('user_id', $self->id)['count']);
}
}
beforeFindAll(ActiveRecord $ActiveRecord)
Esto probablemente solo sea útil si necesitas manipular la consulta cada vez.
class User extends flight\ActiveRecord {
public function __construct($database_connection)
{
parent::__construct($database_connection, 'users');
}
protected function beforeFindAll(self $self) {
// siempre ejecutar id >= 0 si eso es lo que prefieres
$self->gte('id', 0);
}
}
afterFindAll(array<int,ActiveRecord> $results)
Similar a afterFind()
, pero ¡puedes hacerlo a todos los registros en su lugar!
class User extends flight\ActiveRecord {
public function __construct($database_connection)
{
parent::__construct($database_connection, 'users');
}
protected function afterFindAll(array $results) {
foreach($results as $self) {
// hacer algo genial como afterFind()
}
}
}
beforeInsert(ActiveRecord $ActiveRecord)
Realmente útil si necesitas establecer algunos valores predeterminados cada vez.
class User extends flight\ActiveRecord {
public function __construct($database_connection)
{
parent::__construct($database_connection, 'users');
}
protected function beforeInsert(self $self) {
// establecer algunos valores predeterminados
if(!$self->created_date) {
$self->created_date = gmdate('Y-m-d');
}
if(!$self->password) {
$self->password = password_hash((string) microtime(true));
}
}
}
afterInsert(ActiveRecord $ActiveRecord)
Tal vez tengas un caso de uso para cambiar datos después de que se inserte.
class User extends flight\ActiveRecord {
public function __construct($database_connection)
{
parent::__construct($database_connection, 'users');
}
protected function afterInsert(self $self) {
// tú decides
Flight::cache()->set('most_recent_insert_id', $self->id);
// o lo que sea....
}
}
beforeUpdate(ActiveRecord $ActiveRecord)
Realmente útil si necesitas establecer algunos valores predeterminados cada vez en una actualización.
class User extends flight\ActiveRecord {
public function __construct($database_connection)
{
parent::__construct($database_connection, 'users');
}
protected function beforeInsert(self $self) {
// establecer algunos valores predeterminados
if(!$self->updated_date) {
$self->updated_date = gmdate('Y-m-d');
}
}
}
afterUpdate(ActiveRecord $ActiveRecord)
Tal vez tengas un caso de uso para cambiar datos después de que se actualice.
class User extends flight\ActiveRecord {
public function __construct($database_connection)
{
parent::__construct($database_connection, 'users');
}
protected function afterInsert(self $self) {
// tú decides
Flight::cache()->set('most_recently_updated_user_id', $self->id);
// o lo que sea....
}
}
beforeSave(ActiveRecord $ActiveRecord)/afterSave(ActiveRecord $ActiveRecord)
Esto es útil si deseas que se produzcan eventos tanto cuando se inserten como cuando se actualicen. Te ahorraré la larga explicación, pero estoy seguro de que puedes adivinar qué es.
class User extends flight\ActiveRecord {
public function __construct($database_connection)
{
parent::__construct($database_connection, 'users');
}
protected function beforeSave(self $self) {
$self->last_updated = gmdate('Y-m-d H:i:s');
}
}
beforeDelete(ActiveRecord $ActiveRecord)/afterDelete(ActiveRecord $ActiveRecord)
¡No estoy seguro de lo que querrías hacer aquí, pero no hago juicios! ¡Adelante!
class User extends flight\ActiveRecord {
public function __construct($database_connection)
{
parent::__construct($database_connection, 'users');
}
protected function beforeDelete(self $self) {
echo 'Él fue un valiente soldado... :cry-face:';
}
}
Gestión de Conexiones a la Base de Datos
Al utilizar esta biblioteca, puedes establecer la conexión a la base de datos de varias maneras. Puedes establecer la conexión en el constructor, puedes configurarla a través de una variable de configuración $config['connection']
o puedes establecerla a través de setDatabaseConnection()
(v0.4.1).
$pdo_connection = new PDO('sqlite:test.db'); // por ejemplo
$user = new User($pdo_connection);
// o
$user = new User(null, [ 'connection' => $pdo_connection ]);
// o
$user = new User();
$user->setDatabaseConnection($pdo_connection);
Si deseas evitar establecer siempre una $database_connection
cada vez que llamas a un registro activo, ¡hay formas de hacerlo!
// index.php o bootstrap.php
// Establecer esto como una clase registrada en Flight
Flight::register('db', 'PDO', [ 'sqlite:test.db' ]);
// User.php
class User extends flight\ActiveRecord {
public function __construct(array $config = [])
{
$database_connection = $config['connection'] ?? Flight::db();
parent::__construct($database_connection, 'users', $config);
}
}
// ¡Y ahora, no se requieren argumentos!
$user = new User();
Nota: Si planeas hacer pruebas unitarias, hacerlo de esta manera puede agregar algunos desafíos a las pruebas unitarias, pero en general debido a que puedes inyectar tu conexión con
setDatabaseConnection()
o$config['connection']
, no es tan malo.
Si necesitas refrescar la conexión a la base de datos, por ejemplo, si estás ejecutando un script CLI de larga duración y necesitas refrescar la conexión de vez en cuando, puedes restablecer la conexión con $your_record->setDatabaseConnection($pdo_connection)
.
Contribuyendo
Por favor, hazlo. :D
Configuración
Cuando contribuyas, asegúrate de ejecutar composer test-coverage
para mantener un 100% de cobertura de pruebas (esto no es una cobertura de pruebas unitarias verdadera, más bien pruebas de integración).
Además, asegúrate de ejecutar composer beautify
y composer phpcs
para corregir cualquier error de sintaxis.
Licencia
MIT
Awesome-plugins/latte
Latte
Latte es un motor de plantillas completo que es muy fácil de usar y se siente más cercano a la sintaxis de PHP que Twig o Smarty. También es muy fácil de extender y agregar tus propios filtros y funciones.
Instalación
Instala con composer.
composer require latte/latte
Configuración Básica
Hay algunas opciones de configuración básicas para comenzar. Puedes leer más sobre ellas en la Documentación de Latte.
require 'vendor/autoload.php';
$app = Flight::app();
$app->map('render', function(string $template, array $data, ?string $block): void {
$latte = new Latte\Engine;
// Dónde Latte almacena específicamente su caché
$latte->setTempDirectory(__DIR__ . '/../cache/');
$finalPath = Flight::get('flight.views.path') . $template;
$latte->render($finalPath, $data, $block);
});
Ejemplo Simple de Diseño
Aquí hay un ejemplo simple de un archivo de diseño. Este es el archivo que se usará para envolver todas tus otras vistas.
<!-- app/views/layout.latte -->
<!doctype html>
<html lang="en">
<head>
<title>{$title ? $title . ' - '}Mi App</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<header>
<nav>
<!-- tus elementos de navegación aquí -->
</nav>
</header>
<div id="content">
<!-- Aquí está la magia -->
{block content}{/block}
</div>
<div id="footer">
© Copyright
</div>
</body>
</html>
Y ahora tenemos tu archivo que se va a renderizar dentro de ese bloque de contenido:
<!-- app/views/home.latte -->
<!-- Esto le dice a Latte que este archivo está "dentro" del archivo layout.latte -->
{extends layout.latte}
<!-- Este es el contenido que se renderizará dentro del diseño en el bloque de contenido -->
{block content}
<h1>Página de Inicio</h1>
<p>¡Bienvenido a mi app!</p>
{/block}
Luego, cuando vayas a renderizar esto dentro de tu función o controlador, harías algo como esto:
// ruta simple
Flight::route('/', function () {
Flight::render('home.latte', [
'title' => 'Página de Inicio'
]);
});
// o si estás usando un controlador
Flight::route('/', [HomeController::class, 'index']);
// HomeController.php
class HomeController
{
public function index()
{
Flight::render('home.latte', [
'title' => 'Página de Inicio'
]);
}
}
¡Consulta la Documentación de Latte para obtener más información sobre cómo usar Latte a su máximo potencial!
Depuración con Tracy
Se requiere PHP 8.1+ para esta sección.
¡También puedes usar Tracy para ayudar con la depuración de tus archivos de plantillas Latte directamente de la caja! Si ya tienes Tracy instalado, necesitas agregar la extensión de Latte a Tracy.
// services.php
use Tracy\Debugger;
$app->map('render', function(string $template, array $data, ?string $block): void {
$latte = new Latte\Engine;
// Dónde Latte almacena específicamente su caché
$latte->setTempDirectory(__DIR__ . '/../cache/');
$finalPath = Flight::get('flight.views.path') . $template;
// Esto solo agregará la extensión si la Barra de Depuración de Tracy está habilitada
if (Debugger::$showBar === true) {
// aquí es donde agregas el Panel de Latte a Tracy
$latte->addExtension(new Latte\Bridges\Tracy\TracyExtension);
}
$latte->render($finalPath, $data, $block);
});
Awesome-plugins/awesome_plugins
Plugins Geniales
Flight es increíblemente extensible. Hay una serie de plugins que se pueden usar para agregar funcionalidad a tu aplicación Flight. Algunos son soportados oficialmente por el Equipo de Flight y otros son bibliotecas micro/lite para ayudarte a comenzar.
Documentación de API
La documentación de API es crucial para cualquier API. Ayuda a los desarrolladores a entender cómo interactuar con tu API y qué esperar a cambio. Hay un par de herramientas disponibles para ayudarte a generar documentación de API para tus Proyectos Flight.
- FlightPHP OpenAPI Generator - Publicación de blog escrita por Daniel Schreiber sobre cómo usar la Especificación OpenAPI con FlightPHP para construir tu API utilizando un enfoque API first.
- SwaggerUI - Swagger UI es una gran herramienta para ayudarte a generar documentación de API para tus proyectos Flight. Es muy fácil de usar y se puede personalizar para adaptarse a tus necesidades. Esta es la biblioteca PHP para ayudarte a generar la documentación Swagger.
Monitoreo de Rendimiento de Aplicaciones (APM)
El Monitoreo de Rendimiento de Aplicaciones (APM) es crucial para cualquier aplicación. Te ayuda a entender cómo está funcionando tu aplicación y dónde están los cuellos de botella. Hay una serie de herramientas APM que se pueden usar con Flight.
- oficial flightphp/apm - Flight APM es una biblioteca APM simple que se puede usar para monitorear tus aplicaciones Flight. Se puede usar para monitorear el rendimiento de tu aplicación y ayudarte a identificar cuellos de botella.
Async
Flight ya es un framework rápido, pero agregarle un motor turbo lo hace todo más divertido (¡y desafiante)!
- flightphp/async - Biblioteca oficial de Flight Async. Esta biblioteca es una forma simple de agregar procesamiento asíncrono a tu aplicación. Usa Swoole/Openswoole bajo el capó para proporcionar una forma simple y efectiva de ejecutar tareas de manera asíncrona.
Autorización/Permisos
La autorización y los permisos son cruciales para cualquier aplicación que requiera controles en su lugar para quién puede acceder a qué.
- oficial flightphp/permissions - Biblioteca oficial de Permisos de Flight. Esta biblioteca es una forma simple de agregar permisos a nivel de usuario y aplicación a tu aplicación.
Caché
El caché es una gran manera de acelerar tu aplicación. Hay una serie de bibliotecas de caché que se pueden usar con Flight.
- oficial flightphp/cache - Clase ligera, simple y autónoma de caché en archivo PHP
CLI
Las aplicaciones CLI son una gran manera de interactuar con tu aplicación. Puedes usarlas para generar controladores, mostrar todas las rutas y más.
- oficial flightphp/runway - Runway es una aplicación CLI que te ayuda a gestionar tus aplicaciones Flight.
Cookies
Las cookies son una gran manera de almacenar pequeños bits de datos en el lado del cliente. Se pueden usar para almacenar preferencias de usuario, configuraciones de aplicación y más.
- overclokk/cookie - PHP Cookie es una biblioteca PHP que proporciona una forma simple y efectiva de gestionar cookies.
Depuración
La depuración es crucial cuando estás desarrollando en tu entorno local. Hay algunos plugins que pueden elevar tu experiencia de depuración.
- tracy/tracy - Esta es una manejadora de errores completa que se puede usar con Flight. Tiene una serie de paneles que pueden ayudarte a depurar tu aplicación. También es muy fácil de extender y agregar tus propios paneles.
- oficial flightphp/tracy-extensions - Usado con el manejador de errores Tracy, este plugin agrega algunos paneles extra para ayudar con la depuración específicamente para proyectos Flight.
Bases de Datos
Las bases de datos son el núcleo de la mayoría de las aplicaciones. Así es como almacenas y recuperas datos. Algunas bibliotecas de bases de datos son simplemente envolturas para escribir consultas y otras son ORMs completos.
- oficial flightphp/core PdoWrapper - Envoltura oficial de PDO de Flight que forma parte del núcleo. Esta es una envoltura simple para ayudar a simplificar el proceso de escribir consultas y ejecutarlas. No es un ORM.
- oficial flightphp/active-record - ORM/Mapper ActiveRecord oficial de Flight. Gran biblioteca pequeña para recuperar y almacenar datos fácilmente en tu base de datos.
- byjg/php-migration - Plugin para mantener un seguimiento de todos los cambios de base de datos para tu proyecto.
Encriptación
La encriptación es crucial para cualquier aplicación que almacene datos sensibles. Encriptar y desencriptar los datos no es terriblemente difícil, pero almacenar correctamente la clave de encriptación puede ser difícil. Lo más importante es nunca almacenar tu clave de encriptación en un directorio público o cometerla en tu repositorio de código.
- defuse/php-encryption - Esta es una biblioteca que se puede usar para encriptar y desencriptar datos. Ponerse en marcha es bastante simple para comenzar a encriptar y desencriptar datos.
Cola de Trabajos
Las colas de trabajos son realmente útiles para procesar tareas de manera asíncrona. Esto puede ser enviar correos electrónicos, procesar imágenes o cualquier cosa que no necesite hacerse en tiempo real.
- n0nag0n/simple-job-queue - Simple Job Queue es una biblioteca que se puede usar para procesar trabajos de manera asíncrona. Se puede usar con beanstalkd, MySQL/MariaDB, SQLite y PostgreSQL.
Sesión
Las sesiones no son realmente útiles para las API, pero para construir una aplicación web, las sesiones pueden ser cruciales para mantener el estado e información de inicio de sesión.
- oficial flightphp/session - Biblioteca oficial de Sesión de Flight. Esta es una biblioteca de sesión simple que se puede usar para almacenar y recuperar datos de sesión. Usa el manejo de sesiones incorporado de PHP.
- Ghostff/Session - Gestor de Sesión PHP (no bloqueante, flash, segmento, encriptación de sesión). Usa PHP open_ssl para encriptación/desencriptación opcional de datos de sesión.
Plantillas
Las plantillas son el núcleo de cualquier aplicación web con una UI. Hay una serie de motores de plantillas que se pueden usar con Flight.
- deprecado flightphp/core View - Este es un motor de plantillas muy básico que forma parte del núcleo. No se recomienda usarlo si tienes más de un par de páginas en tu proyecto.
- latte/latte - Latte es un motor de plantillas completo que es muy fácil de usar y se siente más cercano a la sintaxis de PHP que Twig o Smarty. También es muy fácil de extender y agregar tus propios filtros y funciones.
Integración con WordPress
¿Quieres usar Flight en tu proyecto de WordPress? ¡Hay un plugin práctico para eso!
- n0nag0n/wordpress-integration-for-flight-framework - Este plugin de WordPress te permite ejecutar Flight justo al lado de WordPress. Es perfecto para agregar APIs personalizadas, microservicios o incluso aplicaciones completas a tu sitio de WordPress usando el framework Flight. Súper útil si quieres lo mejor de ambos mundos!
Contribuir
¿Tienes un plugin que te gustaría compartir? ¡Envía una solicitud de pull para agregarlo a la lista!
Media
Media
Hemos intentado rastrear lo que podemos de los diversos tipos de media en internet sobre Flight. A continuación, encontrarás diferentes recursos que puedes usar para aprender más sobre Flight.
Artículos y reseñas
- Unit Testing and SOLID Principles por Brian Fenton (2015?)
- PHP Web Framework Flight por ojambo (2025)
- Define, Generate, and Implement: An API-First Approach with OpenAPI Generator and FlightPHP por Daniel Schreiber (2025)
- Best PHP Micro Frameworks for 2024 por n0nag0n (2024)
- Creating a RESTful API with Flight Framework por n0nag0n (2024)
- Building a Simple Blog with Flight Part 2 por n0nag0n (2024)
- Building a Simple Blog with Flight Part 1 por n0nag0n (2024)
- 🚀 Build a Simple CRUD API in PHP with the Flight Framework por soheil-khaledabadi (2024)
- Building a PHP Web Application with the Flight Micro-framework por Arthur C. Codex (2023)
- Best PHP Frameworks for Web Development in 2024 por Ravikiran A S (2023)
- Top 12 PHP Frameworks: A Comprehensive Guide for 2023 por marketing kbk (2023)
- 5 PHP Frameworks You've (Probably) Never Heard of por n0nag0n (2022)
- 12 top PHP frameworks for web developers to consider in 2023 por Anna Monus (2022)
- The Best PHP Microframeworks on a Cloud Server por Shahzeb Ahmed (2021)
- PHP framework: Top 15 powerful ones for your web development por AHT Tech (2020)
- Easy PHP Routing with FlightPHP por Lucas Conceição (2019)
- Trying Out New PHP Framework (Flight) por Leon (2017)
- Setting up FlightPHP to work with Backbonejs por Timothy Tocci (2015)
Videos y tutoriales
- Build a Flight PHP App with MVC & MariaDB in 10 Minutes! (Beginner Friendly) por ojamboshop (2025)
- Create a REST API for IoT Devices Using PHP & FlightPHP - ESP32 API por IoT Craft Hub (2024)
- PHP Flight Framework Simple Introductory Video por n0nag0n (2024)
- Set header HTTP code in Flightphp (3 Solutions!!) por Roel Van de Paar (2024)
- PHP Flight Framework Tutorial. Super easy API Project! por n0nag0n (2022)
- Aplicación web CRUD con php y mysql y bootstrap usando flight por Devlopteca - Oscar Uh (2021)
- DevOps & SysAdmins: Lighttpd rewrite rule for Flight PHP microframework por Roel Van de Paar (2021)
- Tutorial REST API Flight PHP #PART2 INSERT TABLE Info #Code (Tagalog) por Info Singkat Official (2020)
- Tutorial REST API Flight PHP #PART1 Info #Code (Tagalog) por Info Singkat Official (2020)
- How To Create JSON REST API IN PHP - Part 2 por Codewife (2018)
- How To Create JSON REST API IN PHP - Part 1 por Codewife (2018)
- Teste Micro Frameworks PHP - Flight PHP, Lumen, Slim 3 e Laravel por Codemarket (2016)
- Tutorial 1 Flight PHP - Instalación por absagg (2014)
- Tutorial 2 Flight PHP - Route parte 1 por absagg (2014)
¿Falta algo?
¿Nos falta algo que escribiste o grabaste? ¡Avísanos con un issue o pull request!
Examples
¿Necesitas un inicio rápido?
Tienes dos opciones para comenzar con un nuevo proyecto de Flight:
- Full Skeleton Boilerplate: Un ejemplo más completo con controladores y vistas.
- Single File Skeleton Boilerplate: Un solo archivo que incluye todo lo que necesitas para ejecutar tu aplicación en un archivo simple.
Ejemplos aportados por la comunidad:
- flightravel: FlightPHP con directorios de Laravel, con herramientas de PHP + GH Actions
- fleact - Un kit de inicio de FlightPHP con integración de ReactJS.
- flastro - Un kit de inicio de FlightPHP con integración de Astro.
- velt - Velt es una plantilla de inicio rápida y fácil para Svelte con un backend de FlightPHP.
¿Necesitas algo de inspiración?
Aunque estos no están patrocinados oficialmente por el equipo de Flight, podrían darte ideas sobre cómo estructurar tus propios proyectos construidos con Flight!
- Ivox Car Rental - Ivox Car Rental es una aplicación web de alquiler de autos de una sola página, amigable con dispositivos móviles, construida con PHP (FlightPHP), JavaScript y MySQL. Soporta registro de usuarios, navegación y reserva de autos, mientras que los administradores pueden gestionar autos, usuarios y reservas. La aplicación cuenta con una API REST, autenticación JWT y un diseño responsivo para una experiencia de alquiler moderna.
- Decay - Flight v3 con HTMX y SleekDB, todo sobre zombis! (Demo)
- Flight Example Blog - Flight v3 con Middleware, Controladores, Active Record y Latte.
- Flight CRUD RESTful API - Proyecto de API CRUD simple utilizando el framework Flight, que proporciona una estructura básica para que los nuevos usuarios configuren rápidamente una aplicación PHP con operaciones CRUD y conectividad a la base de datos. El proyecto demuestra cómo usar Flight para el desarrollo de API RESTful, lo que lo convierte en una herramienta de aprendizaje ideal para principiantes y un kit de inicio útil para desarrolladores más experimentados.
- Flight School Management System - Flight v3
- Paste Bin with Comments - Flight v3
- Basic Skeleton App
- Example Wiki
- The IT-Innovator PHP Framework Application
- LittleEducationalCMS (Spanish)
- Italian Yellow Pages API
- Generic Content Management System (with....very little documentation)
- A tiny php framework based on Flight and medoo.
- Example MVC Application
- Production ready Flight Boilerplate - Framework de autenticación listo para producción que te ahorra semanas de desarrollo. Características de seguridad de grado empresarial: 2FA/TOTP, integración LDAP, SSO de Azure, limitación de velocidad inteligente, huella dactilar de sesión, protección contra fuerza bruta, panel de análisis de seguridad, registro de auditoría completo y control de acceso basado en roles granular.
¿Quieres compartir tu propio ejemplo?
Si tienes un proyecto que quieres compartir, ¡por favor envía una solicitud de pull para agregarlo a esta lista!
Install/install
Instrucciones de Instalación
Hay algunos prerrequisitos básicos antes de que puedas instalar Flight. Específicamente, necesitarás:
- Instalar PHP en tu sistema
- Instalar Composer para la mejor experiencia de desarrollo.
Instalación Básica
Si estás usando Composer, puedes ejecutar el siguiente comando:
composer require flightphp/core
Esto solo colocará los archivos principales de Flight en tu sistema. Necesitarás definir la estructura del proyecto, diseño, dependencias, configuraciones, carga automática, etc. Este método asegura que no se instalen otras dependencias además de Flight.
También puedes descargar los archivos directamente y extraerlos a tu directorio web.
Instalación Recomendada
Se recomienda altamente comenzar con la aplicación flightphp/skeleton para cualquier proyecto nuevo. La instalación es muy sencilla.
composer create-project flightphp/skeleton my-project/
Esto configurará la estructura de tu proyecto, configurará la carga automática con espacios de nombres, configurará una configuración y proporcionará otras herramientas como Tracy, Extensiones de Tracy y Runway.
Configura tu Servidor Web
Servidor de Desarrollo PHP Integrado
Esta es, con mucho, la forma más simple de comenzar. Puedes usar el servidor integrado para ejecutar tu aplicación e incluso usar SQLite para una base de datos (siempre y cuando sqlite3 esté instalado en tu sistema) y no requerir mucho más. Solo ejecuta el siguiente comando una vez que PHP esté instalado:
php -S localhost:8000
# o con la aplicación skeleton
composer start
Luego abre tu navegador e ingresa a http://localhost:8000
.
Si quieres hacer que la raíz de documentos de tu proyecto sea un directorio diferente (Ej: tu proyecto es ~/myproject
, pero tu raíz de documentos es ~/myproject/public/
), puedes ejecutar el siguiente comando una vez que estés en el directorio ~/myproject
:
php -S localhost:8000 -t public/
# con la aplicación skeleton, esto ya está configurado
composer start
Luego abre tu navegador e ingresa a http://localhost:8000
.
Apache
Asegúrate de que Apache ya esté instalado en tu sistema. Si no, busca en Google cómo instalar Apache en tu sistema.
Para Apache, edita tu archivo .htaccess
con lo siguiente:
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php [QSA,L]
Nota: Si necesitas usar flight en un subdirectorio, agrega la línea
RewriteBase /subdir/
justo después deRewriteEngine On
.Nota: Si quieres proteger todos los archivos del servidor, como un archivo db o env. Pon esto en tu archivo
.htaccess
:
RewriteEngine On
RewriteRule ^(.*)$ index.php
Nginx
Asegúrate de que Nginx ya esté instalado en tu sistema. Si no, busca en Google cómo instalar Nginx en tu sistema.
Para Nginx, agrega lo siguiente a tu declaración de servidor:
server {
location / {
try_files $uri $uri/ /index.php;
}
}
Crea tu archivo index.php
Si estás haciendo una instalación básica, necesitarás algo de código para comenzar.
<?php
// Si estás usando Composer, requiere el cargador automático.
require 'vendor/autoload.php';
// si no estás usando Composer, carga el framework directamente
// require 'flight/Flight.php';
// Luego define una ruta y asigna una función para manejar la solicitud.
Flight::route('/', function () {
echo 'hello world!';
});
// Finalmente, inicia el framework.
Flight::start();
Con la aplicación skeleton, esto ya está configurado y manejado en tu archivo app/config/routes.php
. Los servicios se configuran en app/config/services.php
.
Instalando PHP
Si ya tienes php
instalado en tu sistema, salta estas instrucciones y ve a la sección de descarga.
macOS
Instalando PHP usando Homebrew
-
Instala Homebrew (si no está ya instalado):
- Abre Terminal y ejecuta:
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
- Abre Terminal y ejecuta:
-
Instala PHP:
- Instala la versión más reciente:
brew install php
- Para instalar una versión específica, por ejemplo, PHP 8.1:
brew tap shivammathur/php brew install shivammathur/php/php@8.1
- Instala la versión más reciente:
-
Cambia entre versiones de PHP:
- Desvincula la versión actual y vincula la versión deseada:
brew unlink php brew link --overwrite --force php@8.1
- Verifica la versión instalada:
php -v
- Desvincula la versión actual y vincula la versión deseada:
Windows 10/11
Instalando PHP manualmente
-
Descarga PHP:
- Visita PHP for Windows y descarga la versión más reciente o una específica (p.ej., 7.4, 8.0) como un archivo zip no seguro para hilos.
-
Extrae PHP:
- Extrae el archivo zip descargado a
C:\php
.
- Extrae el archivo zip descargado a
-
Agrega PHP al PATH del sistema:
- Ve a Propiedades del Sistema > Variables de Entorno.
- Bajo Variables del sistema, encuentra Path y haz clic en Editar.
- Agrega la ruta
C:\php
(o dondequiera que hayas extraído PHP). - Haz clic en Aceptar para cerrar todas las ventanas.
-
Configura PHP:
- Copia
php.ini-development
aphp.ini
. - Edita
php.ini
para configurar PHP según sea necesario (p.ej., estableciendoextension_dir
, habilitando extensiones).
- Copia
-
Verifica la instalación de PHP:
- Abre el Símbolo del sistema y ejecuta:
php -v
- Abre el Símbolo del sistema y ejecuta:
Instalando Múltiples Versiones de PHP
-
Repite los pasos anteriores para cada versión, colocándolas en un directorio separado (p.ej.,
C:\php7
,C:\php8
). -
Cambia entre versiones ajustando la variable PATH del sistema para apuntar al directorio de la versión deseada.
Ubuntu (20.04, 22.04, etc.)
Instalando PHP usando apt
-
Actualiza las listas de paquetes:
- Abre Terminal y ejecuta:
sudo apt update
- Abre Terminal y ejecuta:
-
Instala PHP:
- Instala la versión más reciente de PHP:
sudo apt install php
- Para instalar una versión específica, por ejemplo, PHP 8.1:
sudo apt install php8.1
- Instala la versión más reciente de PHP:
-
Instala módulos adicionales (opcional):
- Por ejemplo, para instalar soporte para MySQL:
sudo apt install php8.1-mysql
- Por ejemplo, para instalar soporte para MySQL:
-
Cambia entre versiones de PHP:
- Usa
update-alternatives
:sudo update-alternatives --set php /usr/bin/php8.1
- Usa
-
Verifica la versión instalada:
- Ejecuta:
php -v
- Ejecuta:
Rocky Linux
Instalando PHP usando yum/dnf
-
Habilita el repositorio EPEL:
- Abre Terminal y ejecuta:
sudo dnf install epel-release
- Abre Terminal y ejecuta:
-
Instala el repositorio de Remi:
- Ejecuta:
sudo dnf install https://rpms.remirepo.net/enterprise/remi-release-8.rpm sudo dnf module reset php
- Ejecuta:
-
Instala PHP:
- Para instalar la versión predeterminada:
sudo dnf install php
- Para instalar una versión específica, por ejemplo, PHP 7.4:
sudo dnf module install php:remi-7.4
- Para instalar la versión predeterminada:
-
Cambia entre versiones de PHP:
- Usa el comando de módulo
dnf
:sudo dnf module reset php sudo dnf module enable php:remi-8.0 sudo dnf install php
- Usa el comando de módulo
-
Verifica la versión instalada:
- Ejecuta:
php -v
- Ejecuta:
Notas Generales
- Para entornos de desarrollo, es importante configurar las opciones de PHP según los requisitos de tu proyecto.
- Al cambiar versiones de PHP, asegúrate de que todas las extensiones relevantes de PHP estén instaladas para la versión específica que pretendes usar.
- Reinicia tu servidor web (Apache, Nginx, etc.) después de cambiar versiones de PHP o actualizar configuraciones para aplicar los cambios.
Guides
Guías
Flight PHP está diseñado para ser simple pero poderoso, y nuestras guías te ayudarán a construir aplicaciones del mundo real paso a paso. Estos tutoriales prácticos te guían a través de proyectos completos para demostrar cómo Flight puede usarse de manera efectiva.
Guías Oficiales
Construyendo un Blog
Aprende cómo crear una aplicación de blog funcional con Flight PHP. Esta guía te guía a través de:
- Configurando una estructura de proyecto
- Trabajando con plantillas usando Latte
- Implementando rutas para publicaciones
- Almacenando y recuperando datos
- Manejo de envíos de formularios
- Manejo básico de errores
Este tutorial es perfecto para principiantes que quieran ver cómo todas las piezas encajan en una aplicación real.
Pruebas Unitarias y Principios SOLID
Esta guía cubre los fundamentos de las pruebas unitarias en aplicaciones Flight PHP. Incluye:
- Configurando PHPUnit
- Escribiendo código probables usando principios SOLID
- Burlando dependencias
- Fosas comunes para evitar
- Escalando tus pruebas a medida que crece tu aplicación Este tutorial es ideal para desarrolladores que busquen mejorar la calidad y mantenibilidad del código.
Guías No Oficiales
Aunque estas guías no son mantenidas oficialmente por el equipo de Flight, son recursos valiosos creados por la comunidad. Cubren varios temas y casos de uso, proporcionando insights adicionales sobre el uso de Flight PHP.
Creating a RESTful API with Flight Framework
Esta guía te guía a través de la creación de una API RESTful usando el framework Flight PHP. Cubre los conceptos básicos de configurar una API, definir rutas y devolver respuestas JSON.
Building a Simple Blog
Esta guía te guía a través de la creación de un blog básico usando el framework Flight PHP. De hecho, tiene 2 partes: una para cubrir los conceptos básicos y la otra para cubrir temas más avanzados y refinamientos para un blog listo para producción.
- Building a Simple Blog with Flight - Part 1 - Comenzando con un blog simple.
- Building a Simple Blog with Flight - Part 2 - Refinando el blog para producción.
Building a Pokémon API in PHP: A Beginner's Guide
Esta guía divertida te guía a través de la creación de una simple API de Pokémon usando Flight PHP. Cubre los conceptos básicos de configurar una API, definir rutas y devolver respuestas JSON.
Contribuyendo
¿Tienes una idea para una guía? ¿Encontraste un error? ¡Bienvenidas las contribuciones! Nuestras guías se mantienen en el repositorio de documentación de FlightPHP.
Si has construido algo interesante con Flight y quieres compartirlo como una guía, por favor envía una solicitud de extracción. Compartir tu conocimiento ayuda a que la comunidad de Flight crezca.
¿Buscando Documentación de API?
Si estás buscando información específica sobre las características y métodos principales de Flight, consulta la sección Aprender de nuestra documentación.