Learn

Aprende sobre Flight

Flight es un marco rápido, simple y extensible para PHP. Es bastante versátil y se puede utilizar para construir cualquier tipo de aplicación web. Se construyó con la simplicidad en mente y está escrito de una manera que es fácil de entender y usar.

Conceptos Importantes del Marco

¿Por qué un Marco?

Aquí hay un artículo breve sobre por qué deberías usar un marco. Es una buena idea comprender los beneficios de usar un marco antes de comenzar a usar uno.

Además, se ha creado un excelente tutorial por @lubiana. Aunque no entra en muchos detalles sobre Flight específicamente, esta guía te ayudará a entender algunos de los conceptos principales que rodean a un marco y por qué son beneficiosos de usar. Puedes encontrar el tutorial aquí.

Flight en Comparación con Otros Marcos

Si estás migrando de otro marco como Laravel, Slim, Fat-Free o Symfony a Flight, esta página te ayudará a entender las diferencias entre los dos.

Temas Principales

Autocarga

Aprende cómo cargar automáticamente tus propias clases en tu aplicación.

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.

Solicitudes

Aprende cómo manejar solicitudes y respuestas en tu aplicación.

Respuestas

Aprende cómo enviar respuestas a tus usuarios.

Eventos

Aprende cómo usar el sistema de eventos para agregar eventos personalizados a tu aplicación.

Plantillas HTML

Aprende cómo usar el motor de vistas incorporado para renderizar tus plantillas HTML.

Seguridad

Aprende cómo asegurar tu aplicación de amenazas de seguridad comunes.

Configuración

Aprende cómo configurar el marco para tu aplicación.

Extendiendo Flight

Aprende cómo extender el marco agregando tus propios métodos y clases.

Eventos y Filtrado

Aprende cómo usar el sistema de eventos para agregar ganchos a tus métodos y métodos internos del marco.

Contenedor de Inyección de Dependencias

Aprende cómo usar contenedores de inyección de dependencias (DIC) para gestionar las dependencias de tu aplicación.

API del Marco

Aprende sobre los métodos principales del marco.

Migrando a v3

La compatibilidad hacia atrás se ha mantenido en su mayor parte, pero hay algunos cambios de los que deberías estar al tanto al migrar de v2 a v3.

Resolución de Problemas

Hay algunos problemas comunes con los que puedes encontrarte al usar Flight. Esta página te ayudará a solucionar esos problemas.

Learn/stopping

Detener

Puedes detener el marco de trabajo en cualquier momento llamando al método halt:

Flight::halt();

También puedes especificar un código de estado HTTP opcional y un mensaje:

Flight::halt(200, 'Vuelvo enseguida...');

Llamar a halt descartará cualquier contenido de respuesta hasta ese punto. Si deseas detener el marco de trabajo y generar la respuesta actual, utiliza el método stop:

Flight::stop();

Learn/errorhandling

Manejo de Errores

Errores y Excepciones

Todos los errores y excepciones son capturados por Flight y pasados al método error. El comportamiento predeterminado es enviar una respuesta genérica de HTTP 500 Internal Server Error con información sobre el error.

Puede anular este comportamiento según sus 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);

No Encontrado

Cuando una URL no se puede encontrar, Flight llama al método notFound. El comportamiento predeterminado es enviar una respuesta de HTTP 404 Not Found con un mensaje simple.

Puede anular este comportamiento según sus necesidades:

Flight::map('notFound', function () {
  // Manejar no encontrado
});

Learn/flight_vs_laravel

Vuelo vs Laravel

¿Qué es Laravel?

Laravel es un marco de trabajo completo que tiene todas las campanas y silbatos y un asombroso ecosistema enfocado en el desarrollador, pero a un costo en rendimiento y complejidad. El objetivo de Laravel es que el desarrollador tenga el más alto nivel de productividad y que las tareas comunes sean fáciles. Laravel es una excelente opción para desarrolladores que buscan construir una aplicación web empresarial completa. Eso conlleva algunos compromisos, especialmente en términos de rendimiento y complejidad. Aprender los conceptos básicos de Laravel puede ser fácil, pero adquirir competencia en el marco de trabajo puede llevar algún tiempo.

También hay tantos módulos de Laravel que los desarrolladores a menudo sienten que la única forma de resolver problemas es a través de estos módulos, cuando realmente podrían usar otra biblioteca o escribir su propio código.

Pros en comparación con Vuelo

Contras en comparación con Vuelo

Learn/migrating_to_v3

Migrating to v3

La compatibilidad con versiones anteriores se ha mantenido en su mayor parte, pero hay algunos cambios de los que debes ser consciente al migrar de v2 a v3.

Comportamiento del almacenamiento en búfer de salida (3.5.0)

El almacenamiento en búfer de salida es el proceso por el cual la salida generada por un script de PHP se almacena en un búfer (interno de PHP) antes de ser enviada al cliente. Esto te permite modificar la salida antes de enviarla al cliente.

En una aplicación MVC, el Controlador es el "gestor" y se encarga de lo que hace la vista. Tener una salida generada fuera del controlador (o en algunos casos de forma anónima en Flight) rompe el patrón MVC. Este cambio es para que se ajuste más al patrón MVC y hace que el marco sea más predecible y fácil de usar.

En v2, el almacenamiento en búfer de salida se manejaba de una manera en la que no cerraba consistentemente su propio búfer de salida, lo que hacía que las pruebas unitarias y la transmisión fueran más difíciles. Para la mayoría de los usuarios, este cambio puede que en realidad no les afecte. Sin embargo, si estás haciendo eco de contenido fuera de las llamadas de retorno y controladores (por ejemplo, en un gancho), es probable que tengas problemas. Hacer eco de contenido en ganchos y antes de que el marco realmente se ejecute puede haber funcionado en el pasado, pero no funcionará en el futuro.

Donde podrías tener problemas

// index.php
require 'vendor/autoload.php';

// solo un ejemplo
define('START_TIME', microtime(true));

function hello() {
    echo 'Hola Mundo';
}

Flight::map('hello', 'hello');
Flight::after('hello', function(){
    // esto en realidad estará bien
    echo '<p>Esta frase Hola Mundo fue traída a usted por la letra "H"</p>';
});

Flight::before('start', function(){
    // cosas como esta causarán un error
    echo '<html><head><title>Mi Página</title></head><body>';
});

Flight::route('/', function(){
    // esto está bien
    echo 'Hola Mundo';

    // Esto también debería estar bien
    Flight::hello();
});

Flight::after('start', function(){
    // esto causará un error
    echo '<div>Su página se cargó en '.(microtime(true) - START_TIME).' segundos</div></body></html>';
});

Activando el Comportamiento de Renderizado v2

¿Todavía puedes mantener tu código antiguo tal como está sin necesidad de reescribirlo para que funcione con v3? ¡Sí, puedes! Puedes activar el comportamiento de renderizado v2 estableciendo la opción de configuración flight.v2.output_buffering en true. Esto te permitirá seguir usando el antiguo comportamiento de renderizado, pero se recomienda corregirlo de aquí en adelante. En la versión 4 del marco, esto será eliminado.

// 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>Mi Página</title></head><body>';
});

// más código

Cambios en el Despachador (3.7.0)

Si has estado llamando directamente a 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 una manera más orientada a objetos para que los Contenedores de Inyección de Dependencias puedan ser utilizados de una forma más sencilla. Si necesitas invocar un método de manera 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() (3.10.0)

El comportamiento predeterminado antes de la versión 3.10.0 era limpiar tanto los encabezados como el cuerpo de la respuesta. Esto fue cambiado para limpiar solo el cuerpo de la respuesta. Si necesitas limpiar también los encabezados, puedes usar Flight::response()->clear().

Learn/configuration

Configuración

Puede personalizar ciertos comportamientos de Flight configurando valores de configuración a través del método 'set'.

Flight::set('flight.log_errors', true);

Configuraciones Disponibles

La siguiente es una lista de todas las configuraciones disponibles:

Configuración del Loader

Adicionalmente, hay otra configuración del cargador. Esto le permitirá cargar clases con _ en el nombre de la clase.

// Habilitar la carga de clase con guiones bajos
// Predeterminado a true
Loader::$v2ClassLoading = false;

Variables

Flight le permite guardar variables para que puedan ser utilizadas en cualquier lugar de su aplicación.

// Guarde su variable
Flight::set('id', 123);

// En otro lugar de su aplicación
$id = Flight::get('id');

Para ver si una variable ha sido establecida, puede hacerlo así:

if (Flight::has('id')) {
  // Hacer algo
}

Puede borrar una variable haciendo:

// Borra la variable id
Flight::clear('id');

// Borra todas las variables
Flight::clear();

Flight también utiliza variables con fines de configuración.

Flight::set('flight.log_errors', true);

Manejo de Errores

Errores y Excepciones

Todos los errores y excepciones son capturados por Flight y pasados al método 'error'. El comportamiento predeterminado es enviar una respuesta genérica de 'HTTP 500 Internal Server Error' con alguna información de error.

Puede anular este comportamiento según sus 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);

No Encontrado

Cuando no se puede encontrar una URL, Flight llama al método 'notFound'. El comportamiento predeterminado es enviar una respuesta de 'HTTP 404 Not Found' con un mensaje simple.

Puede anular este comportamiento según sus necesidades:

Flight::map('notFound', function () {
  // Manejar no encontrado
});

Learn/security

Seguridad

La seguridad es un gran problema cuando se trata de aplicaciones web. Quieres asegurarte de que tu aplicación sea segura y que los datos de tus usuarios estén a salvo. Flight proporciona una serie de características para ayudarte a asegurar tus aplicaciones web.

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 maneras de 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.

Agregar a Mano

Puedes agregar manualmente estos encabezados utilizando el método header en el objeto Flight\Response.

// Establece el encabezado X-Frame-Options para prevenir clickjacking
Flight::response()->header('X-Frame-Options', 'SAMEORIGIN');

// Establece 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'");

// Establece el encabezado X-XSS-Protection para prevenir XSS
Flight::response()->header('X-XSS-Protection', '1; mode=block');

// Establece el encabezado X-Content-Type-Options para prevenir MIME sniffing
Flight::response()->header('X-Content-Type-Options', 'nosniff');

// Establece el encabezado Referrer-Policy para controlar cuánta información de referencia se envía
Flight::response()->header('Referrer-Policy', 'no-referrer-when-downgrade');

// Establece el encabezado Strict-Transport-Security para forzar HTTPS
Flight::response()->header('Strict-Transport-Security', 'max-age=31536000; includeSubDomains; preload');

// Establece el encabezado Permissions-Policy para controlar qué características y APIs pueden usarse
Flight::response()->header('Permissions-Policy', 'geolocation=()');

Estos se pueden agregar al principio de tus archivos bootstrap.php o index.php.

Agregar como un Filtro

También puedes agregarlos en un filtro/gatillo como el siguiente:

// Agrega 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 Middleware

También puedes agregarlos como una clase middleware. Esta es una buena manera de mantener tu código limpio y organizado.

// app/middleware/SecurityHeadersMiddleware.php

namespace app\middleware;

class SecurityHeadersMiddleware
{
    public function before(array $params): void
    {
        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=()');
    }
}

// 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 solo a rutas específicas.
Flight::group('', function(Router $router) {
    $router->get('/users', [ 'UserController', 'getUsers' ]);
    // más rutas
}, [ new SecurityHeadersMiddleware() ]);

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 utilizando 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 se envíe el formulario.

// Genera un token CSRF y almacénalo en la sesión del usuario
// (suponiendo 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', \Ghostff\Session\Session::class);

// Solo necesitas generar un único token por sesión (para que funcione 
// en 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)) );
}
<!-- Usa 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.

// Establece una función personalizada para mostrar el token CSRF
// Nota: La vista ha sido configurada con Latte como motor de vista
Flight::view()->addFunction('csrf', function() {
    $csrfToken = Flight::session()->get('csrf_token');
    return new \Latte\Runtime\Html('<input type="hidden" name="csrf_token" value="' . $csrfToken . '">');
});

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>

¿Corto y sencillo, verdad?

Verificar el Token CSRF

Puedes verificar el token CSRF usando 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') {

        // captura el token csrf de los valores del formulario
        $token = Flight::request()->data->csrf_token;
        if($token !== Flight::session()->get('csrf_token')) {
            Flight::halt(403, 'Token CSRF inválido');
            // o para una respuesta JSON
            Flight::jsonHalt(['error' => 'Token CSRF inválido'], 403);
        }
    }
});

O puedes usar una clase middleware:

// app/middleware/CsrfMiddleware.php

namespace app\middleware;

class CsrfMiddleware
{
    public function before(array $params): void
    {
        if(Flight::request()->method == 'POST') {
            $token = Flight::request()->data->csrf_token;
            if($token !== Flight::session()->get('csrf_token')) {
                Flight::halt(403, 'Token CSRF inválido');
            }
        }
    }
}

// index.php o donde tengas tus rutas
Flight::group('', function(Router $router) {
    $router->get('/users', [ 'UserController', 'getUsers' ]);
    // más rutas
}, [ new CsrfMiddleware() ]);

Cross Site Scripting (XSS)

Cross Site Scripting (XSS) es un tipo de ataque donde un sitio web malicioso puede inyectar código en tu sitio web. La mayoría de estas oportunidades provienen de valores de formularios que tus usuarios finales llenarán. ¡Nunca debes confiar 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, puedes escapar fácilmente la salida para prevenir ataques XSS.

// Supongamos que el usuario es astuto y trata de usar esto como su nombre
$name = '<script>alert("XSS")</script>';

// Esto escapará la salida
Flight::view()->set('name', $name);
// Esto mostrará: &lt;script&gt;alert(&quot;XSS&quot;)&lt;/script&gt;

// Si usas algo como Latte registrado como tu clase de vista, también escapará esto automáticamente.
Flight::view()->render('template', ['name' => $name]);

Inyección de SQL

La inyección de SQL 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. Nuevamente, jamás debes confiar en la entrada de tus usuarios. Siempre asume que están en busca de venganza. Puedes usar declaraciones preparadas en tus objetos PDO para prevenir la inyección de SQL.

// Suponiendo 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 se puede hacer 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 ]);

// Solo prométeme que nunca, NUNCA harás algo como esto...
$users = Flight::db()->fetchAll("SELECT * FROM users WHERE username = '{$username}' LIMIT 5");
// porque ¿qué pasa si $username = "' OR 1=1; -- "; 
// Después de que se construye la consulta, 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 de SQL muy común que devolverá todos los usuarios.

CORS

El intercambio de recursos de origen cruzado (CORS) es un mecanismo que permite que muchos recursos (por ejemplo, fuentes, JavaScript, etc.) en una página web sean solicitados desde otro dominio fuera del dominio del que se originó el recurso. Flight no tiene funcionalidad incorporada, pero esto se puede manejar fácilmente con un gancho que se ejecute antes de que se llame al 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 debe ejecutarse antes de que se inicie el arranque.
Flight::before('start', [ $CorsUtil, 'setupCors' ]);

Manejo de Errores

Oculta detalles sensibles de los errores en producción para evitar filtrar información a los atacantes.

// En tu bootstrap.php o index.php

// en flightphp/skeleton, esto está en app/config/config.php
$environment = ENVIRONMENT;
if ($environment === 'production') {
    ini_set('display_errors', 0); // Desactivar 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, 'Acceso denegado');

Sanitización de Entrada

Nunca confíes en la entrada del usuario. Sanitiza antes de procesar para evitar que datos maliciosos se cuelen.


// Supongamos que hay una solicitud $_POST con $_POST['input'] y $_POST['email']

// Sanitiza una entrada de cadena
$clean_input = filter_var(Flight::request()->data->input, FILTER_SANITIZE_STRING);
// Sanitiza un email
$clean_email = filter_var(Flight::request()->data->email, FILTER_SANITIZE_EMAIL);

Hashing de Contraseña

Almacena las contraseñas de forma segura y verifícalas de manera segura utilizando las funciones integradas de PHP.

$password = Flight::request()->data->password;
// Hashea una contraseña al almacenarla (por ejemplo, durante el registro)
$hashed_password = password_hash($password, PASSWORD_DEFAULT);

// Verifica una contraseña (por ejemplo, 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 limitando las tasas de solicitud con un caché.

// Suponiendo que tienes flightphp/cache instalado y registrado
// Usando flightphp/cache en un middleware
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, 'Demasiadas solicitudes');
    }

    $cache->set($key, $attempts + 1, 60); // Reiniciar después de 60 segundos
});

Conclusión

La seguridad es un gran problema y es importante asegurarte de que tus aplicaciones web sean seguras. Flight proporciona una serie de características para ayudarte a asegurar tus aplicaciones web, pero es importante siempre estar alerta y asegurarte de que estás haciendo todo lo posible para mantener seguros los datos de tus usuarios. Siempre asume lo peor y nunca confíes en la entrada de tus usuarios. Siempre escapa la salida y utiliza sentencias preparadas para prevenir la inyección de SQL. Siempre usa middleware para proteger tus rutas de ataques CSRF y CORS. Si haces todas estas cosas, estarás bien encaminado para construir aplicaciones web seguras.

Learn/overriding

Anulando

Flight te permite anular su funcionalidad predeterminada para adaptarla a tus propias necesidades, sin necesidad de modificar ningún código.

Por ejemplo, cuando Flight no puede encontrar una URL que coincida con una ruta, invoca el método notFound que envía una respuesta genérica de HTTP 404. Puedes anular este comportamiento utilizando el método map:

Flight::map('notFound', function() {
  // Mostrar página de error 404 personalizada
  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:

// Registra tu clase personalizada
Flight::register('router', MyRouter::class);

// Cuando Flight carga la instancia del enrutador, cargará tu clase
$myrouter = Flight::router();

Sin embargo, los métodos del framework como map y register no se pueden anular. Obtendrás un error si intentas hacerlo.

Learn/routing

Enrutamiento

Nota: ¿Quieres entender más sobre el enrutamiento? Consulta la página de "¿por qué un framework?" para una explicación más detallada.

El enrutamiento básico en Flight se realiza haciendo coincidir un patrón de URL con una función de devolución de llamada o un arreglo de una clase y un método.

Flight::route('/', function(){
    echo '¡hola mundo!';
});

Las rutas se emparejan en el orden en que se definen. La primera ruta que coincida con una solicitud será invocada.

Devoluciones de llamada/Funciones

La devolución de llamada puede ser cualquier objeto que sea invocable. Así que puedes usar una función normal:

function hello() {
    echo '¡hola mundo!';
}

Flight::route('/', 'hello');

Clases

También puedes usar un método estático de una clase:

class Greeting {
    public static function hello() {
        echo '¡hola mundo!';
    }
}

Flight::route('/', [ 'Greeting','hello' ]);

O creando primero un objeto y luego llamando al método:


// Greeting.php
class Greeting
{
    public function __construct() {
        $this->name = 'John Doe';
    }

    public function hello() {
        echo "¡Hola, {$this->name}!";
    }
}

// index.php
$greeting = new Greeting();

Flight::route('/', [ $greeting, 'hello' ]);
// También puedes hacer esto sin crear el objeto primero
// Nota: No se inyectarán argumentos en el constructor
Flight::route('/', [ 'Greeting', 'hello' ]);
// Además, puedes usar esta sintaxis más corta
Flight::route('/', 'Greeting->hello');
// o
Flight::route('/', Greeting::class.'->hello');

Inyección de Dependencias a través de DIC (Contenedor de Inyección de Dependencias)

Si deseas utilizar la 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 utilizando el contenedor para crear tu objeto o puedes usar cadenas para definir la clase y el método a llamar. Puedes consultar la página de Inyección de Dependencias para obtener 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) {
        // hacer algo con $this->pdoWrapper
        $name = $this->pdoWrapper->fetchField("SELECT name FROM users WHERE id = ?", [ $id ]);
        echo "¡Hola, mundo! ¡Mi nombre es {$name}!";
    }
}

// index.php

// Configura el contenedor con los parámetros que necesites
// Consulta la página de Inyección de Dependencias para obtener 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',
        'contraseña'
    ]
]);

// 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();

Enrutamiento por Método

Por defecto, los patrones de ruta se comparan con 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 'He recibido una solicitud GET.';
});

Flight::route('POST /', function () {
  echo 'He recibido una solicitud POST.';
});

// 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() { /* código */ });
// Flight::patch('/', function() { /* código */ });
// Flight::put('/', function() { /* código */ });
// Flight::delete('/', function() { /* código */ });

También puedes mapear múltiples métodos a una sola devolución de llamada usando un delimitador |:

Flight::route('GET|POST /', function () {
  echo 'He recibido ya sea una solicitud GET o POST.';
});

Además, puedes obtener el objeto Router que tiene algunos métodos auxiliares para que los uses:


$router = Flight::router();

// mapea todos los métodos
$router->map('/', function() {
    echo '¡hola mundo!';
});

// solicitud GET
$router->get('/users', function() {
    echo 'usuarios';
});
// $router->post();
// $router->put();
// $router->delete();
// $router->patch();

Expresiones Regulares

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 nombrados, o parámetros nombrados con expresiones regulares, ya que son más legibles y fáciles de mantener.

Parámetros Nombrados

Puedes especificar parámetros nombrados en tus rutas que se pasarán a tu función de devolución de llamada. Esto es más por la legibilidad de la ruta que por cualquier otra cosa. Por favor, consulta la sección siguiente sobre una advertencia importante.

Flight::route('/@name/@id', function (string $name, string $id) {
  echo "¡hola, $name ($id)!"; 
});

También puedes incluir expresiones regulares en tus parámetros nombrados 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: No se admiten grupos de coincidencias regex () con parámetros posicionales. :'(

Advertencia Importante

Aunque en el ejemplo anterior, parece que @name está directamente vinculado 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. Así que, 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 "¡hola, $name ($id)!"; 
});

Y si fueras a la siguiente URL: /bob/123, la salida sería ¡hola, 123 (bob)!. Por favor, ten cuidado al configurar tus rutas y tus funciones de devolución de llamada.

Parámetros Opcionales

Puedes especificar parámetros nombrados que son opcionales para la coincidencia envolviendo segmentos entre paréntesis.

Flight::route(
  '/blog(/@year(/@month(/@day)))',
  function(?string $year, ?string $month, ?string $day) {
    // Esto coincidirá con las siguientes URL:
    // /blog/2012/12/10
    // /blog/2012/12
    // /blog/2012
    // /blog
  }
);

Cualquier parámetro opcional que no coincida se pasará como NULL.

Comodines

La coincidencia se realiza solo en segmentos individuales de la URL. Si deseas hacer 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 () {
  // Hacer algo
});

Pasar

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") {
    // Continuar a la siguiente ruta
    return true;
  }
});

Flight::route('/user/*', function () {
  // Esto se llamará
});

Alias de Ruta

Puedes asignar un alias a una ruta, para que la URL pueda generarse de forma dinámica más tarde en tu código (como una plantilla, por ejemplo).

Flight::route('/users/@id', function($id) { echo 'usuario:'.$id; }, false, 'user_view');

// más tarde en el código en alguna parte
Flight::getUrl('user_view', [ 'id' => 5 ]); // devolverá '/users/5'

Esto es especialmente útil si tu URL cambia. En el ejemplo anterior, digamos que los usuarios se movieron a /admin/users/@id en su lugar. Con el alias en su lugar, no tienes que cambiar en ningún lugar donde referencias el alias porque el alias ahora devolverá /admin/users/5 como en el ejemplo anterior.

El alias de ruta también funciona en grupos:

Flight::group('/users', function() {
    Flight::route('/@id', function($id) { echo 'usuario:'.$id; }, false, 'user_view');
});

// más tarde en el código en alguna parte
Flight::getUrl('user_view', [ 'id' => 5 ]); // devolverá '/users/5'

Información de Ruta

Si deseas inspeccionar la información de la ruta coincidente, puedes solicitar que el objeto de ruta se pase a tu función de devolución de llamada pasando true como tercer parámetro en el método de ruta. El objeto de ruta siempre será el último parámetro pasado a tu función de devolución de llamada.

Flight::route('/', function(\flight\net\Route $route) {
  // Array de métodos HTTP coincidentes
  $route->methods;

  // Array de parámetros nombrados
  $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 lo necesitas
  $route->pattern;

  // Muestra qué middleware está asignado a esto
  $route->middleware;

  // Muestra el alias asignado a esta ruta
  $route->alias;
}, true);

Agrupación de Rutas

Puede haber momentos en los que desees agrupar rutas relacionadas (como /api/v1). Puedes hacer esto 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! Consulta el contexto del 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! Consulta el contexto del objeto a continuación
    Flight::route('GET /users', function () {
      // Coincide con GET /api/v2/users
    });
  });
});

Agrupación con Contexto de Objeto

Aún puedes utilizar la agrupación de rutas con el objeto Engine de la siguiente manera:

$app = new \flight\Engine();
$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
  });
});

Enrutamiento de 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 se crearán las siguientes rutas:

[
      'index' => 'GET ',
      'create' => 'GET /create',
      'store' => 'POST ',
      'show' => 'GET /@id',
      'edit' => 'GET /@id/edit',
      'update' => 'PUT /@id',
      'destroy' => 'DELETE /@id'
]

Y tu controlador se verá así:

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 añadidas con runway al ejecutar php 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/ daría como resultado un aliasBase de users. Cuando se crean estas rutas, los alias son users.index, users.create, etc. Si deseas 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 deseas crear utilizando las opciones only y except.

Flight::resource('/users', UsersController::class, [ 'only' => [ 'index', 'show' ] ]);
Flight::resource('/users', UsersController::class, [ 'except' => [ 'create', 'store', 'edit', 'update', 'destroy' ] ]);

Estas son opciones básicamente de listas blancas y negras para que puedas especificar qué rutas deseas crear.

Middleware

También puedes especificar middleware que se ejecute en cada una de las rutas creadas por el método resource.

Flight::resource('/users', UsersController::class, [ 'middleware' => [ MyAuthMiddleware::class ] ]);

Transmisión

Ahora puedes transmitir respuestas al cliente utilizando el método streamWithHeaders(). Esto es útil para enviar archivos grandes, procesos de larga duración o para generar grandes respuestas. Transmitir una ruta se maneja de manera un poco diferente a una ruta regular.

Nota: La transmisión de respuestas solo está disponible si tienes flight.v2.output_buffering configurado en falso.

Transmitir con Encabezados Manuales

Puedes transmitir una respuesta al cliente utilizando el método stream() en una ruta. Si haces esto, debes establecer todos los métodos a mano antes de enviar cualquier cosa al cliente. Esto se hace con la función header() de php o con el método Flight::response()->setRealHeader().

Flight::route('/@filename', function($filename) {

    // obviamente sanitizarías la ruta y demás.
    $fileNameSafe = basename($filename);

    // Si tienes encabezados adicionales que establecer aquí después de que se haya ejecutado la ruta
    // debes definirlos antes de que se imprima cualquier cosa.
    // Todos deben ser una llamada sin procesar a la función header() o 
    // una llamada a Flight::response()->setRealHeader()
    header('Content-Disposition: attachment; filename="'.$fileNameSafe.'"');
    // o
    Flight::response()->setRealHeader('Content-Disposition', 'attachment; filename="'.$fileNameSafe.'"');

    $fileData = file_get_contents('/some/path/to/files/'.$fileNameSafe);

    // Captura de errores y demás
    if(empty($fileData)) {
        Flight::halt(404, 'Archivo no encontrado');
    }

    // establece manualmente la longitud del contenido si lo deseas
    header('Content-Length: '.filesize($filename));

    // Transmite los datos al cliente
    echo $fileData;

// Esta es la línea mágica aquí
})->stream();

Transmitir 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 desees aquí
    // solo debes usar header() o Flight::response()->setRealHeader()

    // sin importar cómo 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 necesario para enviar los datos al cliente
        ob_flush();
    }
    echo '}';

// Así es como 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, predeterminado es 200
    'status' => 200
]);

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

Contras en comparación con Vuelo

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/variables

Variables

Flight permite guardar variables para que puedan ser utilizadas en cualquier lugar de tu aplicación.

// Guarda tu variable
Flight::set('id', 123);

// En otro lugar de tu aplicación
$id = Flight::get('id');

Para verificar si una variable ha sido establecida, puedes hacer lo siguiente:

if (Flight::has('id')) {
  // Haz algo
}

Puedes limpiar una variable haciendo:

// Elimina la variable id
Flight::clear('id');

// Elimina todas las variables
Flight::clear();

Flight también utiliza variables con propósitos de configuración.

Flight::set('flight.log_errors', true);

Learn/dependency_injection_container

Contenedor de Inyección de Dependencias

Introducción

El Contenedor de Inyección de Dependencias (DIC) es una herramienta potente que te permite gestionar las dependencias de tu aplicación. Es un concepto clave en los marcos de PHP modernos y se utiliza para gestionar la instanciación y configuración de objetos. Algunos ejemplos de bibliotecas DIC son: Dice, Pimple, PHP-DI y league/container.

Un DIC es una forma elegante de decir que te permite crear y gestionar tus clases en una ubicación centralizada. Esto es útil cuando necesitas pasar el mismo objeto a varias clases (como tus controladores). Un ejemplo sencillo podría ayudar a entender esto mejor.

Ejemplo 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());
    }
}

$User = new UserController(new PDO('mysql:host=localhost;dbname=test', 'user', 'pass'));
Flight::route('/user/@id', [ $UserController, 'view' ]);

Flight::start();

Se puede 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, descubrirás que estás creando el mismo objeto PDO en múltiples lugares. Aquí es donde resulta útil un DIC.

Aquí tienes el mismo ejemplo utilizando un DIC (usando Dice):


require 'vendor/autoload.php';

// misma clase que arriba. Sin cambios
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;
// ¡no olvides volver a asignarlo a sí mismo como se muestra abajo!
$container = $container->addRule('PDO', [
    // shared significa que el mismo objeto se devolverá cada vez
    'shared' => true,
    'constructParams' => ['mysql:host=localhost;dbname=test', 'user', 'pass' ]
]);

// Esto registra el controlador de 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', 'view' ]);
// o alternativamente puedes definir la ruta así
Flight::route('/user/@id', 'UserController->view');
// o
Flight::route('/user/@id', 'UserController::view');

Flight::start();

Apuesto a que puedes estar pensando que se añadió mucho código extra al ejemplo. La magia reside en 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 recibirán automáticamente inyectado!!!
Flight::route('/empresa/@id', 'CompanyController->view');
Flight::route('/organización/@id', 'OrganizationController->view');
Flight::route('/categoría/@id', 'CategoryController->view');
Flight::route('/ajustes', 'SettingsController->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. ¡Este es un gran beneficio al escribir pruebas para tu aplicación!

PSR-11

Flight también puede utilizar cualquier contenedor compatible con PSR-11. Esto significa que puedes usar cualquier contenedor que implemente la interfaz PSR-11. Aquí tienes un ejemplo utilizando 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();

Aunque pueda ser un poco más detallado que el ejemplo anterior con Dice, aún se logra el mismo resultado con los mismos beneficios.

Controlador DIC Personalizado

También puedes crear tu propio controlador DIC. Esto es útil si tienes un contenedor personalizado que quieres utilizar y que no es compatible con PSR-11 (Dice). Consulta el ejemplo básico para ver cómo hacerlo.

Además, existen algunas configuraciones útiles que facilitarán tu vida al usar Flight.

Instancia del Motor

Si estás utilizando la instancia del Engine en tus controladores/middleware, así es como lo configurarías:


// En algún lugar de tu archivo de inicio
$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 del Motor en tus controladores/middleware

class MyController {
    public function __construct(Engine $app) {
        $this->app = $app;
    }

    public function index() {
        $this->app->render('index');
    }
}

Añadiendo 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í tienes un ejemplo:


$container = new \Dice\Dice;
// Si no necesitas inyectar nada en tu clase
// ¡no necesitas definir nada!
Flight::registerContainerHandler(function($class, $params) use ($container) {
    return $container->create($class, $params);
});

class MyCustomClass {
    public function parseThing() {
        return 'cosa';
    }
}

class UserController {

    protected MyCustomClass $MyCustomClass;

    public function __construct(MyCustomClass $MyCustomClass) {
        $this->MyCustomClass = $MyCustomClass;
    }

    public function index() {
        echo $this->MyCustomClass->parseThing();
    }
}

Flight::route('/usuario', 'UserController->index');

Learn/middleware

Middleware de Ruta

Flight admite middleware de ruta y de grupo de ruta. El middleware es una función que se ejecuta 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 su código, o para validar que el usuario tiene permiso para acceder a la ruta.

Middleware Básico

Aquí tienes un ejemplo básico:

// Si solo proporciona una función anónima, se ejecutará antes de la devolución de llamada de la ruta.
// no hay funciones de middleware "después" excepto para las clases (ver abajo)
Flight::route('/path', function() { echo '¡Aquí estoy!'; })->addMiddleware(function() {
    echo '¡Middleware primero!';
});

Flight::start();

// ¡Esto mostrará "¡Middleware primero! ¡Aquí estoy!"

Hay algunas notas muy importantes sobre el middleware que debes tener en cuenta antes de usarlo:

Clases de Middleware

El middleware también se puede registrar como una clase. Si necesitas la funcionalidad "después", deb usar una clase.

class MyMiddleware {
    public function before($params) {
        echo '¡Middleware primero!';
    }

    public function after($params) {
        echo '¡Middleware último!';
    }
}

$MyMiddleware = new MyMiddleware();
Flight::route('/path', function() { echo '¡Aquí estoy! '; })->addMiddleware($MyMiddleware); // también ->addMiddleware([ $MyMiddleware, $MyMiddleware2 ]);

Flight::start();

// Esto mostrará "¡Middleware primero! ¡Aquí estoy! ¡Middleware último!"

Manejo de Errores de Middleware

Digamos 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 algunas opciones a tu disposición:

  1. Puedes devolver false desde la función de middleware y Flight devolverá automáticamente un error 403 Prohibido, pero sin personalización.
  2. Puedes redirigir al usuario a una página de inicio de sesión usando Flight::redirect().
  3. Puedes crear un error personalizado dentro del middleware y detener la ejecución de la ruta.

Ejemplo Básico

Aquí tienes un ejemplo simple de return false;:

class MyMiddleware {
    public function before($params) {
        if (isset($_SESSION['user']) === false) {
            return false;
        }

        // dado que es verdadero, todo continúa
    }
}

Ejemplo de Redirección

Aquí tienes un ejemplo de redirigir al usuario a una página de inicio de sesión:

class MyMiddleware {
    public function before($params) {
        if (isset($_SESSION['user']) === false) {
            Flight::redirect('/login');
            exit;
        }
    }
}

Ejemplo de Error Personalizado

Digamos que necesitas lanzar un error JSON porque estás construyendo una API. Puedes hacerlo de la siguiente manera:

class MyMiddleware {
    public function before($params) {
        $authorization = Flight::request()->headers['Authorization'];
        if(empty($authorization)) {
            Flight::jsonHalt(['error' => 'Debes iniciar sesión para acceder a esta página.'], 403);
            // o
            Flight::json(['error' => 'Debes iniciar sesión para acceder a esta página.'], 403);
            exit;
            // o
            Flight::halt(403, json_encode(['error' => 'Debes iniciar sesión para acceder a esta página.']);
        }
    }
}

Agrupación de 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 varias rutas por un middleware de autenticación para verificar la clave de API en el encabezado.


// añadido al final del método de grupo
Flight::group('/api', function() {

    // Esta ruta con aspecto "vacío" en realidad coincidirá con /api
    Flight::route('', function() { echo 'api'; }, false, 'api');
    // Esto coincidirá con /api/usuarios
    Flight::route('/usuarios', function() { echo 'usuarios'; }, false, 'usuarios');
    // Esto coincidirá con /api/usuarios/1234
    Flight::route('/usuarios/@id', function($id) { echo 'usuario:'.$id; }, false, 'user_view');
}, [ new ApiAuthMiddleware() ]);

Si deseas aplicar un middleware global a todas tus rutas, puedes agregar un grupo "vacío":


// añadido al final del método de grupo
Flight::group('', function() {

    // Esto sigue siendo /usuarios
    Flight::route('/usuarios', function() { echo 'usuarios'; }, false, 'usuarios');
    // Y esto sigue siendo /usuarios/1234
    Flight::route('/usuarios/@id', function($id) { echo 'usuario:'.$id; }, false, 'user_view');
}, [ new ApiAuthMiddleware() ]);

Learn/filtering

# Filtrado

Flight te permite filtrar métodos antes y después de que sean llamados. No hay
ganchos predefinidos que necesites memorizar. Puedes filtrar cualquiera de los métodos
predeterminados del marco de trabajo, así como cualquier método personalizado que hayas mapeado.

Una función de filtro se ve así:

```php
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 añadir tantos filtros como quieras a cualquier método. Serán llamados en el orden en el que son declarados.

Aquí tienes un ejemplo del proceso de filtrado:

// Mapear un método personalizado
Flight::map('hello', function (string $name) {
  return "¡Hola, $name!";
});

// Agregar un filtro antes
Flight::before('hello', function (array &$params, string &$output): bool {
  // Manipular el parámetro
  $params[0] = 'Fred';
  return true;
});

// Agregar un filtro después
Flight::after('hello', function (array &$params, string &$output): bool {
  // Manipular la salida
  $output .= " ¡Que tengas un buen día!";
  return true;
});

// Invocar el método personalizado
echo Flight::hello('Bob');

Esto debería mostrar:

¡Hola Fred! ¡Que tengas un buen día!

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 'uno';
  return true;
});

Flight::before('start', function (array &$params, string &$output): bool {
  echo 'dos';

  // Esto terminará la cadena
  return false;
});

// Esto no se llamará
Flight::before('start', function (array &$params, string &$output): bool {
  echo 'tres';
  return true;
});

Nota, los métodos principales como map y register no pueden ser filtrados porque son llamados directamente y no son invocados dinámicamente.

Learn/requests

Solicitudes

Flight encapsula la solicitud HTTP en un solo objeto, que puede ser accedido haciendo:

$request = Flight::request();

Casos de Uso Típicos

Cuando estás trabajando con una solicitud en una aplicación web, típicamente querrás extraer un encabezado, o un parámetro $_GET o $_POST, o quizás incluso el cuerpo de la solicitud en bruto. Flight proporciona una interfaz simple para hacer todas estas cosas.

Aquí hay un ejemplo de obtención de un parámetro de la cadena de consulta:

Flight::route('/search', function(){
    $keyword = Flight::request()->query['keyword'];
    echo "Estás buscando: $keyword";
    // consulta una base de datos o algo más con el $keyword
});

Aquí hay un ejemplo de tal vez un formulario con un método POST:

Flight::route('POST /submit', function(){
    $name = Flight::request()->data['name'];
    $email = Flight::request()->data['email'];
    echo "Has enviado: $name, $email";
    // guarda en una base de datos o algo más con el $name y $email
});

Propiedades del Objeto Solicitud

El objeto solicitud proporciona las siguientes propiedades:

Puedes acceder a las propiedades query, data, cookies y files como matrices u objetos.

Así que, para obtener un parámetro de la cadena de consulta, puedes hacer:

$id = Flight::request()->query['id'];

O puedes hacer:

$id = Flight::request()->query->id;

Cuerpo de Solicitud RAW

Para obtener el cuerpo de la solicitud HTTP en bruto, por ejemplo al manejar solicitudes PUT, puedes hacer:

$body = Flight::request()->getBody();

Entrada JSON

Si envías una solicitud con el tipo application/json y los datos {"id": 123} estará disponible desde la propiedad data:

$id = Flight::request()->data->id;

$_GET

Puedes acceder al arreglo $_GET a través de la propiedad query:

$id = Flight::request()->query['id'];

$_POST

Puedes acceder al arreglo $_POST a través de la propiedad data:

$id = Flight::request()->data['id'];

$_COOKIE

Puedes acceder al arreglo $_COOKIE a través de la propiedad cookies:

$myCookieValue = Flight::request()->cookies['myCookieName'];

$_SERVER

Hay un acceso directo disponible para acceder al arreglo $_SERVER a través del método getVar():


$host = Flight::request()->getVar['HTTP_HOST'];

Accediendo a Archivos Subidos a través de $_FILES

Puedes acceder a archivos subidos a través de la propiedad files:

$uploadedFile = Flight::request()->files['myFile'];

Procesamiento de Cargas de Archivos

Puedes procesar cargas de archivos utilizando el framework con algunos métodos de ayuda. Básicamente se reduce a extraer los datos del archivo de la solicitud y moverlos a una nueva ubicación.

Flight::route('POST /upload', function(){
    // Si tuvieras 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 tienes múltiples archivos subidos, puedes recorrerlos:

Flight::route('POST /upload', function(){
    // Si tuvieras 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 valida y desinfecta la entrada del usuario, especialmente al tratar con cargas de archivos. Siempre valida el tipo de extensiones que permitirás que se suban, pero también debes validar los "bytes mágicos" del archivo para asegurarte de que es realmente el tipo de archivo que el usuario dice que es. Hay artículos y bibliotecas disponibles para ayudar con esto.

Encabezados de Solicitud

Puedes acceder a los encabezados de solicitud usando el método getHeader() o getHeaders():


// Tal vez necesites el encabezado de Autorización
$host = Flight::request()->getHeader('Authorization');
// o
$host = Flight::request()->header('Authorization');

// Si necesitas obtener todos los encabezados
$headers = Flight::request()->getHeaders();
// o
$headers = Flight::request()->headers();

Cuerpo de Solicitud

Puedes acceder al cuerpo de la solicitud en bruto utilizando el método getBody():

$body = Flight::request()->getBody();

Método de Solicitud

Puedes acceder al método de solicitud utilizando la propiedad method o el método getMethod():

$method = Flight::request()->method; // en realidad llama a getMethod()
$method = Flight::request()->getMethod();

Nota: El método getMethod() primero obtiene 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.

URLs de Solicitud

Hay un par de métodos auxiliares para ensamblar partes de una URL para tu conveniencia.

URL Completa

Puedes 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

Puedes acceder a la URL base usando el método getBaseUrl():

$url = Flight::request()->getBaseUrl();
// Nota, sin barra final.
// https://example.com

Análisis de Consultas

Puedes pasar una URL al método parseQuery() para analizar la cadena de consulta en un arreglo asociativo:

$query = Flight::request()->parseQuery('https://example.com/some/path?foo=bar');
// ['foo' => 'bar']

Learn/frameworkmethods

Métodos del Framework

Flight está diseñado para ser fácil de usar y entender. Lo siguiente es el conjunto completo de métodos para el framework. Consiste en métodos centrales, que son métodos estáticos regulares, y métodos extensibles, que son métodos asignados que pueden ser filtrados o anulados.

Métodos Centrales

Flight::map(string $nombre, callable $retorno, bool $pasar_ruta = false) // Crea un método personalizado para el framework.
Flight::register(string $nombre, string $clase, array $params = [], ?callable $retorno = null) // Registra una clase a un método del framework.
Flight::before(string $nombre, callable $retorno) // Agrega un filtro antes de un método del framework.
Flight::after(string $nombre, callable $retorno) // Agrega un filtro después de un método del framework.
Flight::path(string $ruta) // Agrega una ruta para la carga automática de clases.
Flight::get(string $clave) // Obtiene una variable.
Flight::set(string $clave, mixed $valor) // Establece una variable.
Flight::has(string $clave) // Verifica si una variable está establecida.
Flight::clear(array|string $clave = []) // Borra una variable.
Flight::init() // Inicializa el framework a sus ajustes predeterminados.
Flight::app() // Obtiene la instancia del objeto de la aplicación

Métodos Extensibles

Flight::start() // Inicia el framework.
Flight::stop() // Detiene el framework y envía una respuesta.
Flight::halt(int $codigo = 200, string $mensaje = '') // Detiene el framework con un código de estado opcional y mensaje.
Flight::route(string $patrón, callable $retorno, bool $pasar_ruta = false) // Asigna un patrón de URL a un retorno.
Flight::group(string $patrón, callable $retorno) // Crea agrupaciones para URLs, el patrón debe ser una cadena.
Flight::redirect(string $url, int $codigo) // Redirige a otra URL.
Flight::render(string $archivo, array $datos, ?string $clave = 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 $tipo = 'string') // Realiza almacenamiento en caché HTTP ETag.
Flight::lastModified(int $tiempo) // Realiza almacenamiento en caché HTTP de Última Modificación.
Flight::json(mixed $datos, int $codigo = 200, bool $codificar = true, string $charset = 'utf8', int $opción) // Envía una respuesta JSON.
Flight::jsonp(mixed $datos, string $parametro = 'jsonp', int $codigo = 200, bool $codificar = true, string $charset = 'utf8', int $opción) // Envía una respuesta JSONP.

Cualquier método personalizado añadido con map y register también puede ser filtrado.

Learn/api

Métodos de la API del Framework

Flight está diseñado para ser fácil de usar y entender. Lo siguiente es 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 fundamentales 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) // Anula el registro de una clase a 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 con su configuración predeterminada.
Flight::app() // Obtiene la instancia del objeto de la 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) // Dispara un evento.

Cualquier método personalizado agregado con map y register también puede ser filtrado. Para ejemplos sobre cómo mapear estos métodos, consulte la guía Extending Flight.

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:

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

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

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/httpcaching

Caché HTTP

Flight proporciona soporte incorporado para el almacenamiento en caché a nivel HTTP. Si se cumple la condición de caché, Flight devolverá una respuesta HTTP 304 No modificado. La próxima vez que el cliente solicite el mismo recurso, se le pedirá que utilice su versión en caché local.

Última modificación

Puedes utilizar el método lastModified y pasar un sello de tiempo UNIX para establecer la fecha y hora en que se modificó por última vez una página. El cliente seguirá utilizando su caché hasta que se cambie el valor de última modificación.

Flight::route('/noticias', function () {
  Flight::lastModified(1234567890);
  echo 'Este contenido se almacenará en caché.';
});

ETag

La caché ETag es similar a Última modificación, excepto que puedes especificar cualquier identificación que desees para el recurso:

Flight::route('/noticias', function () {
  Flight::etag('mi-identificador-único');
  echo 'Este contenido se almacenará en caché.';
});

Ten en cuenta que llamar a lastModified o etag establecerá y comprobará el valor de caché. Si el valor de caché es el mismo entre las solicitudes, Flight enviará inmediatamente una respuesta HTTP 304 y detendrá el procesamiento.

Learn/responses

Respuestas

Flight ayuda a generar parte de los encabezados de respuesta por ti, pero tú mantienes la mayor parte del control sobre lo que envías de vuelta al usuario. A veces puedes acceder al objeto Response directamente, pero la mayor parte del tiempo usarás la instancia de Flight para enviar una respuesta.

Enviando una Respuesta Básica

Flight utiliza 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á "¡Hola, Mundo!" al navegador del usuario
Flight::route('/', function() {
    echo "¡Hola, Mundo!";
});

// HTTP/1.1 200 OK
// Content-Type: text/html
//
// ¡Hola, Mundo!

Como alternativa, puedes llamar al método write() para agregar al cuerpo también.


// Esto enviará "¡Hola, Mundo!" al navegador del usuario
Flight::route('/', function() {
    // Verboso, pero a veces hace el trabajo cuando lo necesitas
    Flight::response()->write("¡Hola, Mundo!");

    // Si deseas recuperar el cuerpo que has establecido en este punto
    // puedes hacerlo de esta manera
    $body = Flight::response()->getBody();
});

Códigos de Estado

Puedes establecer el código de estado de la respuesta utilizando el método status:

Flight::route('/@id', function($id) {
    if($id == 123) {
        Flight::response()->status(200);
        echo "¡Hola, Mundo!";
    } else {
        Flight::response()->status(403);
        echo "Prohibido";
    }
});

Si deseas obtener el código de estado actual, puedes utilizar el método status sin ningún argumento:

Flight::response()->status(); // 200

Estableciendo un Cuerpo de Respuesta

Puedes establecer el cuerpo de la respuesta utilizando el método write, sin embargo, si haces echo o print de algo, será capturado y enviado como el cuerpo de respuesta a través del almacenamiento en búfer.

Flight::route('/', function() {
    Flight::response()->write("¡Hola, Mundo!");
});

// igual que

Flight::route('/', function() {
    echo "¡Hola, Mundo!";
});

Limpiando un Cuerpo de Respuesta

Si deseas limpiar el cuerpo de la respuesta, puedes usar el método clearBody:

Flight::route('/', function() {
    if($someCondition) {
        Flight::response()->write("¡Hola, Mundo!");
    } else {
        Flight::response()->clearBody();
    }
});

Ejecutando un Callback en el Cuerpo de Respuesta

Puedes ejecutar un callback en el cuerpo de la respuesta utilizando el método addResponseBodyCallback:

Flight::route('/users', function() {
    $db = Flight::db();
    $users = $db->fetchAll("SELECT * FROM users");
    Flight::render('users_table', ['users' => $users]);
});

// Esto comprimirá 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 fueron agregados. Debido a que esto puede aceptar cualquier callable, puede aceptar un array de clase [ $class, 'method' ], un 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 utilizando la opción de configuración flight.v2.output_buffering.

Callback de Ruta Específica

Si quieres que esto aplique solo 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 comprimirá 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 {
        // minificar el cuerpo de alguna manera
        return $body;
    }
}

// index.php
Flight::group('/users', function() {
    Flight::route('', function() { /* ... */ });
    Flight::route('/@id', function($id) { /* ... */ });
}, [ new MinifyMiddleware() ]);

Estableciendo un Encabezado de Respuesta

Puedes establecer un encabezado como el tipo de contenido de la respuesta utilizando el método header:


// Esto enviará "¡Hola, Mundo!" 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 "¡Hola, Mundo!";
});

JSON

Flight proporciona soporte para enviar respuestas JSON y JSONP. Para enviar una respuesta JSON pasas algunos datos para que sean codificados en JSON:

Flight::json(['id' => 123]);

Nota: Por defecto, Flight enviará un encabezado Content-Type: application/json con la respuesta. También utilizará las constantes JSON_THROW_ON_ERROR y JSON_UNESCAPED_SLASHES al codificar el JSON.

JSON con Código de Estado

También puedes pasar un código de estado como el segundo argumento:

Flight::json(['id' => 123], 201);

JSON con Formato Bonito

También puedes pasar un argumento en la última posición para habilitar el formato bonito:

Flight::json(['id' => 123], 200, true, 'utf-8', JSON_PRETTY_PRINT);

Si estás cambiando las opciones pasadas a Flight::json() y deseas una sintaxis más simple, simplemente puedes volver a mapear el método JSON:

Flight::map('json', function($data, $code = 200, $options = 0) {
    Flight::_json($data, $code, true, 'utf-8', $options);
}

// Y ahora se puede usar así
Flight::json(['id' => 123], 200, JSON_PRETTY_PRINT);

JSON y Detener la Ejecución (v3.10.0)

Si deseas enviar una respuesta JSON y detener la ejecución, puedes usar el método jsonHalt. Esto es útil en casos donde estás verificando tal vez algún tipo de autorización y si el usuario no está autorizado, puedes enviar una respuesta JSON de inmediato, 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' => 'No autorizado'], 401);
    }

    // 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' => 'No autorizado']));
    }

    // Continúa con el resto de la ruta
});

JSONP

Para las solicitudes JSONP, puedes opcionalmente pasar el nombre del parámetro de consulta que estás utilizando 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, predeterminará a jsonp.

Redirigir a otra URL

Puedes redirigir la solicitud actual utilizando el método redirect() y pasando una nueva URL:

Flight::redirect('/new/location');

Por defecto, Flight envía un código de estado HTTP 303 ("Ver Otro"). Puedes opcionalmente establecer un código personalizado:

Flight::redirect('/new/location', 401);

Deteniendo

Puedes detener el framework en cualquier momento llamando al método halt:

Flight::halt();

También puedes especificar un código de estado HTTP y un mensaje opcional:

Flight::halt(200, 'Regresaré pronto...');

Llamar a halt descartará cualquier contenido de respuesta hasta ese punto. Si deseas detener el framework y outputar la respuesta actual, usa el método stop:

Flight::stop();

Limpiando Datos de Respuesta

Puedes limpiar el cuerpo y los encabezados de la respuesta utilizando el método clear(). Esto limpiará cualquier encabezado asignado a la respuesta, limpiará el cuerpo de la respuesta y establecerá el código de estado en 200.

Flight::response()->clear();

Limpiando Solo el Cuerpo de Respuesta

Si solo deseas limpiar el cuerpo de la respuesta, puedes usar el método clearBody():

// Esto aún mantendrá cualquier encabezado establecido 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 No Modificado. La próxima vez que el cliente solicite el mismo recurso, se le recomendará utilizar su versión en caché local.

Caché a Nivel de Ruta

Si deseas caché tu respuesta completa, puedes utilizar el método cache() y pasar el tiempo para caché.


// Esto almacenará en caché la respuesta durante 5 minutos
Flight::route('/news', function () {
  Flight::response()->cache(time() + 300);
  echo 'Este contenido será almacenado en caché.';
});

// Alternativamente, puedes utilizar una cadena que pasarías
// al método strtotime()
Flight::route('/news', function () {
  Flight::response()->cache('+5 minutes');
  echo 'Este contenido será almacenado en caché.';
});

Última Modificación

Puedes usar el método lastModified y pasar un timestamp UNIX para establecer la fecha y hora en que se modificó por última vez una página. El cliente seguirá utilizando su caché hasta que el valor de última modificación cambie.

Flight::route('/news', function () {
  Flight::lastModified(1234567890);
  echo 'Este contenido será almacenado en caché.';
});

ETag

El almacenamiento en caché de ETag es similar a Última Modificación, excepto que puedes especificar cualquier id que quieras para el recurso:

Flight::route('/news', function () {
  Flight::etag('mi-id-único');
  echo 'Este contenido será almacenado en caché.';
});

Ten en cuenta que llamar a lastModified o etag establecerá y comprobará el valor de la caché. Si el valor de la caché es el mismo entre solicitudes, Flight enviará inmediatamente una respuesta HTTP 304 y detendrá el procesamiento.

Descargar un Archivo (v3.12.0)

Hay un método auxiliar para descargar un archivo. Puedes usar el método download y pasar la ruta.

Flight::route('/download', function () {
  Flight::download('/path/to/file.txt');
});

Learn/frameworkinstance

Instancia del Framework

En lugar de ejecutar Flight como una clase estática global, puedes opcionalmente ejecutarlo como una instancia de objeto.

require 'flight/autoload.php';

$app = Flight::app();

$app->route('/', function () {
  echo '¡hola mundo!';
});

$app->start();

Así que en lugar de llamar al método estático, llamarías al método de instancia con el mismo nombre en el objeto Engine.

Learn/redirects

Redirecciones

Puedes redirigir la solicitud actual utilizando el método redirect y pasando una nueva URL:

Flight::redirect('/nueva/ubicacion');

Por defecto, Flight envía un código de estado HTTP 303. Opcionalmente, puedes establecer un código personalizado:

Flight::redirect('/nueva/ubicacion', 401);

Learn/events

Sistema de Eventos en Flight PHP (v3.15.0+)

Flight PHP introduce un sistema de eventos ligero e intuitivo que te permite registrar y activar eventos personalizados en tu aplicación. Con la adición de Flight::onEvent() y Flight::triggerEvent(), ahora puedes engancharte a momentos clave del ciclo de vida de tu aplicación o definir tus propios eventos 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.

Esta guía cubre todo lo que necesitas saber para comenzar con eventos, incluyendo por qué son valiosos, cómo usarlos y ejemplos prácticos para ayudar a los principiantes a entender su poder.

¿Por qué usar eventos?

Los eventos te permiten separar diferentes partes de tu aplicación para que no dependan demasiado entre sí. Esta separación—frecuentemente llamada desacoplamiento—hace que tu código sea más fácil de actualizar, extender o depurar. En lugar de escribir todo en un solo bloque grande, puedes dividir tu lógica en piezas más pequeñas e independientes que responden a acciones específicas (eventos).

Imagina que estás construyendo una aplicación de blog:

Sin eventos, meterías todo esto 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 central.

Usos Comunes

Registro de Oyentes de Eventos

Para escuchar un evento, utiliza Flight::onEvent(). Este método te permite definir lo que debería suceder cuando un evento ocurre.

Sintaxis

Flight::onEvent(string $event, callable $callback): void

Cómo Funciona

"Te suscribes" a un evento diciéndole a Flight qué hacer cuando sucede. El callback puede aceptar argumentos que se pasan desde el disparador del evento.

El sistema de eventos de Flight es sincrónico, lo que significa que cada oyente de eventos se ejecuta en secuencia, uno tras otro. Cuando activas un evento, todos los oyentes registrados para ese evento se ejecutarán completamente antes de que tu código continúe. Esto es importante de entender, ya que difiere de los sistemas de eventos asincrónicos donde los oyentes pueden ejecutarse en paralelo o en un momento posterior.

Ejemplo Sencillo

Flight::onEvent('user.login', function ($username) {
    echo "¡Bienvenido de nuevo, $username!";
});

Aquí, cuando se activa el evento 'user.login', saludará al usuario por su nombre.

Puntos Clave

Activación de Eventos

Para hacer que un evento suceda, utiliza Flight::triggerEvent(). Esto le dice a Flight que ejecute todos los oyentes registrados para ese evento, pasando cualquier dato que proporciones.

Sintaxis

Flight::triggerEvent(string $event, ...$args): void

Ejemplo Sencillo

$username = 'alice';
Flight::triggerEvent('user.login', $username);

Esto activa el evento 'user.login' y envía 'alice' al oyente que definimos anteriormente, que mostrará: ¡Bienvenido de nuevo, alice!.

Puntos Clave

Registro de Oyentes de Eventos

...

Deteniendo Oyentes Adicionales: Si un oyente devuelve false, no se ejecutarán oyentes adicionales para ese evento. Esto te permite detener la cadena de eventos según condiciones específicas. Recuerda, el orden de los oyentes es importante, ya que el primero que devuelva false detendrá el resto.

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 excelente para usuarios avanzados que desean personalizar el sistema de eventos, como agregar registro o cambiar la forma en que se envían los eventos.

Ejemplo: Personalizando onEvent

Flight::map('onEvent', function (string $event, callable $callback) {
    // Registra cada registro de evento
    error_log("Nuevo oyente de evento agregado para: $event");
    // Llama al comportamiento por defecto (asumiendo un sistema de eventos interno)
    Flight::_onEvent($event, $callback);
});

Ahora, cada vez que registras un evento, se registra antes de continuar.

¿Por qué sobrescribir?

Dónde Colocar Tus Eventos

Como principiante, podrías preguntarte: ¿dónde registro todos estos eventos en mi aplicación? La simplicidad de Flight significa que no hay reglas estrictas: puedes colocarlos donde tenga sentido para tu proyecto. Sin embargo, mantenerlos organizados te ayuda a mantener tu código a medida que tu aplicación crece. Aquí hay algunas opciones prácticas y mejores prácticas, adaptadas a la naturaleza ligera de Flight:

Opción 1: En tu index.php Principal

Para aplicaciones pequeñas o prototipos rápidos, puedes registrar eventos directamente en tu archivo index.php junto a 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 inició sesión en " . date('Y-m-d H:i:s'));
});

// Definir rutas
Flight::route('/login', function () {
    $username = 'bob';
    Flight::triggerEvent('user.login', $username);
    echo "¡Iniciado sesión!";
});

Flight::start();

Opción 2: Un Archivo events.php Separado

Para una aplicación ligeramente más grande, considera mover 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 a menudo se organizan las rutas en app/config/routes.php en proyectos de Flight.

// app/config/events.php
Flight::onEvent('user.login', function ($username) {
    error_log("$username inició sesión en " . date('Y-m-d H:i:s'));
});

Flight::onEvent('user.registered', function ($email, $name) {
    echo "Correo enviado a $email: ¡Bienvenido, $name!";
});
// index.php
require 'vendor/autoload.php';
require 'app/config/events.php';

Flight::route('/login', function () {
    $username = 'bob';
    Flight::triggerEvent('user.login', $username);
    echo "¡Iniciado sesión!";
});

Flight::start();

Opción 3: Cerca de Donde Son Activados

Otro enfoque es registrar eventos cerca de donde son activados, como dentro de un controlador o definición de ruta. Esto funciona bien si un evento es específico de una parte de tu aplicación.

Flight::route('/signup', function () {
    // Registrar evento aquí
    Flight::onEvent('user.registered', function ($email) {
        echo "Correo de bienvenida enviado a $email!";
    });

    $email = 'jane@example.com';
    Flight::triggerEvent('user.registered', $email);
    echo "¡Registrado!";
});

Mejores Prácticas para Flight

Consejo: Agrupar por Propósito

En events.php, agrupa eventos relacionados (por ejemplo, todos los eventos relacionados con usuarios juntos) con comentarios para claridad:

// app/config/events.php
// Eventos de Usuario
Flight::onEvent('user.login', function ($username) {
    error_log("$username inició sesión");
});
Flight::onEvent('user.registered', function ($email) {
    echo "¡Bienvenido a $email!";
});

// Eventos de Página
Flight::onEvent('page.updated', function ($pageId) {
    unset($_SESSION['pages'][$pageId]);
});

Esta estructura escala bien y se mantiene amigable para principiantes.

Ejemplos para Principiantes

Veamos algunos escenarios reales 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 inició sesión en $time");
});

// Paso 2: Activarlo en tu aplicación
Flight::route('/login', function () {
    $username = 'bob'; // Suponiendo que esto proviene de un formulario
    Flight::triggerEvent('user.login', $username);
    echo "¡Hola, $username!";
});

Por qué es útil: El código de inicio de sesión no necesita saber sobre el registro: simplemente activa el evento. Podrías agregar más oyentes más tarde (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 el envío de un correo electrónico
    echo "Correo enviado a $email: ¡Bienvenido, $name!";
});

// Activarlo cuando alguien se registre
Flight::route('/signup', function () {
    $email = 'jane@example.com';
    $name = 'Jane';
    Flight::triggerEvent('user.registered', $email, $name);
    echo "¡Gracias por registrarte!";
});

Por qué es útil: La lógica de registro se centra en crear al usuario, mientras que el evento maneja las notificaciones. Podrías agregar más oyentes (por ejemplo, registrar el registro) más tarde.

Ejemplo 3: Limpiando una Caché

// Oyente para limpiar una caché
Flight::onEvent('page.updated', function ($pageId) {
    unset($_SESSION['pages'][$pageId]); // Limpiar caché de sesión si corresponde
    echo "Caché limpiada para la página $pageId.";
});

// Activarlo cuando se edita una página
Flight::route('/edit-page/(@id)', function ($pageId) {
    // Suponiendo que actualizamos la página
    Flight::triggerEvent('page.updated', $pageId);
    echo "Página $pageId actualizada.";
});

Por qué es útil: El código de edición no se preocupa por la caché: simplemente señala la actualización. Otras partes de la aplicación pueden reaccionar según sea necesario.

Mejores Prácticas

El sistema de eventos en Flight PHP, con Flight::onEvent() y Flight::triggerEvent(), te ofrece una forma simple pero poderosa de construir aplicaciones flexibles. Al permitir que diferentes partes de tu aplicación 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. Comienza con un solo evento y observa cómo transforma la estructura de tu aplicación.

Eventos Incorporados

Flight PHP viene con algunos eventos incorporados que puedes usar para engancharte al ciclo de vida del marco. 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 Incorporados

Learn/views

Vistas

Flight proporciona algunas funcionalidades básicas de plantillas de forma predeterminada. Para mostrar una vista de plantilla llame 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 pase se inyectan automáticamente en la plantilla y se pueden hacer referencia 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 puede configurar manualmente variables de vista utilizando el método set:

Flight::view()->set('name', 'Bob');

La variable name ahora está disponible en todas sus vistas. Entonces simplemente puede hacer:

Flight::render('hello');

Tenga en cuenta que al especificar el nombre de la plantilla en el método render, puede omitir la extensión .php.

Por defecto, Flight buscará un directorio views para los archivos de plantilla. Puede establecer una ruta alternativa para sus plantillas configurando lo siguiente:

Flight::set('flight.views.path', '/ruta/a/vistas');

Diseños

Es común que los sitios web tengan un solo archivo de plantilla de diseño con contenido intercambiable. Para renderizar contenido que se utilizará en un diseño, puede pasar un parámetro opcional al método render.

Flight::render('header', ['heading' => 'Hola'], 'headerContent');
Flight::render('body', ['body' => 'Mundo'], 'bodyContent');

Su vista tendrá variables guardadas llamadas headerContent y bodyContent. Luego puede renderizar su diseño 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>

Vistas Personalizadas

Flight le permite cambiar el motor de vista predeterminado simplemente registrando su propia clase de vista. Así es como utilizaría el Smarty motor de plantillas para sus vistas:

// Cargar biblioteca Smarty
require './Smarty/libs/Smarty.class.php';

// Registrar Smarty como clase de vista
// También pase 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 la plantilla
Flight::view()->assign('name', 'Bob');

// Mostrar la plantilla
Flight::view()->display('hello.tpl');

Por completitud, también debería anular el método de renderizado predeterminado de Flight:

Flight::map('render', function(string $template, array $data): void {
  Flight::view()->assign($data);
  Flight::view()->display($template);
});

Learn/templates

Vistas HTML y Plantillas

Flight proporciona una funcionalidad básica de plantillas por defecto.

Flight te permite cambiar el motor de vista 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.

Motor de Vista Integrado

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 pases se inyectan automáticamente en la plantilla y se pueden referenciar 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 manualmente variables de vista utilizando el método set:

Flight::view()->set('name', 'Bob');

La variable name ahora está disponible en todas tus vistas. Así que puedes hacerlo simplemente:

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 archivos de plantilla. Puedes establecer una ruta alternativa para tus plantillas configurando lo siguiente:

Flight::set('flight.views.path', '/path/to/views');

Diseños

Es común que los sitios web tengan un único archivo de plantilla de diseño con contenido intercambiable. Para renderizar contenido que se utilizará en un diseño, puedes pasar un parámetro opcional al método render.

Flight::render('header', ['heading' => 'Hola'], 'headerContent');
Flight::render('body', ['body' => 'Mundo'], 'bodyContent');

Tu vista tendrá variables guardadas llamadas headerContent y bodyContent. Luego puedes renderizar tu diseño haciendo:

Flight::render('layout', ['title' => 'Página Principal']);

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 Principal</title>
  </head>
  <body>
    <h1>Hola</h1>
    <div>Mundo</div>
  </body>
</html>

Smarty

Aquí tienes 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 callback 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 a la plantilla
Flight::view()->assign('name', 'Bob');

// Mostrar la plantilla
Flight::view()->display('hello.tpl');

Para ser completo, 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);
});

Latte

Aquí tienes cómo usar el motor de plantillas Latte para tus vistas:

// Registrar Latte como la clase de vista
// También pasar una función de callback para configurar Latte al cargar
Flight::register('view', Latte\Engine::class, [], function (Latte\Engine $latte) {
  // Aquí es donde Latte almacenará en caché tus plantillas para acelerar las cosas
    // Una característica interesante de Latte es que automáticamente actualiza tu
    // caché cuando haces cambios en tus plantillas.
    $latte->setTempDirectory(__DIR__ . '/../cache/');

    // Indicar a Latte dónde estará el directorio raíz para tus vistas.
    $latte->setLoader(new \Latte\Loaders\FileLoader(__DIR__ . '/../views/'));
});

// Y rematarlo para que puedas usar Flight::render() correctamente
Flight::map('render', function(string $template, array $data): void {
  // Esto es como $latte_engine->render($template, $data);
  echo Flight::view()->render($template, $data);
});

Blade

Aquí tienes 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 callback 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 a la plantilla
Flight::view()->share('name', 'Bob');

// Mostrar la plantilla
echo Flight::view()->run('hello', []);

Para ser completo, 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!

Al seguir estos pasos, puedes integrar el motor de plantillas Blade con Flight y usarlo para renderizar tus vistas.

Learn/flight_vs_fat_free

Flight vs Fat-Free

¿Qué es Fat-Free?

Fat-Free (conocido afectuosamente como F3) es un micro marco PHP poderoso pero fácil de usar diseñado para ayudarte a construir aplicaciones web dinámicas y robustas - ¡rápido!

Flight se compara con Fat-Free en muchos aspectos y probablemente es el pariente más cercano en cuanto a 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 solía ser.

Las actualizaciones son menos frecuentes y la comunidad no es tan activa como solía ser. El código es lo suficientemente simple, pero a veces la falta de disciplina sintáctica puede hacer que sea difícil de leer y entender. Funciona para PHP 8.3, pero el código en sí mismo todavía parece vivir en PHP 5.3.

Pros en comparación con Flight

Contras en comparación con Flight

Learn/extending

Extendiendo

Flight está diseñado para ser un marco extensible. El marco 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.

Si estás buscando un DIC (Contenedor de Inyección de Dependencias), dirígete a la página del Contenedor de Inyección de Dependencias.

Mapeo de Métodos

Para mapear tu propio método personalizado simple, usas la función map:

// Mapea tu método
Flight::map('hello', function (string $name) {
  echo "¡hola $name!";
});

// Llama a tu método personalizado
Flight::hello('Bob');

Si bien es posible crear métodos personalizados simples, se recomienda crear funciones estándar en PHP. Esto tiene autocompletado en IDEs y es más fácil de leer. El equivalente del código anterior sería:

function hello(string $name) {
  echo "¡hola $name!";
}

hello('Bob');

Esto se utiliza más cuando necesitas pasar variables a tu método para obtener un valor esperado. Usar el método register() como se muestra a continuación es más para pasar configuraciones y luego llamar a tu clase preconfigurada.

Registro de Clases

Para registrar tu propia clase y configurarla, utilizas la función register:

// Registra tu clase
Flight::register('user', User::class);

// Obtén una instancia de tu clase
$user = Flight::user();

El método de registro también te permite pasar parámetros al constructor de tu clase. Así que cuando cargas tu clase personalizada, vendrá preinicializada. Puedes definir los parámetros del constructor pasando un array adicional. Aquí tienes un ejemplo de carga de una conexión a base de datos:

// Registra la clase con parámetros de constructor
Flight::register('db', PDO::class, ['mysql:host=localhost;dbname=test', 'user', 'pass']);

// Obtén 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 tarde en tu código, solo llama 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);

Ten en cuenta que los métodos mapeados tienen prioridad sobre las clases registradas. Si declara ambos usando el mismo nombre, solo se invocará el método mapeado.

Registro

Flight no tiene un sistema de registro integrado, sin embargo, es muy fácil utilizar una biblioteca de registro con Flight. Aquí hay un ejemplo usando la biblioteca Monolog:

// index.php o bootstrap.php

// Registra 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('Este es un mensaje de advertencia');

Esto registrará un mensaje en el archivo de registro 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());
    // Muestra tu página de error personalizada
    include 'errors/500.html';
});

También podrías crear un sistema básico de APM (Monitoreo del Rendimiento de la Aplicación) usando los métodos before y after:

// En tu archivo de bootstrap

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('La solicitud '.Flight::request()->url.' tomó ' . round($end - $start, 4) . ' segundos');

    // También podrías agregar tus encabezados de solicitud o respuesta
    // para registrarlos también (ten cuidado ya que esto sería un 
    // gran volumen de datos si tienes muchas solicitudes)
    Flight::log()->info('Encabezados de Solicitud: ' . json_encode(Flight::request()->headers));
    Flight::log()->info('Encabezados de Respuesta: ' . json_encode(Flight::response()->headers));
});

Sobrescribiendo Métodos del Marco

Flight te permite sobrescribir su funcionalidad predeterminada para adaptarla a tus propias necesidades, sin necesidad de modificar ningún código. Puedes ver todos los métodos que puedes sobrescribir aquí.

Por ejemplo, cuando Flight no puede hacer coincidir una URL con una ruta, invoca el método notFound que envía una respuesta genérica HTTP 404. Puedes sobrescribir este comportamiento utilizando el método map:

Flight::map('notFound', function() {
  // Muestra la página personalizada 404
  include 'errors/404.html';
});

Flight también te permite reemplazar componentes centrales del marco. Por ejemplo, puedes reemplazar la clase Router predeterminada con tu propia clase personalizada:

// Registra tu clase personalizada
Flight::register('router', MyRouter::class);

// Cuando Flight carga la instancia del Router, cargará tu clase
$myrouter = Flight::router();

Sin embargo, los métodos del marco como map y register no se pueden sobrescribir. Recibirás un error si intentas hacerlo.

Learn/json

JSON

Flight proporciona soporte para enviar respuestas JSON y JSONP. Para enviar una respuesta JSON, pasas algunos datos para ser codificados en JSON:

Flight::json(['id' => 123]);

Para solicitudes JSONP, opcionalmente puedes pasar el nombre del parámetro de consulta que estás utilizando 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 utilizará por defecto jsonp.

Learn/flight_vs_slim

Comparación entre Flight y Slim

¿Qué es Slim?

Slim es un micro marco de PHP que te ayuda a escribir rápidamente aplicaciones web y APIs simples pero potentes.

Mucha de la inspiración para algunas de las características de la versión 3 de Flight realmente 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ó enfocado hacia la simplicidad, pero ha habido resenas mixtas con respecto a la v4.

Ventajas en comparación con Flight

Desventajas en comparación con Flight

Learn/autoloading

Carga automática

La carga automática es un concepto en PHP donde especificas un directorio o directorios para cargar clases desde. Esto es mucho más beneficioso que usar require o include para cargar clases. También es un requisito para usar paquetes de Composer.

Por defecto, cualquier clase de Flight se carga automáticamente gracias a composer. Sin embargo, si deseas cargar automáticamente tus propias clases, puedes usar el método Flight::path() para especificar un directorio desde el cual cargar clases.

Ejemplo básico

Supongamos que tenemos una estructura de directorios como la 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 haber notado que esta es la misma estructura de archivos que este sitio de documentación.

Puedes especificar cada directorio para cargar desde así:


/**
 * public/index.php
 */

// Agregar una ruta al cargador automático
Flight::path(__DIR__.'/../app/controllers/');
Flight::path(__DIR__.'/../app/utils/');

/**
 * app/controllers/MyController.php
 */

// no se requiere espacio de nombres

// Se recomienda que todas las clases cargadas automáticamente estén en Pascal Case (cada palabra en mayúscula, sin espacios)
// A partir de la versión 3.7.2, puedes utilizar Pascal_Snake_Case para los nombres de tus clases ejecutando Loader::setV2ClassLoading(false);
class MyController {

    public function index() {
        // hacer algo
    }
}

Espacios de nombres

Si tienes espacios de nombres, en realidad se vuelve muy fácil de implementar. Deberías usar el método Flight::path() para especificar el directorio raíz (no el directorio de documentos o la carpeta public/) de tu aplicación.


/**
 * public/index.php
 */

// Agregar una ruta al cargador automático
Flight::path(__DIR__.'/../');

Así es como podría verse tu controlador. Observa el ejemplo a continuación, pero presta atención a los comentarios para obtener información importante.

/**
 * app/controllers/MyController.php
 */

// se requieren espacios de nombres
// los espacios de nombres son iguales a 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 se establezca Loader::setV2ClassLoading(false))
namespace app\controllers;

// Se recomienda que todas las clases cargadas automáticamente estén en Pascal Case (cada palabra en mayúscula, sin espacios)
// A partir de la versión 3.7.2, puedes utilizar Pascal_Snake_Case para los nombres de tus clases ejecutando Loader::setV2ClassLoading(false);
class MyController {

    public function index() {
        // hacer algo
    }
}

Y si quisieras cargar automáticamente una clase en tu directorio de utilidades, 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 anterior)
namespace app\UTILS;

class ArrayHelperUtil {

    public function changeArrayCase(array $array) {
        // hacer algo
    }
}

Guiones bajos en los nombres de las clases

A partir de la versión 3.7.2, puedes utilizar Pascal_Snake_Case para tus nombres de clases ejecutando Loader::setV2ClassLoading(false);. Esto te permitirá utilizar guiones bajos en los nombres de tus clases. Esto no se recomienda, pero está disponible para aquellos que lo necesiten.


/**
 * public/index.php
 */

// Agregar una ruta al cargador automático
Flight::path(__DIR__.'/../app/controllers/');
Flight::path(__DIR__.'/../app/utils/');
Loader::setV2ClassLoading(false);

/**
 * app/controllers/My_Controller.php
 */

// no se requiere espacio de nombres

class My_Controller {

    public function index() {
        // hacer algo
    }
}

Learn/troubleshooting

Resolución de Problemas

Esta página te ayudará a solucionar problemas comunes que puedes encontrar al usar Flight.

Problemas Comunes

404 No Encontrado o Comportamiento de Ruta Inesperado

Si ves un error 404 No Encontrado (pero juras por tu vida que realmente está ahí y no es un error tipográfico) esto realmente podría ser un problema con que estás devolviendo un valor en el punto final de tu ruta en lugar de simplemente hacer un eco. La razón de esto es intencional pero podría sorprender a algunos desarrolladores.


Flight::route('/hello', function(){
    // Esto podría causar un error 404 No Encontrado
    return '¡Hola Mundo!';
});

// Lo que probablemente deseas
Flight::route('/hello', function(){
    echo '¡Hola Mundo!';
});

La razón de esto es debido a un mecanismo especial incorporado en el enrutador que maneja la salida de retorno como un solo "ir a la siguiente ruta". Puedes ver el comportamiento documentado en la sección Enrutamiento.

Clase No Encontrada (la carga automática no funciona)

Podría haber un par de razones por las que esto no está sucediendo. A continuación se muestran algunos ejemplos, pero asegúrate de revisar también la sección de carga automática.

Nombre de Archivo Incorrecto

Lo más común es que el nombre de la clase no coincida con el nombre del archivo.

Si tienes una clase llamada MiClase entonces el archivo debería llamarse MiClase.php. Si tienes una clase llamada MiClase y el archivo se llama miclase.php entonces el cargador automático no podrá encontrarlo.

Namespace Incorrecto

Si estás utilizando espacios de nombres (namespaces), entonces el namespace debe coincidir con la estructura de directorios.

// código

// si tu MyController está en el directorio app/controllers y tiene un espacio de nombres
// esto no funcionará.
Flight::route('/hello', 'MyController->hello');

// deberías elegir una de estas opciones
Flight::route('/hello', 'app\controllers\MiControlador->hello');
// o si tienes una declaración use arriba

use app\controllers\MiControlador;

Flight::route('/hello', [ MiControlador::class, 'hello' ]);
// también puede escribirse
Flight::route('/hello', MiControlador::class.'->hello');
// también...
Flight::route('/hello', [ 'app\controllers\MiControlador', 'hello' ]);

path() no definido

En la aplicación esquelética, esto está definido dentro del archivo config.php, pero para que tus clases se encuentren, debes asegurarte de que el método path() esté definido (probablemente en la raíz de tu directorio) antes de intentar usarlo.


// Agregar una ruta al cargador automático
Flight::path(__DIR__.'/../');

Install

Instalación

Descargar los archivos.

Si estás utilizando Composer, puedes ejecutar el siguiente comando:

composer require flightphp/core

O puedes descargar los archivos directamente y extraerlos en tu directorio web.

Configurar tu Servidor Web.

Apache

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, añade la línea RewriteBase /subdir/ justo después de RewriteEngine On.

Nota: Si deseas proteger todos los archivos del servidor, como un archivo de base de datos o env. Agrega esto en tu archivo .htaccess:

RewriteEngine On
RewriteRule ^(.*)$ index.php

Nginx

Para Nginx, agrega lo siguiente a la declaración de tu servidor:

server {
  location / {
    try_files $uri $uri/ /index.php;
  }
}

Crea tu archivo index.php.

<?php

// Si estás utilizando Composer, requiere el cargador automático.
require 'vendor/autoload.php';
// si no estás utilizando 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 '¡Hola Mundo!';
});

// Finalmente, inicia el framework.
Flight::start();

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

Paso 1: Configura Tu Proyecto

Comienza creando un nuevo directorio de proyecto e instalando Flight a través de Composer.

  1. Crear un Directorio:

    mkdir flight-blog
    cd flight-blog
  2. Instalar Flight:

    composer require flightphp/core
  3. Crear un Directorio Público: Flight utiliza un único punto de entrada (index.php). Crea una carpeta public/ para ello:

    mkdir public
  4. Básico index.php: Crea public/index.php con una ruta simple de “hola mundo”:

    <?php
    require '../vendor/autoload.php';
    
    Flight::route('/', function () {
       echo '¡Hola, Flight!';
    });
    
    Flight::start();
  5. 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

Paso 3: Instalar y Configurar Latte

Latte es un motor de plantillas ligero que se integra bien con Flight.

  1. Instalar Latte:

    composer require latte/latte
  2. 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();
  3. 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>&copy; {date('Y')} Blog Flight</p>
    </footer>
    </body>
    </html>
  4. 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.

  5. 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.

  1. Crear routes.php: En app/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']);
    });
  2. 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.

  1. 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);
    });
  2. 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.

  1. 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.

  1. 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}
  2. 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('/');
    });
  3. 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.

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

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

¿Qué es Flight?

Flight es un marco 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.

Flight es un gran marco para principiantes que son nuevos en PHP y quieren aprender a construir aplicaciones web. También es un gran marco para desarrolladores experimentados que desean más control sobre sus aplicaciones web. Está diseñado para construir fácilmente una API RESTful, una aplicación web simple o una aplicación web compleja.

Comenzar Rápido

Primero 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 mediante un archivo zip
// require 'flight/Flight.php';

Flight::route('/', function() {
  echo '¡hola mundo!';
});

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.

¿Lo suficientemente simple, verdad?
¡Aprende más sobre Flight en la documentación!

¿Es rápido?

¡Sí! Flight es rápido. Es uno de los marcos PHP más rápidos disponibles. Puedes ver todos los benchmarks en TechEmpower

Ve el benchmark a continuación con algunos otros marcos PHP populares.

Marco Reqs de texto plano/sec Reqs de JSON/sec
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

Aplicación Esqueleto/Plantilla

Hay una aplicación de ejemplo que puede ayudarte a comenzar con el marco Flight. Ve a flightphp/skeleton para obtener instrucciones sobre cómo comenzar. También puedes visitar la página de ejemplos para inspirarte en algunas de las cosas que puedes hacer con Flight.

Comunidad

Estamos en Matrix Chat

Matrix

Y en Discord

Contribuciones

Hay dos formas en que puedes contribuir a Flight:

  1. Puedes contribuir al marco central visitando el repositorio principal.
  2. Puedes contribuir a la documentación. Este sitio web de documentación está alojado en Github. Si notas un error o quieres mejorar algo, siéntete libre de corregirlo y enviar una solicitud de extracción. Intentamos mantenernos al día, pero las actualizaciones y traducciones de idiomas son bienvenidas.

Requisitos

Flight requiere PHP 7.4 o superior.

Nota: PHP 7.4 es compatible porque en el momento actual de escribir (2024) PHP 7.4 es la versión predeterminada para algunas distribuciones Linux LTS. Forzar un cambio a PHP >8 causaría muchos inconvenientes para esos usuarios. El marco también es compatible con PHP >8.

Licencia

Flight se publica 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 de caché de archivo PHP ligera, simple y autónoma

Ventajas

¡Este sitio de documentación está usando esta biblioteca para almacenar en caché cada una de las páginas!

Haz clic aquí para ver el código.

Instalación

Instalar a través de composer:

composer require flightphp/cache

Uso

El uso es bastante sencillo. Esto guarda un archivo de caché en el directorio de caché.

use flight\Cache;

$app = Flight::app();

// Pasas el directorio donde se almacenará la caché en el constructor
$app->register('cache', Cache::class, [ __DIR__ . '/../cache/' ], function(Cache $cache) {

    // Esto asegura que la caché solo se use cuando esté en modo de producción
    // ENVIRONMENT es una constante que se establece en tu archivo de inicialización o en otro lugar de tu aplicación
    $cache->setDevMode(ENVIRONMENT === 'development');
});

Luego puedes usarlo en tu código así:


// Obtener instancia de caché
$cache = Flight::cache();
$data = $cache->refreshIfExpired('simple-cache-test', function () {
    return date("H:i:s"); // devolver datos a ser almacenados en caché
}, 10); // 10 segundos

// o
$data = $cache->retrieve('simple-cache-test');
if(empty($data)) {
    $data = date("H:i:s");
    $cache->store('simple-cache-test', $data, 10); // 10 segundos
}

Documentación

Visita https://github.com/flightphp/cache para la documentación completa y asegúrate de ver la carpeta de ejemplos.

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:

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/index

Increíbles complementos

Flight es increíblemente extensible. Hay varios complementos que se pueden utilizar para añadir funcionalidad a tu aplicación Flight. Algunos son oficialmente compatibles con el Equipo de Flight y otros son bibliotecas micro/lite para ayudarte a comenzar.

Caché

La caché es una excelente manera de acelerar tu aplicación. Hay varias bibliotecas de caché que se pueden utilizar con Flight.

Depuración

La depuración es crucial cuando estás desarrollando en tu entorno local. Hay algunos complementos que pueden mejorar tu experiencia de depuración.

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 envoltorios para escribir consultas y otras son ORM completos.

Sesión

Las sesiones no son realmente útiles para las API, pero para desarrollar una aplicación web, las sesiones pueden ser cruciales para mantener el estado e información de inicio de sesión.

Plantillas

Las plantillas son esenciales para cualquier aplicación web con un UI. Hay varios motores de plantillas que se pueden utilizar con Flight.

Contribuir

¿Tienes un complemento que te gustaría compartir? ¡Envía una solicitud de extracción para añadirlo a la lista!

Awesome-plugins/ghost_session

Ghostff/Session

Gestor de Sesiones PHP (no bloqueante, flash, segmento, cifrado de sesión). Utiliza PHP open_ssl para la cifrado/descifrado opcional de los datos de la sesión. Soporta Archivos, 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 se requiere 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 necesitarás ejecutar auto_commit en tu configuración. 

Ejemplo Sencillo

Aquí hay un ejemplo simple de cómo podrías usar esto.

Flight::route('POST /login', function() {
    $session = Flight::session();

    // realiza 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);

    // cada vez que escribas en la sesión, debes confirmarlo deliberadamente.
    $session->commit();
});

// Esta verificación podría estar en la lógica de la página restringida, o envuelta en middleware.
Flight::route('/some-restricted-page', function() {
    $session = Flight::session();

    if(!$session->get('is_logged_in')) {
        Flight::redirect('/login');
    }

    // realiza 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();

// establecer una ruta personalizada para tu archivo de configuración de sesión y darle una cadena aleatoria para el id de sesión
$app->register('session', Session::class, [ 'path/to/session_config.php', bin2hex(random_bytes(32)) ], function(Session $session) {
        // o puedes sobreescribir manualmente las opciones de configuración
        $session->updateConfiguration([
            // si deseas almacenar tus datos de sesión en una base de datos (bueno si quieres algo como, "desconéctame de 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 diferente
            Session::CONFIG_AUTO_COMMIT   => true, // haz esto solo si lo requiere y/o es difícil confirmarlo
                                                   // adicionalmente podrías hacer Flight::after('start', function() { Flight::session()->commit(); });
            Session::CONFIG_MYSQL_DS         => [
                'driver'    => 'mysql',             # Controlador de base de datos para dns de PDO eg(mysql:host=...;dbname=...)
                'host'      => '127.0.0.1',         # Host de base de datos
                'db_name'   => 'my_app_database',   # Nombre de la base de datos
                'db_table'  => 'sessions',          # Tabla de 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 sobrecostos de establecer una nueva conexión cada vez que un script necesita comunicarse con una base de datos, resultando en una aplicación web más rápida. ENCUENTRA EL REVERSO TÚ MISMO
            ]
        ]);
    }
);

¡Ayuda! ¡Mis Datos de Sesión No Están Persistiendo!

¿Estás estableciendo tus datos de sesión y no están persistiendo entre solicitudes? Puede que hayas olvidado confirmar tus datos de sesión. Puedes hacerlo llamando a $session->commit() después de haber establecido tus datos de sesión.

Flight::route('POST /login', function() {
    $session = Flight::session();

    // realiza 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);

    // cada vez que escribas en la sesión, debes confirmarlo deliberadamente.
    $session->commit();
});

La otra forma de solucionar 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. El código es simple de entender si deseas revisar este paquete por ti mismo.

Awesome-plugins/pdo_wrapper

Clase de ayuda PdoWrapper PDO

Flight viene con una clase de ayuda para PDO. Le permite consultar fácilmente su base de datos con toda la locura de preparar/ejecutar/obtenerTodo(). Simplifica en gran medida cómo puede consultar su base de datos. Cada resultado de fila se devuelve como una clase Flight Collection que le permite acceder a sus datos mediante la sintaxis de matriz o la sintaxis de objeto.

Registrando la Clase de Ayuda PDO

// Registrar la clase de ayuda PDO
Flight::register('db', \flight\database\PdoWrapper::class, ['mysql:host=localhost;dbname=cool_db_name', 'usuario', 'contraseña', [
        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
    ]
]);

Uso

Este objeto extiende PDO, por lo que todos los métodos normales de PDO están disponibles. Los siguientes métodos se agregan para facilitar la consulta a la base de datos:

runQuery(string $sql, array $params = []): PDOStatement

Úselo para INSERTS, UPDATES o si planea usar un SELECT en un bucle while

$db = Flight::db();
$statement = $db->runQuery("SELECT * FROM table WHERE something = ?", [ $algo ]);
while($row = $statement->fetch()) {
    // ...
}

// O escribir en la base de datos
$db->runQuery("INSERT INTO table (nombre) VALUES (?)", [ $nombre ]);
$db->runQuery("UPDATE table SET nombre = ? WHERE id = ?", [ $nombre, $id ]);

fetchField(string $sql, array $params = []): mixed

Extrae el primer campo de la consulta

$db = Flight::db();
$count = $db->fetchField("SELECT COUNT(*) FROM table WHERE something = ?", [ $algo ]);

fetchRow(string $sql, array $params = []): array

Extrae una fila de la consulta

$db = Flight::db();
$row = $db->fetchRow("SELECT id, nombre FROM table WHERE id = ?", [ $id ]);
echo $row['nombre'];
// o
echo $row->nombre;

fetchAll(string $sql, array $params = []): array

Extrae todas las filas de la consulta

$db = Flight::db();
$rows = $db->fetchAll("SELECT id, nombre FROM table WHERE something = ?", [ $algo ]);
foreach($rows as $row) {
    echo $row['nombre'];
    // o
    echo $row->nombre;
}

Nota para la sintaxis de IN()

Esto también tiene un envoltorio útil para las declaraciones IN(). Simplemente puede pasar un signo de interrogación como marcador de posición para IN() y luego un array de valores. Aquí hay un ejemplo de cómo podría verse eso:

$db = Flight::db();
$nombre = 'Bob';
$ids_compañía = [1,2,3,4,5];
$rows = $db->fetchAll("SELECT id, nombre FROM table WHERE nombre = ? AND company_id IN (?)", [ $nombre, $ids_compañía ]);

Ejemplo Completo

// Ruta de ejemplo y cómo usar este envoltorio
Flight::route('/usuarios', function () {
    // Obtener todos los usuarios
    $usuarios = Flight::db()->fetchAll('SELECT * FROM users');

    // Transmitir todos los usuarios
    $declaración = Flight::db()->runQuery('SELECT * FROM users');
    while ($usuario = $declaración->fetch()) {
        echo $usuario['nombre'];
        // o echo $usuario->nombre;
    }

    // Obtener un usuario único
    $usuario = Flight::db()->fetchRow('SELECT * FROM users WHERE id = ?', [123]);

    // Obtener un valor único
    $count = Flight::db()->fetchField('SELECT COUNT(*) FROM users');

    // Sintaxis especial de IN() para ayudar (asegúrese de que IN esté en mayúsculas)
    $usuarios = Flight::db()->fetchAll('SELECT * FROM users WHERE id IN (?)', [[1,2,3,4,5]]);
    // también se podría hacer esto
    $usuarios = Flight::db()->fetchAll('SELECT * FROM users WHERE id IN (?)', [ '1,2,3,4,5']);

    // Insertar un nuevo usuario
    Flight::db()->runQuery("INSERT INTO users (nombre, email) VALUES (?, ?)", ['Bob', 'bob@example.com']);
    $id_insertado = Flight::db()->lastInsertId();

    // Actualizar un usuario
    Flight::db()->runQuery("UPDATE users SET nombre = ? WHERE id = ?", ['Bob', 123]);

    // Eliminar un usuario
    Flight::db()->runQuery("DELETE FROM users WHERE id = ?", [123]);

    // Obtener el número de filas afectadas
    $declaración = Flight::db()->runQuery("UPDATE users SET nombre = ? WHERE nombre = ?", ['Bob', 'Sally']);
    $filas_afectadas = $declaración->rowCount();

});

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:

Los Scripts SQL

Los scripts se dividen en tres conjuntos de scripts:

El directorio de scripts es:

 <root dir>
     |
     +-- base.sql
     |
     +-- /migrations
              |
              +-- /up
                   |
                   +-- 00001.sql
                   +-- 00002.sql
              +-- /down
                   |
                   +-- 00000.sql
                   +-- 00001.sql

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:

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

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 - Controlador de Sesiones Basado en Archivos Liviano

Este es un plugin liviano de controlador de sesiones basado en archivos para el Flight PHP Framework. Proporciona una solución simple pero poderosa para gestionar sesiones, con características como lecturas de sesiones no bloqueantes, cifrado opcional, funcionalidad de auto-confirmación y un modo de prueba para el desarrollo. Los datos de la 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 tiene 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í tienes 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();

// Registrar el servicio de sesión
$app->register('session', Session::class);

// Ruta de ejemplo 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'); // Salida: johndoe
    echo $session->get('preferences', 'default_theme'); // Salida: default_theme

    if ($session->get('user_id')) {
        Flight::json(['message' => '¡El usuario ha iniciado sesión!', 'user_id' => $session->get('user_id')]);
    }
});

Flight::route('/logout', function() {
    $session = Flight::session();
    $session->clear(); // Limpiar todos los datos de la sesión
    Flight::json(['message' => '¡Salida exitosa!']);
});

Flight::start();

Puntos Clave

Configuración

Puedes personalizar el controlador de sesiones pasando un array de opciones al registrarte:

$app->register('session', Session::class, [
    'save_path' => '/custom/path/to/sessions',         // Directorio para archivos de sesión
    'encryption_key' => 'a-secure-32-byte-key-here',   // Habilitar cifrado (32 bytes recomendados para AES-256-CBC)
    'auto_commit' => false,                            // Desactivar auto-confirmación para control manual
    'start_session' => true,                           // Iniciar sesión automáticamente (predeterminado: true)
    'test_mode' => false                               // Habilitar modo de prueba para el desarrollo
]);

Opciones de Configuración

Opción Descripción Valor Predeterminado
save_path Directorio donde se almacenan los archivos de sesión sys_get_temp_dir() . '/flight_sessions'
encryption_key Clave para cifrado AES-256-CBC (opcional) null (sin cifrado)
auto_commit Guardar automáticamente los datos de la sesión al apagarse true
start_session Iniciar la sesión automáticamente true
test_mode Ejecutar en modo de prueba sin afectar las sesiones de PHP false
test_session_id ID de sesión personalizado para modo de prueba (opcional) Generado aleatoriamente si no está configurado

Uso Avanzado

Confirmación Manual

Si desactivas la auto-confirmación, debes confirmar manualmente los cambios:

$app->register('session', Session::class, ['auto_commit' => false]);

Flight::route('/update', function() {
    $session = Flight::session();
    $session->set('key', 'value');
    $session->commit(); // Guardar cambios explícitamente
});

Seguridad de la Sesión con Cifrado

Habilitar 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'); // Cifrado automáticamente
    echo $session->get('credit_card'); // Desencriptado al recuperar
});

Regeneración de Sesión

Regenerar 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, conservar datos
    // O
    $session->regenerate(true); // Nuevo ID, eliminar datos antiguos
});

Ejemplo de Middleware

Proteger rutas con autenticación basada en sesión:

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');
    }
});

Este es solo un ejemplo simple de cómo usar esto en middleware. Para un ejemplo más detallado, consulta la documentación de middleware.

Métodos

La clase Session proporciona estos métodos:

Todos los métodos excepto get() y id() devuelven la instancia de Session para la encadenación.

¿Por Qué Usar Este Plugin?

Detalles Técnicos

Contribuyendo

¡Las contribuciones son bienvenidas! Haz un fork del repositorio, realiza tus cambios y envía una solicitud de extracción. Informa sobre errores o sugiere funcionalidades a través del rastreador de problemas de Github.

Licencia

Este plugin está 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.

  1. Si estás usando el proyecto esqueleto, puedes ejecutar php runway [comando] desde la raíz de tu proyecto.
  2. 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

=====

This is a set of extensions to make working with Flight a little richer.

This is the Panel

Flight Bar

And each panel displays very helpful information about your application!

Flight Data Flight Database Flight Request

Click here to view the code.

Installation

Run composer require flightphp/tracy-extensions --dev and you're on your way!

Configuration

There is very little configuration you need to do to get this started. You will need to initiate the Tracy debugger prior to using this https://tracy.nette.org/en/guide:

<?php

use Tracy\Debugger;
use flight\debug\tracy\TracyExtensionLoader;

// bootstrap code
require __DIR__ . '/vendor/autoload.php';

Debugger::enable();
// You may need to specify your environment with Debugger::enable(Debugger::DEVELOPMENT)

// if you use database connections in your app, there is a 
// required PDO wrapper to use ONLY IN DEVELOPMENT (not production please!)
// It has the same parameters as a regular PDO connection
$pdo = new PdoQueryCapture('sqlite:test.db', 'user', 'pass');
// or if you attach this to the Flight framework
Flight::register('db', PdoQueryCapture::class, ['sqlite:test.db', 'user', 'pass']);
// now whenever you make a query it will capture the time, query, and parameters

// This connects the dots
if(Debugger::$showBar === true) {
    // This needs to be false or Tracy can't actually render :(
    Flight::set('flight.content_length', false);
    new TracyExtensionLoader(Flight::app());
}

// more code

Flight::start();

Additional Configuration

Session Data

If you have a custom session handler (such as ghostff/session), you can pass any array of session data to Tracy and it will automatically output it for you. You pass it in with the session_data key in the second parameter of the TracyExtensionLoader constructor.


use Ghostff\Session\Session;

require 'vendor/autoload.php';

$app = Flight::app();

$app->register('session', Session::class);

if(Debugger::$showBar === true) {
    // This needs to be false or Tracy can't actually render :(
    Flight::set('flight.content_length', false);
    new TracyExtensionLoader(Flight::app(), [ 'session_data' => Flight::session()->getAll() ]);
}

// routes and other things...

Flight::start();

Latte

If you have Latte installed in your project, you can use the Latte panel to analyze your templates. You can pass in the Latte instance to the TracyExtensionLoader constructor with the latte key in the second parameter.



use Latte\Engine;

require 'vendor/autoload.php';

$app = Flight::app();

$app->register('latte', Engine::class, [], function($latte) {
    $latte->setTempDirectory(__DIR__ . '/temp');

    // this is where you add the Latte Panel to Tracy
    $latte->addExtension(new Latte\Bridges\Tracy\TracyExtension);
});

if(Debugger::$showBar === true) {
    // This needs to be false or Tracy can't actually render :(
    Flight::set('flight.content_length', false);
    new TracyExtensionLoader(Flight::app());
}

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.

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 una sintaxis de PHP que Twig o Smarty. También es muy fácil de extender y agregar tus propios filtros y funciones.

Instalación

Instalar 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.


use Latte\Engine as LatteEngine;

require 'vendor/autoload.php';

$app = Flight::app();

$app->register('latte', LatteEngine::class, [], function(LatteEngine $latte) use ($app) {

    // Aquí es donde Latte almacenará en caché tus plantillas para acelerar las cosas
    // ¡Una cosa genial sobre Latte es que actualiza automáticamente tu caché
    // cuando realizas cambios en tus plantillas!
    $latte->setTempDirectory(__DIR__ . '/../cache/');

    // Indica a Latte dónde estará el directorio raíz de tus vistas.
    // $app->get('flight.views.path') se establece en el archivo config.php
    // También podrías hacer algo como `__DIR__ . '/../views/'`
    $latte->setLoader(new \Latte\Loaders\FileLoader($app->get('flight.views.path')));
});

Ejemplo de Diseño Simple

Aquí tienes un ejemplo simple de un archivo de diseño. Este es el archivo que se utilizará para envolver todas tus otras vistas.

<!-- app/views/layout.latte -->
<!doctype html>
<html lang="es">
    <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">
            &copy; Derechos de Autor
        </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 dentro del 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 así:

// ruta simple
Flight::route('/', function () {
    Flight::latte()->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::latte()->render('home.latte', [
            'title' => 'Página de Inicio'
        ]);
    }
}

¡Consulta la Documentación de Latte para obtener más información sobre cómo utilizar Latte al máximo!

Awesome-plugins/awesome_plugins

Plugins Asombrosos

Flight es increíblemente extensible. Hay una serie de plugins que se pueden usar para agregar funcionalidad a tu aplicación Flight. Algunos son oficialmente compatibles con el equipo de Flight y otros son bibliotecas micro/lite para ayudarte a comenzar.

Documentación de la API

La documentación de la 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 la API para tus proyectos Flight.

Autenticación/Autorización

La autenticación y la autorización son cruciales para cualquier aplicación que requiera controles sobre quién puede acceder a qué.

Caching

El caching es una excelente manera de acelerar tu aplicación. Hay varias bibliotecas de caching que se pueden utilizar con Flight.

CLI

Las aplicaciones CLI son una excelente manera de interactuar con tu aplicación. Puedes usarlas para generar controladores, mostrar todas las rutas y más.

Cookies

Las cookies son una excelente manera de almacenar pequeñas cantidades de datos en el lado del cliente. Pueden usarse para almacenar preferencias del usuario, configuraciones de la aplicación y más.

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.

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 algunas son ORM completamente desarrolladas.

Cifrado

El cifrado es crucial para cualquier aplicación que almacene datos sensibles. Cifrar y descifrar los datos no es terriblemente difícil, pero almacenar adecuadamente la clave de cifrado puede ser difícil. Lo más importante es nunca almacenar tu clave de cifrado en un directorio público o comprometerla en tu repositorio de código.

Cola de Trabajo

Las colas de trabajo 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.

Sesión

Las sesiones realmente no son útiles para APIs, pero para construir una aplicación web, las sesiones pueden ser cruciales para mantener el estado y la información de inicio de sesión.

Plantillas

Las plantillas son fundamentales para cualquier aplicación web con una UI. Hay una serie de motores de plantillas que se pueden utilizar con Flight.

Contribuyendo

¿Tienes un plugin que te gustaría compartir? ¡Envía una solicitud de extracción para agregarlo a la lista!

Media

Media

Hemos intentado rastrear lo que podemos de los diversos tipos de medios en Internet alrededor de Flight. Consulta a continuación diferentes recursos que puedes utilizar para aprender más sobre Flight.

Artículos y Escrituras

Videos y Tutoriales

Examples

¿Necesitas un comienzo rápido?

Tienes dos opciones para comenzar con un nuevo proyecto de Flight:

Ejemplos contribuidos por la comunidad:

¿Necesitas algo de inspiración?

Aunque estos no están oficialmente patrocinados por el equipo de Flight, ¡podrían darte ideas sobre cómo estructurar tus propios proyectos construidos con Flight!

¿Quieres compartir tu propio ejemplo?

Si tienes un proyecto que deseas compartir, ¡envía una solicitud de extracción para agregarlo a esta lista!

Install/install

Instalación

Descargar los archivos

Asegúrate de tener PHP instalado en tu sistema. Si no lo tienes, haz clic aquí para obtener instrucciones sobre cómo instalarlo en tu sistema.

Si estás utilizando Composer, puedes ejecutar el siguiente comando:

composer require flightphp/core

O puedes descargar los archivos directamente y extraerlos en tu directorio web.

Configurar tu Servidor Web

Servidor de Desarrollo PHP Integrado

Esta es la forma más sencilla de poner en marcha. Puedes utilizar el servidor integrado para ejecutar tu aplicación e incluso usar SQLite como base de datos (si sqlite3 está instalado en tu sistema) ¡y no requiere mucho más! Simplemente ejecuta el siguiente comando una vez que PHP esté instalado:

php -S localhost:8000

Luego abre tu navegador e ingresa a http://localhost:8000.

Si deseas establecer como directorio raíz de tu proyecto un directorio diferente (por ejemplo, tu proyecto es ~/myproject, pero tu directorio raíz es ~/myproject/public/), puedes ejecutar el siguiente comando una vez que te encuentres en el directorio ~/myproject:

php -S localhost:8000 -t public/

Luego abre tu navegador e ingresa a http://localhost:8000.

Apache

Asegúrate de que Apache ya esté instalado en tu sistema. Si no lo está, busca 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 de RewriteEngine On.

Nota: Si deseas proteger todos los archivos del servidor, como un archivo db o env. Coloca lo siguiente en tu archivo .htaccess:

RewriteEngine On
RewriteRule ^(.*)$ index.php

Nginx

Asegúrate de que Nginx ya esté instalado en tu sistema. Si no lo está, busca cómo instalar Nginx en tu sistema.

Para Nginx, agrega lo siguiente a la declaración de tu servidor:

server {
  location / {
    try_files $uri $uri/ /index.php;
  }
}

Crea tu archivo index.php

<?php

// Si estás utilizando Composer, require el autoloader.
require 'vendor/autoload.php';
// si no estás utilizando 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 '¡hola mundo!';
});

// Finalmente, inicia el framework.
Flight::start();

Instalación de PHP

Si ya tienes php instalado en tu sistema, puedes saltarte estas instrucciones y pasar a la sección de descarga

¡Claro! Aquí tienes las instrucciones para instalar PHP en macOS, Windows 10/11, Ubuntu y Rocky Linux. También incluiré detalles sobre cómo instalar diferentes versiones de PHP.

macOS

Instalar PHP usando Homebrew

  1. Instalar Homebrew (si aún no está instalado):

    • Abre Terminal y ejecuta:
      /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
  2. Instalar PHP:

    • Instalar la última versión:
      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
  3. Cambiar entre versiones de PHP:

    • Desvincula la versión actual y enlaza la versión deseada:
      brew unlink php
      brew link --overwrite --force php@8.1
    • Verifica la versión instalada:
      php -v

Windows 10/11

Instalar PHP manualmente

  1. Descargar PHP:

    • Visita PHP for Windows y descarga la última versión o una específica (por ejemplo, 7.4, 8.0) como un archivo zip no seguro para subprocesos.
  2. Extraer PHP:

    • Extrae el archivo zip descargado en C:\php.
  3. Agregar PHP al PATH del sistema:

    • Ve a Propiedades del Sistema > Variables de Entorno.
    • En Variables del sistema, encuentra Path y haz clic en Editar.
    • Agrega la ruta C:\php (o donde hayas extraído PHP).
    • Haz clic en Aceptar para cerrar todas las ventanas.
  4. Configurar PHP:

    • Copia php.ini-development a php.ini.
    • Edita php.ini para configurar PHP según sea necesario (por ejemplo, configurar extension_dir, habilitar extensiones).
  5. Verificar la instalación de PHP:

    • Abre el Símbolo del sistema y ejecuta:
      php -v

Instalar Múltiples Versiones de PHP

  1. Repite los pasos anteriores para cada versión, colocando cada una en un directorio separado (por ejemplo, C:\php7, C:\php8).

  2. Cambiar entre versiones ajustando la variable PATH del sistema para que apunte al directorio de la versión deseada.

Ubuntu (20.04, 22.04, etc.)

Instalar PHP usando apt

  1. Actualizar listas de paquetes:

    • Abre Terminal y ejecuta:
      sudo apt update
  2. Instalar PHP:

    • Instalar la última versión de PHP:
      sudo apt install php
    • Para instalar una versión específica, por ejemplo, PHP 8.1:
      sudo apt install php8.1
  3. Instalar módulos adicionales (opcional):

    • Por ejemplo, para instalar soporte para MySQL:
      sudo apt install php8.1-mysql
  4. Cambiar entre versiones de PHP:

    • Usa update-alternatives:
      sudo update-alternatives --set php /usr/bin/php8.1
  5. Verificar la versión instalada:

    • Ejecuta:
      php -v

Rocky Linux

Instalar PHP usando yum/dnf

  1. Habilitar el repositorio EPEL:

    • Abre Terminal y ejecuta:
      sudo dnf install epel-release
  2. Instalar el repositorio de Remi:

    • Ejecuta:
      sudo dnf install https://rpms.remirepo.net/enterprise/remi-release-8.rpm
      sudo dnf module reset php
  3. Instalar 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
  4. Cambiar 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
  5. Verificar la versión instalada:

    • Ejecuta:
      php -v

Notas Generales

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 se puede utilizar 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:

Este tutorial es perfecto para principiantes que quieren ver cómo todas las piezas encajan en una aplicación real.

Guías No Oficiales

Aunque estas guías no están oficialmente mantenidas por el equipo de Flight, son recursos valiosos creados por la comunidad. Cubren varios temas y casos de uso, proporcionando información adicional sobre el uso de Flight PHP.

Creando una API RESTful con Flight Framework

Esta guía te guía a través de la creación de una API RESTful utilizando el marco de Flight PHP. Cubre los conceptos básicos para configurar una API, definir rutas y devolver respuestas JSON.

Construyendo un Blog Simple

Esta guía te guía a través de la creación de un blog básico utilizando el marco de 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.

Construyendo una API de Pokémon en PHP: Guía para Principiantes

Esta divertida guía te guía a través de la creación de una API simple de Pokémon utilizando Flight PHP. Cubre los conceptos básicos de configuración de una API, definición de rutas y devolución de respuestas JSON.

Contribuyendo

¿Tienes una idea para una guía? ¿Encontraste un error? ¡Aceptamos 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, envía una solicitud de extracción. Compartir tu conocimiento ayuda a la comunidad de Flight a crecer.

¿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.