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. Está construido con 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í tienes un breve artículo sobre por qué deberías usar un marco. Es una buena idea entender 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 gran detalle sobre Flight específicamente, esta guía te ayudará a comprender algunos de los conceptos principales que rodean un marco y por qué son beneficiosos de usar. Puedes encontrar el tutorial aquí.

Temas Principales

Carga Automática

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

Plantillas HTML

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

Seguridad

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

Configuración

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

Extender Flight

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

Eventos y Filtrado

Aprende cómo utilizar 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 utilizar contenedores de inyección de dependencias (DIC) para administrar las dependencias de tu aplicación.

API del Marco

Aprende sobre los métodos principales del marco.

Migración a 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 la v2 a la v3.

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

Migrar a v3

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

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

El almacenamiento en búfer de salida es el proceso mediante el cual la salida generada por un script de PHP se almacena en un búfer (interno de PHP) antes de ser enviado al cliente. Esto le permite modificar la salida antes de que se envíe al cliente.

En una aplicación MVC, el Controlador es el "gerente" y gestiona lo que hace la vista. Generar la salida fuera del controlador (o en el caso de Flight, a veces en una función anónima) rompe el patrón MVC. Este cambio busca estar más en línea con el patrón MVC y hacer que el marco sea más predecible y fácil de usar.

En v2, el almacenamiento en búfer de salida se manejaba de tal manera que no cerraba consistentemente su propio búfer de salida, lo que dificultaba las pruebas unitarias y el streaming. Para la mayoría de los usuarios, este cambio puede que realmente no les afecte. Sin embargo, si está haciendo un eco de contenido fuera de los callables y controladores (por ejemplo, en un hook), es probable que tenga problemas. Hacer un eco de contenido en ganchos y antes de que el marco realmente se ejecute puede haber funcionado en el pasado, pero no funcionará en adelante.

Dónde podrías tener problemas

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

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

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

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

Flight::before('inicio', 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::hola();
});

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

Activar el Comportamiento de Renderización de v2

¿Puedes seguir manteniendo tu código antiguo tal como está sin tener que reescribirlo para que funcione con v3? ¡Sí, puedes! Puedes activar el comportamiento de renderización de v2 configurando la opción de configuración flight.v2.output_buffering en true. Esto te permitirá seguir utilizando el antiguo comportamiento de renderización, pero se recomienda corregirlo en el futuro. En la versión 4 del marco, esto se eliminará.

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

Flight::set('flight.v2.output_buffering', true);

Flight::before('inicio', 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 ha estado llamando directamente a métodos estáticos para Dispatcher como Dispatcher::invokeMethod(), Dispatcher::execute(), etc., deberá actualizar su código para no llamar directamente a estos métodos. Dispatcher se ha convertido en algo más orientado a objetos para permitir que los Contenedores de Inyección de Dependencias se utilicen de una manera más fácil. Si necesita invocar un método de manera similar a como lo hacía Dispatcher, puede usar manualmente algo como $resultado = $clase->$método(...$params); o call_user_func_array() en su lugar.

Learn/configuration

Configuración

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

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

Configuraciones Disponibles

La siguiente es una lista de todas las configuraciones disponibles:

Variables

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

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

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

Para ver si una variable ha sido establecida, puedes hacer:

if (Flight::has('id')) {
  // Haz 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 propósitos 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 por sus propias necesidades:

Flight::map('error', function (Throwable $error) {
  // Maneja el 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 por sus propias necesidades:

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

Learn/security

Seguridad

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

Cabeceras

Las cabeceras de HTTP son una de las formas más fáciles de proteger tus aplicaciones web. Puedes utilizar cabeceras para evitar el secuestro de clics, XSS y otros ataques. Hay varias formas de agregar estas cabeceras a tu aplicación.

Dos excelentes sitios web para verificar la seguridad de tus cabeceras son securityheaders.com y observatory.mozilla.org.

Agregar Manualmente

Puedes agregar manualmente estas cabeceras utilizando el método header en el objeto Flight\Response.

// Configura la cabecera X-Frame-Options para evitar el secuestro de clics
Flight::response()->header('X-Frame-Options', 'SAMEORIGIN');

// Configura la cabecera Content-Security-Policy para evitar XSS
// Nota: esta cabecera puede volverse muy compleja, así que es mejor
//  consultar ejemplos en internet para tu aplicación
Flight::response()->header("Content-Security-Policy", "default-src 'self'");

// Configura la cabecera X-XSS-Protection para evitar XSS
Flight::response()->header('X-XSS-Protection', '1; mode=block');

// Configura la cabecera X-Content-Type-Options para evitar el sniffing de tipo MIME
Flight::response()->header('X-Content-Type-Options', 'nosniff');

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

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

// Configura la cabecera Permissions-Policy para controlar qué características y APIs se pueden utilizar
Flight::response()->header('Permissions-Policy', 'geolocation=()');

Estas pueden agregarse al principio de tus archivos bootstrap.php o index.php.

Agregar como Filtro

También puedes agregarlas en un filtro/gancho de la siguiente manera:

// Agrega las cabeceras 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 agregarlas 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 agregar
// esto solo a rutas específicas.
Flight::group('', function(Router $router) {
    $router->get('/usuarios', [ 'ControladorUsuario', 'obtenerUsuarios' ]);
    // más rutas
}, [ new SecurityHeadersMiddleware() ]);

Falsificación de Petición en Sitios Cruzados (CSRF)

La falsificación de petición en sitios cruzados (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 se puede utilizar para realizar acciones en tu sitio web sin el conocimiento del usuario. Flight no proporciona un mecanismo de protección CSRF integrado, pero fácilmente puedes implementar el tuyo usando middleware.

Configuración

Primero necesitas generar un token CSRF y almacenarlo en la sesión del usuario. Luego puedes utilizar este token en tus formularios y verificarlo cuando se envíe el formulario.

// Genera un token CSRF y guárdalo en la sesión del usuario
// (asumiendo que has creado un objeto de sesión y lo has adjuntado a Flight)
// Solo necesitas generar un único token por sesión (así funciona
// 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)) );
}
<!-- Utiliza 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 configurar una función personalizada para mostrar el token CSRF en tus plantillas de Latte.

// Configura una función personalizada para mostrar el token CSRF
// Nota: La vista se ha configurado 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 de Latte puedes usar la función csrf() para mostrar el token CSRF.

<form method="post">
    {csrf()}
    <!-- otros campos del formulario -->
</form>

¡Corto y simple, ¿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 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('/usuarios', [ 'ControladorUsuario', 'obtenerUsuarios' ]);
    // más rutas
}, [ new CsrfMiddleware() ]);

Secuencias de Comandos entre Sitios (XSS)

La secuencia de comandos entre sitios (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 los valores de los formularios que completarán tus usuarios. ¡Nunca debes confiar en la salida de tus usuarios! Siempre asume que todos ellos son los mejores hackers del mundo. Pueden inyectar JavaScript malicioso o HTML en tu página. Este código se puede utilizar para robar información de tus usuarios o realizar acciones en tu sitio web. Utilizando la clase de vista de Flight, puedes escapar fácilmente la salida para evitar ataques XSS.

// Vamos a suponer que el usuario es astuto e intenta utilizar esto como su nombre
$nombre = '<script>alert("XSS")</script>';

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

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

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 se puede utilizar para robar información de tu base de datos o realizar acciones en tu base de datos. Nuevamente, ¡nunca debes confiar en la entrada de tus usuarios! Siempre asume que están detrás de ti. Puedes utilizar declaraciones preparadas en tus objetos PDO para prevenir la inyección de SQL.

// Suponiendo que tienes Flight::db() registrado como tu objeto PDO
$declaracion = Flight::db()->prepare('SELECT * FROM usuarios WHERE nombre_usuario = :nombre_usuario');
$declaracion->execute([':nombre_usuario' => $nombre_usuario]);
$usuarios = $declaracion->fetchAll();

// Si utilizas la clase PdoWrapper, esto se puede hacer fácilmente en una línea
$usuarios = Flight::db()->fetchAll('SELECT * FROM usuarios WHERE nombre_usuario = :nombre_usuario', [ 'nombre_usuario' => $nombre_usuario ]);

// Puedes hacer lo mismo con un objeto PDO con marcadores de posición ?
$declaracion = Flight::db()->fetchAll('SELECT * FROM usuarios WHERE nombre_usuario = ?', [ $nombre_usuario ]);

//Solo promete que nunca JAMÁS harás algo como esto...
$usuarios = Flight::db()->fetchAll("SELECT * FROM usuarios WHERE nombre_usuario = '{$nombre_usuario}' LIMIT 5");
// porque ¿qué pasa si $nombre_usuario = "' OR 1=1; -- "; 
// Después de construir la consulta, se vería así
// SELECT * FROM usuarios WHERE nombre_usuario = '' OR 1=1; -- LIMIT 5
// Parece extraño, pero es una consulta válida que funcionará. De hecho,
// es un ataque muy común de inyección de SQL que devolverá todos los usuarios.

CORS

El Compartir Recursos de Origen Cruzado (CORS) es un mecanismo que permite que muchos recursos (por ejemplo, fuentes, JavaScript, etc.) en una página web se soliciten 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 middleware o filtros de eventos similar al CSRF.

// app/middleware/CorsMiddleware.php

namespace app\middleware;

class CorsMiddleware
{
    public function before(array $params): void
    {
        $response = Flight::response();
        if (isset($_SERVER['HTTP_ORIGIN'])) {
            $this->allowOrigins();
            $response->header('Access-Control-Allow-Credentials: true');
            $response->header('Access-Control-Max-Age: 86400');
        }

        if ($_SERVER['REQUEST_METHOD'] == 'OPTIONS') {
            if (isset($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_METHOD'])) {
                $response->header(
                    'Access-Control-Allow-Methods: GET, POST, PUT, DELETE, PATCH, OPTIONS'
                );
            }
            if (isset($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_HEADERS'])) {
                $response->header(
                    "Access-Control-Allow-Headers: {$_SERVER['HTTP_ACCESS_CONTROL_REQUEST_HEADERS']}"
                );
            }
            $response->send();
            exit(0);
        }
    }

    private function allowOrigins(): void
    {
        // personaliza tus hosts permitidos aquí.
        $permitidos = [
            'capacitor://localhost',
            'ionic://localhost',
            'http://localhost',
            'http://localhost:4200',
            'http://localhost:8080',
            'http://localhost:8100',
        ];

        if (in_array($_SERVER['HTTP_ORIGIN'], $permitidos)) {
            $response = Flight::response();
            $response->header("Access-Control-Allow-Origin: {$_SERVER['HTTP_ORIGIN']}");
        }
    }
}

// index.php o donde tengas tus rutas
Flight::route('/usuarios', function() {
    $usuarios = Flight::db()->fetchAll('SELECT * FROM usuarios');
    Flight::json($usuarios);
})->addMiddleware(new CorsMiddleware());

Conclusión

La seguridad es muy importante y es fundamental 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 mantenerse siempre vigilante y asegurarse 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 declaraciones preparadas para evitar la inyección de SQL. Siempre usa middleware para proteger tus rutas de ataques CSRF y CORS. Si haces todas estas cosas, estarás en camino de 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 "¿Por qué un framework?" para obtener una explicación más detallada.

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

Flight::route('/', function(){
    echo '¡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. Por lo tanto, puedes usar una función regular:

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

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

Clases

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

class Saludo {
    public static function hello() {
        echo '¡Hola Mundo!';
    }
}

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

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


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

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

// index.php
$saludo = new Saludo();

Flight::route('/', [ $saludo, 'hello' ]);
// También puedes hacer esto sin crear primero el objeto
// Nota: No se inyectarán argumentos en el constructor
Flight::route('/', [ 'Saludo', 'hello' ]);

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

Si deseas usar 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 o creando directamente el objeto tú mismo y usando el contenedor para crear tu objeto o puedes usar cadenas para definir la clase y el método a llamar. Puedes ir a la página de Inyección de Dependencias para obtener más información.

Aquí tienes un ejemplo rápido:


use flight\database\PdoWrapper;

// Saludo.php
class Saludo
{
    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',
        'password'
    ]
]);

// Registra el manejador del contenedor
Flight::registerContainerHandler(function($class, $params) use ($dice) {
    return $dice->create($class, $params);
});

// Rutas como de costumbre
Flight::route('/hola/@id', [ 'Saludo', 'hello' ]);
// o
Flight::route('/hola/@id', 'Saludo->hello');
// o
Flight::route('/hola/@id', 'Saludo::hello');

Flight::start();

Enrutamiento por Método

Por defecto, los patrones de ruta se emparejan contra todos los métodos de solicitud. Puedes responder a métodos específicos colocando un identificador antes de la URL.

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

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

// No puedes usar Flight::get() para rutas ya que es un método 
//    para obtener variables, no 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 asignar múltiples métodos a una única devolución de llamada usando un delimitador |:

Flight::route('GET|POST /', function () {
  echo 'He recibido 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();

// asigna todos los métodos
$router->map('/', function() {
    echo '¡Hola Mundo!';
});

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

Expresiones Regulares

Puedes usar expresiones regulares en tus rutas:

Flight::route('/usuario/[0-9]+', function () {
  // Esto coincidirá con /usuario/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.

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

También puedes incluir expresiones regulares con tus parámetros nombrados usando el delimitador ::

Flight::route('/@nombre/@id:[0-9]{3}', function (string $nombre, string $id) {
  // Esto coincidirá con /bob/123
  // Pero no coincidirá con /bob/12345
});

Nota: No se admite la coincidencia de grupos de regex () con parámetros nombrados. :'(

Parámetros Opcionales

Puedes especificar parámetros nombrados que son opcionales para emparejar al envolver segmentos en paréntesis.

Flight::route(
  '/blog(/@año(/@mes(/@día)))',
  function(?string $año, ?string $mes, ?string $día) {
    // 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

El emparejamiento solo se realiza en segmentos individuales de URL. Si deseas emparejar múltiples segmentos, puedes usar el comodín *.

Flight::route('/blog/*', function () {
  // Esto coincidirá con /blog/2000/02/01
});

Para dirigir todas las solicitudes a una única devolución de llamada, puedes hacer:

Flight::route('*', function () {
  // Hacer algo
});

Pasando

Puedes pasar la ejecución a la siguiente ruta coincidente devolviendo true desde tu función de devolución de llamada.

Flight::route('/usuario/@nombre', function (string $nombre) {
  // Comprueba alguna condición
  if ($nombre !== "Bob") {
    // Continuar con la siguiente ruta
    return true;
  }
});

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

Alias de Ruta

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

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

// más tarde en algún lugar del código
Flight::getUrl('vista_usuario', [ 'id' => 5 ]); // devolverá '/usuarios/5'

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

El alias de ruta también funciona en grupos:

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

// más tarde en algún lugar del código
Flight::getUrl('vista_usuario', [ 'id' => 5 ]); // devolverá '/usuarios/5'

Información de Ruta

Si deseas inspeccionar la información de la ruta coincidente, puedes solicitar que se pase el objeto de ruta a tu 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 $ruta) {
  // Matriz de métodos HTTP emparejados
  $ruta->methods;

  // Matriz de parámetros nombrados
  $ruta->params;

  // Expresión regular coincidente
  $ruta->regex;

  // Contiene el contenido de cualquier '*' usado en el patrón de URL
  $ruta->splat;

  // Muestra la ruta de la URL....si realmente la necesitas
  $ruta->patrón;

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

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

Agrupación de Rutas

Puede haber momentos en los que desees agrupar rutas relacionadas juntas (como /api/v1). Puedes hacer esto usando el método group:

Flight::group('/api/v1', function () {
  Flight::route('/usuarios', function () {
    // Coincide con /api/v1/usuarios
  });

  Flight::route('/publicaciones', function () {
    // Coindice con /api/v1/publicaciones
  });
});

Incluso puedes anidar grupos de grupos:

Flight::group('/api', function () {
  Flight::group('/v1', function () {
    // Flight::get() obtiene variables, ¡no establece una ruta! Ver contexto de objeto a continuación
    Flight::route('GET /usuarios', function () {
      // Coincide con GET /api/v1/usuarios
    });

    Flight::post('/publicaciones', function () {
      // Coincide con POST /api/v1/publicaciones
    });

    Flight::put('/publicaciones/1', function () {
      // Coincide con PUT /api/v1/publicaciones
    });
  });
  Flight::group('/v2', function () {

    // Flight::get() obtiene variables, ¡no establece una ruta! Ver contexto de objeto a continuación
    Flight::route('GET /usuarios', function () {
      // Coincide con GET /api/v2/usuarios
    });
  });
});

Agrupación con Contexto de Objeto

Todavía puedes usar la agrupación de rutas con el objeto Engine de la siguiente manera:

$app = new \flight\Engine();
$app->group('/api/v1', function (Router $enrutador) {

  // usa la variable $router
  $enrutador->get('/usuarios', function () {
    // Coincide con GET /api/v1/usuarios
  });

  $enrutador->post('/publicaciones', function () {
    // Coincide con POST /api/v1/publicaciones
  });
});

Streaming

Ahora puedes transmitir respuestas al cliente usando el método streamWithHeaders(). Esto es útil para enviar archivos grandes, procesos prolongados o generar respuestas grandes. El enrutamiento de una transmisión se maneja un poco diferente que una ruta regular.

Nota: Las respuestas en transmisión solo están disponibles si tienes flight.v2.output_buffering configurado en false


Flight::route('/usuarios-en-transmision', function() {

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

    // como quieras obtener tus datos, solo como ejemplo...
    $usuarios_stmt = Flight::db()->query("SELECT id, first_name, last_name FROM users");

    echo '{';
    $conteo_usuarios = count($usuarios);
    while($usuario = $usuarios_stmt->fetch(PDO::FETCH_ASSOC)) {
        echo json_encode($usuario);
        if(--$conteo_usuarios > 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',
    // código de estado opcional, por defecto es 200
    'estado' => 200
]);

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 poderosa que te permite gestionar las dependencias de tu aplicación. Es un concepto clave en los frameworks modernos de PHP 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 un lugar centralizado. Esto es útil cuando necesitas pasar el mismo objeto a múltiples 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('/usuario/@id', [ $UserController, 'view' ]);

Flight::start();

Puedes ver en el código anterior que estamos creando un nuevo objeto PDO y pasándolo a nuestra clase UserController. Esto está bien para una aplicación pequeña, pero a medida que tu aplicación crece, te darás cuenta de que estás creando el mismo objeto PDO en varios lugares. Aquí es donde un DIC resulta útil.

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


require 'vendor/autoload.php';

// misma clase que arriba. Nada ha cambiado
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 reasignarlo a sí mismo como se muestra a continuación!
$container = $container->addRule('PDO', [
    // shared significa que se devolverá el mismo objeto cada vez
    'shared' => true,
    'constructParams' => ['mysql:host=localhost;dbname=test', 'user', 'pass' ]
]);

// Esto registra el manejador de contenedores 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('/usuario/@id', [ 'UserController', 'view' ]);
// o alternativamente puedes definir la ruta de esta forma
Flight::route('/usuario/@id', 'UserController->view');
// o
Flight::route('/usuario/@id', 'UserController::view');

Flight::start();

Apuesto a que podrías estar pensando que se agregó mucho código extra al ejemplo. La magia sucede 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á 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('/configuraciones', 'SettingsController->view');

El bono adicional de utilizar un DIC es que las pruebas unitarias se vuelven mucho más fáciles. Puedes crear un objeto simulado y pasarlo a tu clase. ¡Esto es de gran ayuda cuando estás escribiendo 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('/usuario', [ 'UserController', 'view' ]);

Flight::start();

Aunque este puede ser un poco más detallado que el ejemplo anterior con Dice, ¡sigue cumpliendo con la misma funcionalidad!

Manejador DIC Personalizado

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

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

Instancia del Motor

Si estás utilizando la instancia Engine en tus controladores/middleware, así es como la 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 utilizar 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');
    }
}

Agregar Otras Clases

Si tienes otras clases que deseas 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 middleware 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 controles de autenticación de API en su código, o para validar que el usuario tenga permiso para acceder a la ruta.

Middleware Básico

Aquí hay 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 clases (ver abajo)
Flight::route('/ruta', function() { echo '¡Aquí estoy!'; })->addMiddleware(function() {
    echo '¡Primero el Middleware!';
});

Flight::start();

// ¡Esto producirá "¡Primero el Middleware! ¡Aquí estoy!"

Hay algunas notas muy importantes sobre el middleware que debe tener en cuenta antes de usarlos:

Clases de Middleware

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

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

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

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

Flight::start();

// Esto mostrará "¡Primero el Middleware! ¡Aquí estoy! ¡Último el Middleware!"

Agrupando Middleware

Puede agregar un grupo de ruta y luego cada ruta en ese grupo tendrá el mismo middleware también. Esto es útil si necesita agrupar un montón de rutas, por ejemplo, un middleware de Autenticación para verificar la clave API en la cabecera.


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

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

Si desea aplicar un middleware global a todas sus rutas, puede agregar un grupo "vacío":


// añadido al final del método de grupo
Flight::group('', function() {
    Flight::route('/usuarios', function() { echo 'usuarios'; }, false, 'usuarios');
    Flight::route('/usuarios/@id', function($id) { echo 'usuario:'.$id; }, false, 'ver_usuario');
}, [ 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, al cual se puede acceder así:

$solicitud = Flight::solicitud();

El objeto de solicitud proporciona las siguientes propiedades:

Se puede acceder a las propiedades query, datos, cookies y archivos como arreglos u objetos.

Entonces, para obtener un parámetro de cadena de consulta, puedes hacer:

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

O puedes hacer:

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

Cuerpo de Solicitud sin Procesar

Para obtener el cuerpo sin procesar de la solicitud HTTP, por ejemplo, al tratar con solicitudes PUT, puedes hacer:

$cuerpo = Flight::solicitud()->getBody();

Entrada JSON

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

$id = Flight::solicitud()->datos->id;

Acceso a $_SERVER

Hay un atajo disponible para acceder a la matriz $_SERVER a través del método getVar():

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

Accediendo a Cabeceras de Solicitud

Puedes acceder a las cabeceras de la solicitud utilizando el método getHeader() o getHeaders():


// Tal vez necesites la cabecera de Autorización
$host = Flight::solicitud()->getHeader('Authorization');

// Si necesitas obtener todas las cabeceras
$cabeceras = Flight::solicitud()->getHeaders();

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. A continuación se muestra el conjunto completo de métodos para el framework. Consta de métodos principales, que son métodos estáticos regulares, y métodos extensibles, que son métodos mapeados que pueden ser filtrados o sobreescritos.

Métodos Principales

Estos métodos son fundamentales para el framework y no pueden ser sobreescritos.

Flight::map(string $nombre, callable $callback, bool $pasar_ruta = false) // Crea un método personalizado para el framework.
Flight::register(string $nombre, string $clase, array $params = [], ?callable $callback = null) // Registra una clase a un método del framework.
Flight::unregister(string $nombre) // Anula el registro de una clase a un método del framework.
Flight::before(string $nombre, callable $callback) // Agrega un filtro antes de un método del framework.
Flight::after(string $nombre, callable $callback) // Agrega un filtro después de un método del framework.
Flight::path(string $ruta) // Agrega una ruta para cargar clases automáticamente.
Flight::get(string $clave) // Obtiene una variable.
Flight::set(string $clave, mixed $valor) // Establece una variable.
Flight::has(string $clave) // Comprueba si una variable está establecida.
Flight::clear(array|string $clave = []) // Borra 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 la solicitud
Flight::response() // Obtiene la instancia del objeto de la respuesta
Flight::router() // Obtiene la instancia del objeto del 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 $código = 200, string $mensaje = '') // Detiene el framework con un código de estado y mensaje opcionales.
Flight::route(string $patrón, callable $callback, bool $pasar_ruta = false, string $alias = '') // Asocia un patrón de URL a un callback.
Flight::post(string $patrón, callable $callback, bool $pasar_ruta = false, string $alias = '') // Asocia un patrón de URL de solicitud POST a un callback.
Flight::put(string $patrón, callable $callback, bool $pasar_ruta = false, string $alias = '') // Asocia un patrón de URL de solicitud PUT a un callback.
Flight::patch(string $patrón, callable $callback, bool $pasar_ruta = false, string $alias = '') // Asocia un patrón de URL de solicitud PATCH a un callback.
Flight::delete(string $patrón, callable $callback, bool $pasar_ruta = false, string $alias = '') // Asocia un patrón de URL de solicitud DELETE a un callback.
Flight::group(string $patrón, callable $callback) // Crea agrupaciones para URL, el patrón debe ser una cadena.
Flight::getUrl(string $nombre, array $params = []) // Genera una URL basada en un alias de ruta.
Flight::redirect(string $url, int $código) // Redirige a otra URL.
Flight::render(string $archivo, array $datos, ?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 $tipo = 'cadena') // Realiza el almacenamiento en caché HTTP ETag.
Flight::lastModified(int $tiempo) // Realiza el almacenamiento en caché HTTP Last-Modified.
Flight::json(mixed $datos, int $código = 200, bool $codificar = true, string $charset = 'utf8', int $opción) // Envía una respuesta JSON.
Flight::jsonp(mixed $datos, string $param = 'jsonp', int $código = 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/why_frameworks

¿Por qué un Framework?

Algunos programadores se oponen vehementemente a usar frameworks. Argumentan que los frameworks son pesados, lentos y difíciles de aprender. Dicen que los frameworks son innecesarios y que se puede escribir un mejor código sin ellos. Ciertamente, hay algunos puntos válidos que se pueden hacer sobre las desventajas de usar frameworks. Sin embargo, también hay muchas ventajas en utilizar frameworks.

Razones para Usar un Framework

Aquí hay algunas razones por las que podrías querer 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 gran parte 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 de forma rápida y sencilla. Si eres nuevo en los frameworks, Flight es un excelente framework para principiantes con el que empezar. Te ayudará a aprender acerca de las ventajas de usar frameworks sin saturarte con demasiada complejidad. Después de ganar 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 sólida y exitosa.

¿Qué es el Enrutamiento?

El enrutamiento es el núcleo del framework Flight, pero ¿qué es exactamente? El enrutamiento es el proceso de tomar una URL y hacerla coincidir con una función específica en tu código. De esta manera puedes hacer que tu sitio web haga diferentes cosas basadas en la URL solicitada. 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 hacer tu vida dramáticamente más fácil! Puede ser difícil verlo al principio. Aquí tienes algunas razones:

Seguramente estás familiarizado con la forma script por script de crear un sitio web. Tal vez tengas un archivo llamado index.php que tiene un montón de declaraciones if para revisar la URL y luego ejecutar una función específica basada en la URL. Esto es una forma de enrutamiento, pero no está muy organizada y puede descontrolarse 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' ]);

// Tal vez en tu app/controladores/ControladorUsuario.php
class ControladorUsuario {
    public function verPerfilUsuario($id) {
        // hacer algo
    }

    public function editarPerfilUsuario($id) {
        // hacer algo
    }
}

Espero que empieces a ver los beneficios de utilizar 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 el núcleo de lo que hace un framework web. Toma una solicitud del navegador de un 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 que un usuario inicie sesión o permitir a un usuario publicar una nueva publicación de blog.

Solicitudes

Una solicitud es lo que envía el navegador de un usuario a tu servidor cuando visita tu sitio web. Esta solicitud contiene información sobre lo que el usuario desea hacer. Por ejemplo, podría contener información sobre qué URL desea visitar el usuario, qué datos quiere enviar al servidor o qué tipo de datos desea recibir del servidor. Es importante saber que una solicitud es de solo lectura. No puedes cambiar la solicitud, pero puedes leerla.

Flight proporciona una forma sencilla de acceder a la información sobre la solicitud. Puedes acceder a la 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 de un usuario cuando visita tu sitio web. Esta respuesta contiene información sobre lo que tu servidor desea hacer. Por ejemplo, podría contener información sobre qué tipo de datos quiere enviar al usuario tu servidor, qué tipo de datos desea recibir del usuario tu servidor o qué tipo de datos quiere almacenar en la computadora del usuario tu servidor.

Flight proporciona una forma simple de enviar una respuesta al navegador de un usuario. Puedes enviar una respuesta utilizando 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 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 para ti, pero tienes 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 añadir al cuerpo también.


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

    // si deseas recuperar el cuerpo que has definido en este punto
    // puedes hacerlo de la siguiente manera
    $body = Flight::response()->getBody();
});

Códigos de Estado

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

Flight::route('/@id', function($id) {
    if($id == 123) {
        Flight::response()->status(200);
        echo "¡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 argumentos:

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

Estableciendo un Encabezado de Respuesta

Puedes establecer un encabezado como el tipo de contenido de la respuesta usando 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');
    echo "¡Hola, Mundo!";
});

JSON

Flight proporciona soporte para enviar respuestas en formato JSON y JSONP. Para enviar una respuesta JSON debes pasar algunos datos para ser codificados en JSON:

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

JSONP

Para peticiones 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');

Así, 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 establecerá por defecto como jsonp.

Redireccionar a otra URL

Puedes redireccionar la solicitud actual usando el método redirect() y proporcionando una nueva URL:

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

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

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

Detención

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

Flight::halt();

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

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

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

Flight::stop();

Caché HTTP

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

Caché a Nivel de Ruta

Si deseas cachear toda tu respuesta, puedes utilizar el método cache() y pasarle el tiempo a cachear.


// Esto cacheará la respuesta durante 5 minutos
Flight::route('/noticias', 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('/noticias', function () {
  Flight::cache('+5 minutos');
  echo 'Este contenido será almacenado en caché.';
});

Última Modificación

Puedes usar el método lastModified y pasarle una marca de tiempo UNIX para establecer la fecha y hora en que una página fue modificada por última vez. El cliente seguirá utilizando su caché hasta que el valor de última modificación cambie.

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

ETag

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

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

Ten en cuenta que llamar a lastModified o etag establecerá y verificará el valor de caché. Si el valor de caché es similar entre solicitudes, Flight enviará inmediatamente una respuesta HTTP 304 y dejará de procesar.

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

Flight proporciona alguna funcionalidad básica de plantillas de forma predeterminada.

Si necesitas necesidades de plantillas más complejas, consulta los ejemplos de Smarty y Latte en la sección de Vistas Personalizadas.

Para mostrar una plantilla de vista llama al método render con el nombre del archivo de la plantilla y datos de la plantilla opcionales:

Flight::render('hello.php', ['name' => 'Bob']);

Los datos de la plantilla que pasas se inyectan automáticamente en la plantilla y pueden ser referenciados 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 usando el método set:

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

La variable name está ahora disponible en todas tus vistas. Por lo tanto, simplemente puedes hacer:

Flight::render('hello');

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

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

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

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á entonces variables guardadas llamadas headerContent y bodyContent. Luego puedes renderizar tu diseño haciendo:

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

Si los archivos de plantilla lucen 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 te permite intercambiar el motor de vista predeterminado simplemente registrando tu propia clase de vista.

Smarty

Así es como usarías el motor de plantillas Smarty para tus vistas:

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

// Registrar Smarty como la clase de vista
// También pasa 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/');
});

// Asignar datos de la plantilla
Flight::view()->assign('name', 'Bob');

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

Para completitud, también deberías sobrescribir el método render predeterminado de Flight:

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

Latte

Así es como usarías el motor de plantillas Latte para tus vistas:


// Registrar Latte como la clase de vista
// También pasa una función de devolución de llamada para configurar Latte al cargar
Flight::register('view', Latte\Engine::class, [], function (Latte\Engine $laette) {
  // Aquí es donde Latte almacenará en caché tus plantillas para acelerar las cosas
  // ¡Algo genial de 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 para tus vistas.
  $latte->setLoader(new \Latte\Loaders\FileLoader(__DIR__ . '/../views/'));
});

// Y envuélvelo 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);
});

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 anular clases y métodos existentes.

Si buscas un DIC (Dependency Injection Container), dirígete a la página del Dependency Injection Container.

Mapeo de Métodos

Para mapear tu propio método personalizado simple, utiliza 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');

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

Registro de Clases

Para registrar tu propia clase y configurarla, utiliza 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. Entonces, al cargar 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 de base de datos:

// Registra 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, simplemente llama al mismo método nuevamente
class SomeController {
  public function __construct() {
    $this->db = Flight::db();
  }
}

Si pasas un parámetro adicional de callback, 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.

// Se pasará el objeto que se creó al callback
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 cargas 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
$compartido = Flight::db();

// Nueva instancia de la clase
$nueva = Flight::db(false);

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

Anulando Métodos del Marco

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

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 de HTTP 404. Puedes anular este comportamiento utilizando el método map:

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

Flight también te permite reemplazar componentes principales del marco. Por ejemplo, puedes reemplazar la clase de 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
$mienrutador = Flight::router();

Sin embargo, los métodos del marco como map y register no se pueden anular. Obtendrá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/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 un árbol de directorios como el siguiente:

# Ruta de ejemplo
/home/user/project/my-flight-project/
├── app
│   ├── cache
│   ├── config
│   ├── controllers - contiene los controladores para este proyecto
│   ├── translations
│   ├── UTILS - contiene clases solo para esta aplicación (esto está en mayúsculas a propósito para un ejemplo posterior)
│   └── vistas
└── 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 desde el cual cargar de esta manera:


/**
 * 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)
// Es un requisito que no se puede tener un guion bajo en el nombre de tu clase
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 la raíz del documento 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 lucir tu controlador. Mira 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
namespace app\controllers;

// Se recomienda que todas las clases cargadas automáticamente estén en Pascal Case (cada palabra en mayúscula, sin espacios)
// Es un requisito que no se puede tener un guion bajo en el nombre de tu clase
class MyController {

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

Y si deseas cargar automáticamente una clase en tu directorio de utils, harías básicamente lo mismo:


/**
 * app/UTILS/ArrayHelperUtil.php
 */

// el espacio de nombres debe coincidir con la estructura y el caso del directorio (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
    }
}

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

License

Licencia MIT (MIT)

========================

Derechos de autor © 2023 @mikecao, @n0nag0n

Se concede permiso, de forma gratuita, a cualquier persona que obtenga una copia de este software y documentación asociada (los “Software”), para tratar el Software sin restricciones, incluidos, entre otros, los derechos de usar, copiar, modificar, fusionar, publicar, distribuir, sublicenciar y/o vender copias del Software, y para permitir a las personas a las que se les proporcione el Software que lo hagan, sujetas a las siguientes condiciones:

El aviso de derechos de autor anterior y este aviso de permiso deberán incluirse en todas las copias o partes sustanciales del Software.

EL SOFTWARE SE SUMINISTRA "TAL CUAL", SIN GARANTÍA DE NINGÚN TIPO, EXPRESA O IMPLÍCITA, INCLUYENDO, PERO SIN LIMITARSE A, LAS GARANTÍAS DE COMERCIALIZACIÓN, ADECUACIÓN PARA UN PROPÓSITO PARTICULAR Y NO INFRACCIÓN. EN NINGÚN CASO LOS AUTORES O TITULARES DE 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 OTRO MODO, DERIVADA DE, FUERA DE O EN CONEXIÓN CON EL SOFTWARE O EL USO U OTROS TRATOS EN EL SOFTWARE.

About

¿Qué es Flight?

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

Flight es un excelente framework para principiantes que son nuevos en PHP y desean aprender a construir aplicaciones web. También es un gran framework para desarrolladores experimentados que desean tener 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.

Inicio rápido

<?php

// si está instalado con composer
require 'vendor/autoload.php';
// o si está instalado manualmente por archivo zip
// require 'flight/Flight.php';

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

Flight::route('/json', function() {
  Flight::json(['hello' => 'world']);
});

Flight::start();

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

Aplicación Esqueleto/Base

Hay una aplicación de ejemplo que puede ayudarte a empezar con el Framework Flight. ¡Ve a flightphp/skeleton para obtener instrucciones sobre cómo empezar! 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! Chatea con nosotros en #flight-php-framework:matrix.org.

Contribuciones

Hay dos maneras en las que puedes contribuir a Flight:

  1. Puedes contribuir al framework principal 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 estar al día en las cosas, pero las actualizaciones y traducciones de idiomas son bienvenidas.

Requisitos

Flight requiere PHP 7.4 o superior.

Nota: Se admite PHP 7.4 porque en el momento actual de la escritura (2024) PHP 7.4 es la versión predeterminada para algunas distribuciones de Linux LTS. Forzar un cambio a PHP >8 causaría muchos dolores de cabeza a esos usuarios. El framework también soporta 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

Wruczek/PHP-File-Cache

Clase de almacenamiento en caché de PHP en archivo ligera, simple y independiente

Ventajas

Instalación

Instalar a través de composer:

composer require wruczek/php-file-cache

Uso

El uso es bastante sencillo.

use Wruczek\PhpFileCache\PhpFileCache;

$app = Flight::app();

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

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

Luego puedes usarlo en tu código de la siguiente manera:


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

// o
$data = $cache->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/Wruczek/PHP-File-Cache para ver la documentación completa y asegúrate de ver la carpeta de ejemplos.

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

Clase de Ayuda PdoWrapper para PDO

Flight viene con una clase de ayuda para PDO. Te permite consultar fácilmente tu base de datos con toda la locura de preparar/ejecutar/fetchAll(). Simplifica en gran medida cómo puedes consultar tu base de datos. Cada resultado de fila se devuelve como una clase de colección de Flight que te permite acceder a tus datos mediante sintaxis de array o sintaxis de objeto.

Registro de 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', 'user', 'pass', [
        PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES \'utf8mb4\'',
        PDO::ATTR_EMULATE_PREPARES => falso,
        PDO::ATTR_STRINGIFY_FETCHES => falso,
        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. Se agregan los siguientes métodos para facilitar la consulta de la base de datos:

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

Úsalo para INSERTS, UPDATES, o si planeas usar un SELECT en un bucle while

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

// O escribiendo en la base de datos
$db->runQuery("INSERT INTO table (name) VALUES (?)", [ $name ]);
$db->runQuery("UPDATE table SET name = ? WHERE id = ?", [ $name, $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 = ?", [ $something ]);

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

Extrae una fila de la consulta

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

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

Extrae todas las filas de la consulta

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

Nota sobre la sintaxis de IN()

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

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

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
    $statement = Flight::db()->runQuery('SELECT * FROM users');
    while ($usuario = $statement->fetch()) {
        echo $usuario['name'];
        // o echo $usuario->name;
    }

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

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

    // Sintaxis especial de IN() para ayudar (asegúrate de que IN esté en mayúsculas)
    $usuarios = Flight::db()->fetchAll('SELECT * FROM users WHERE id IN (?)', [[1,2,3,4,5]]);
    // también podrías 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 (name, email) VALUES (?, ?)", ['Bob', 'bob@example.com']);
    $insert_id = Flight::db()->lastInsertId();

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

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

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

});

Awesome-plugins/session

Ghostff/Session

Administrador de Sesiones PHP (no bloqueante, flash, segmento, encriptación de sesiones). Utiliza open_ssl de PHP para encriptar/desencriptar opcionalmente datos de sesión. Admite Archivo, MySQL, Redis y Memcached.

Instalación

Instala con composer.

composer require ghostff/session

Configuración Básica

No es necesario pasar nada para usar la configuración predeterminada con tu sesión. Puedes leer sobre más configuraciones en el Github Readme.


use Ghostff\Session\Session;

require 'vendor/autoload.php';

$app = Flight::app();

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

// una cosa a tener en cuenta 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í tienes un ejemplo sencillo 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 correcto
    $session->set('is_logged_in', true);
    $session->set('user', $user);

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

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

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

    // realiza tu lógica de página restringida aquí
});

// la versión de 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í tienes un ejemplo más complejo de cómo podrías usar esto.


use Ghostff\Session\Session;

require 'vendor/autoload.php';

$app = Flight::app();

// establece una ruta personalizada a tu archivo de configuración de sesión y dale una cadena aleatoria para el id de sesión
$app->register('session', Session::class, [ 'ruta/a/session_config.php', bin2hex(random_bytes(32)) ], function(Session $session) {
        // o puedes anular manualmente las opciones de configuración
        $session->updateConfiguration([
            // si deseas almacenar tus datos de sesión en una base de datos (útil si deseas algo como funcionalidad "ciérrame la sesión en todos los dispositivos")
            Session::CONFIG_DRIVER        => Ghostff\Session\Drivers\MySql::class,
            Session::CONFIG_ENCRYPT_DATA  => true,
            Session::CONFIG_SALT_KEY      => hash('sha256', 'mi-super-secreto-salt'), // por favor cambia esto a algo más
            Session::CONFIG_AUTO_COMMIT   => true, // hazlo solo si es necesario y/o es difícil confirmar() tu sesión.
                                                // adicionalmente podrías hacer Flight::after('start', function() { Flight::session()->commit(); });
            Session::CONFIG_MYSQL_DS         => [
                'driver'    => 'mysql',             # Controlador de base de datos para PDO dns, ej. (mysql:host=...;dbname=...)
                'host'      => '127.0.0.1',         # Host de la base de datos
                'db_name'   => 'mi_basededatos_app',   # Nombre de la base de datos
                'db_table'  => 'sesiones',          # Tabla de la base de datos
                'db_user'   => 'root',              # Nombre de usuario de la base de datos
                'db_pass'   => '',                  # Contraseña de la base de datos
                'persistent_conn'=> false,          # Evita la sobrecarga de establecer una nueva conexión cada vez que un script necesita comunicarse con una base de datos, lo que resulta en una aplicación web más rápida. BUSCA LA CONTRAPARTE TÚ MISMO
            ]
        ]);
    }
);

Documentación

Visita el Github Readme para ver la documentación completa. Las opciones de configuración están bien documentadas en el archivo default_config.php en sí mismo. El código es fácil de entender si deseas revisar este paquete tú mismo.

Awesome-plugins/tracy_extensions

Extensiones del Panel Tracy para Flight

Este es un conjunto de extensiones para hacer que trabajar con Flight sea un poco más completo.

Este es el Panel

Barra de Flight

¡Y cada panel muestra información muy útil sobre tu aplicación!

Datos de Flight Base de Datos de Flight Petición de Flight

Instalación

Ejecuta composer require flightphp/tracy-extensions --dev ¡y estás listo para comenzar!

Configuración

Hay muy poca configuración que necesitas hacer para comenzar. Deberás iniciar el depurador Tracy antes de usar esto https://tracy.nette.org/en/guide:

<?php

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

// código de inicio
require __DIR__ . '/vendor/autoload.php';

Debugger::enable();
// Puede que necesites especificar tu entorno con Debugger::enable(Debugger::DEVELOPMENT)

// si usas conexiones a bases de datos en tu aplicación, hay
// un envoltorio PDO requerido para USO ÚNICAMENTE EN DESARROLLO (¡no en producción por favor!)
// Tiene los mismos parámetros que una conexión PDO regular
$pdo = new PdoQueryCapture('sqlite:test.db', 'usuario', 'contraseña');
// o si adjuntas esto al framework Flight
Flight::register('db', PdoQueryCapture::class, ['sqlite:test.db', 'usuario', 'contraseña']);
// ahora cada vez que hagas una consulta capturará el tiempo, la consulta y los parámetros

// Esto conecta los puntos
if(Debugger::$showBar === true) {
    // ¡Esto necesita ser false o Tracy no puede renderizar correctamente!
    Flight::set('flight.content_length', false);
    new TracyExtensionLoader(Flight::app());
}

// más código

Flight::start();

Configuración Adicional

Datos de Sesión

Si tienes un controlador de sesión personalizado (como ghostff/session), puedes pasar cualquier arreglo de datos de sesión a Tracy y automáticamente lo mostrará. Puedes pasarlos con la clave session_data en el segundo parámetro del constructor TracyExtensionLoader.


use Ghostff\Session\Session;

require 'vendor/autoload.php';

$app = Flight::app();

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

if(Debugger::$showBar === true) {
    // ¡Esto necesita ser false o Tracy no puede renderizar correctamente!
    Flight::set('flight.content_length', false);
    new TracyExtensionLoader(Flight::app(), [ 'session_data' => Flight::session()->getAll() ]);
}

// rutas y otras cosas...

Flight::start();

Latte

Si tienes Latte instalado en tu proyecto, puedes usar el panel de Latte para analizar tus plantillas. Puedes pasar la instancia de Latte al constructor TracyExtensionLoader con la clave latte en el segundo parámetro.



use Latte\Engine;

require 'vendor/autoload.php';

$app = Flight::app();

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

    // aquí es donde agregas el Panel de Latte a Tracy
    $latte->addExtension(new Latte\Bridges\Tracy\TracyExtension);
});

if(Debugger::$showBar === true) {
    // ¡Esto necesita ser false o Tracy no puede renderizar correctamente!
    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 Registro Activo

Un registro activo es el mapeo de una entidad de base de datos a un objeto PHP. En pocas palabras, si tienes una tabla de usuarios en tu base de datos, puedes "traducir" una fila en esa tabla a una clase User y a un objeto $user en tu código base. Consulta ejemplo básico.

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
 * 
 * Es muy recomendable 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($conexion_base_de_datos)
    {
        // puedes configurarlo de esta manera
        parent::__construct($conexion_base_de_datos, 'users');
        // o de esta manera
        parent::__construct($conexion_base_de_datos, null, [ 'table' => 'users']);
    }
}

¡Ahora observa cómo sucede la magia!

// para sqlite
$conexion_base_de_datos = new PDO('sqlite:test.db'); // esto es solo un ejemplo, probablemente usarías una conexión de base de datos real

// para mysql
$conexion_base_de_datos = new PDO('mysql:host=localhost;dbname=test_db&charset=utf8bm4', 'usuario', 'contraseña');

// o mysqli
$conexion_base_de_datos = new mysqli('localhost', 'usuario', 'contraseña', 'test_db');
// o mysqli con creación no basada en objetos
$conexion_base_de_datos = mysqli_connect('localhost', 'usuario', 'contraseña', 'test_db');

$user = new User($conexion_base_de_datos);
$user->name = 'Bobby Tables';
$user->password = password_hash('una contraseña genial');
$user->insert();
// o $user->save();

echo $user->id; // 1

$user->name = 'Joseph Mamma';
$user->password = password_hash('¡otra contraseña genial!');
$user->insert();
// ¡no puedes usar $user->save() aquí o pensará que es una actualización!

echo $user->id; // 2

¡Y fue tan fácil agregar un nuevo usuario! Ahora que hay una fila de usuario en la base de datos, ¿cómo la extraes?

$user->find(1); // busca el id = 1 en la base de datos y devuélvelo.
echo $user->name; // 'Bobby Tables'

¿Y si quieres encontrar todos los usuarios?

$usuarios = $user->findAll();

¿Y con una condición determinada?

$usuarios = $user->like('name', '%mamma%')->findAll();

¿Ves lo divertido que es esto? ¡Instalémoslo y empecemos!

Instalación

Simplemente instálalo con Composer

composer require flightphp/active-record 

Uso

Esto se puede utilizar como una biblioteca independiente o con el Marco de PHP Flight. Completamente a tu elección.

Independiente

Solo asegúrate de pasar una conexión PDO al constructor.

$conexion_pdo = new PDO('sqlite:test.db'); // esto es solo un ejemplo, probablemente usarías una conexión de base de datos real

$Usuario = new User($conexion_pdo);

Marco PHP de Flight

Si estás utilizando el Marco PHP de Flight, puedes registrar la clase ActiveRecord como un servicio (pero honestamente no es necesario).

Flight::register('usuario', 'User', [ $conexion_pdo ]);

// luego puedes usarlo de esta manera en un controlador, una función, etc.

Flight::usuario()->find(1);

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 pasarle otros métodos auxiliares para consultar tu tabla.

// encontrar un registro con algunas condiciones previas
$user->notNull('password')->orderBy('id DESC')->find();

// encontrar un registro con 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)

Devuelve 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($conexion_pdo);
$user->name = 'demo';
$user->password = md5('demo');
$user->insert();

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, se actualizará, de lo contrario se insertará.

$user = new User($conexion_pdo);
$user->name = 'demo';
$user->password = md5('demo');
$user->save();

Nota: Si tienes relaciones definidas en la clase, guardará recursivamente esas relaciones también si se han definido, instanciado y tienen datos no guardados. (v0.4.0 y superior)

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

Datos "dirty" se refiere a los datos que han sido cambiados en un registro.

$user->greaterThan('id', 0)->orderBy('id desc')->find();

// no hay nada "dirty" hasta este punto.

$user->email = 'test@example.com'; // ahora el correo electrónico se considera "dirty" ya que ha cambiado.
$user->update();
// ahora no hay datos "dirty" porque se han actualizado y persistido en la base de datos

$user->password = password_hash()'newpassword'); // ahora esto es "dirty"
$user->dirty(); // pasar nada limpiará todas las entradas "dirty".
$user->update(); // nada se actualizará porque no se capturó nada como "dirty".

$user->dirty([ 'name' => 'something', 'password' => password_hash('a different password') ]);
$user->update(); // tanto el nombre como la contraseña se actualizan.

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 se actualizan.

isDirty(): boolean (v0.4.0)

Devuelve true si el registro actual ha 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 útil de usar en comportamientos de bucle. Si pasas true, también restablecerá los datos de la consulta que se usaron para encontrar el objeto actual (comportamiento predeterminado).

$usuarios = $user->greaterThan('id', 0)->orderBy('id desc')->find();
$user_empresa = new UserCompany($conexion_pdo);

foreach($usuarios as $user) {
    $user_empresa->reset(); // comenzar con un lienzo limpio
    $user_empresa->user_id = $user->id;
    $user_empresa->company_id = $algún_id_empresa;
    $user_empresa->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 algunos de los campos en una tabla si así lo deseas (es más eficiente en tablas realmente anchas con muchas columnas)

$user->select('id', 'name')->find();

from(string $table)

¡Técnicamente también puedes elegir otra tabla! ¡Por qué diablos no!

$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 de where personalizados (no puedes establecer parámetros en esta declaración de 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 de SQL! Hay muchos artículos en línea, por favor, busca en Google "ataques de inyección de sql php" y encontrarás muchos artículos sobre este tema. La forma adecuada 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 escaparlo 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 según 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 retornada de cierta manera.

$user->orderBy('name DESC')->find();

limit(string $limit)/limit(int $offset, int $limit)

Limita la cantidad de registros retornados. Si se da un segundo entero, tendrá un desplazamiento, igual que 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();

Relaciones

Puedes configurar varios tipos de relaciones utilizando esta biblioteca. Puedes establecer relaciones uno a muchos y uno a uno entre tablas. Esto requiere un poco más de configuración en la clase de antemano.

Configurar la matriz $relations no es difícil, pero adivinar la sintaxis correcta puede ser confuso.

protected array $relations = [
    // puedes nombrar la clave como quieras. Probablemente el nombre de ActiveRecord sea bueno. Ej: usuario, contacto, cliente
    'usuario' => [
        // requerido
        // self::HAS_MANY, self::HAS_ONE, self::BELONGS_TO
        self::HAS_ONE, // este es el tipo de relación

        // requerido
        'Alguna_Clase', // esta es la clase ActiveRecord "otra" a la que hará referencia esta

        // requerido
        // dependiendo del tipo de relación
        // self::HAS_ONE = la clave externa que hace referencia a la unión
        // self::HAS_MANY = la clave externa que hace referencia a la unión
        // self::BELONGS_TO = la clave local que hace referencia a la unión
        'clave_local_o_externa',
        // solo FYI, esto también se une a la clave primaria del modelo "otro"

        // 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
        'nombre_de_referencia_inversa' // esto es si quieres una referencia inversa de esta relación de nuevo a sí misma Ej: $user->contact->user;
    ];
]
class User extends ActiveRecord{
    protected array $relations = [
        'contactos' => [ self::HAS_MANY, Contacto::class, 'id_usuario' ],
        'contacto' => [ self::HAS_ONE, Contacto::class, 'id_usuario' ],
    ];

    public function __construct($conexion_base_de_datos)
    {
        parent::__construct($conexion_base_de_datos, 'usuarios');
    }
}

class Contacto extends ActiveRecord{
    protected array $relations = [
        'usuario' => [ self::BELONGS_TO, Usuario::class, 'id_usuario' ],
        'usuario_con_referencia_atras' => [ self::BELONGS_TO, Usuario::class, 'id_usuario', [], 'contacto' ],
    ];
    public function __construct($conexion_base_de_datos)
    {
        parent::__construct($conexion_base_de_datos, 'contactos');
    }
}

¡Ahora tenemos las referencias configuradas para que podamos usarlas muy fácilmente!

$user = new Usuario($pdo_connection);

// encuentra el usuario más reciente
$user->notNull('id')->orderBy('id desc')->find();

// obtener contactos usando la relación:
foreach($user->contactos as $contacto) {
    echo $contacto->id;
}

// o podemos ir en la otra dirección.
$contacto = new Contacto();

// encuentra un contacto
$contacto->find();

// obtener usuario usando la relación:
echo $contacto->usuario->nombre; // este es el nombre del usuario

¡Bastante genial, ¿no?

Estableciendo Datos Personalizados

A veces es posible que necesites adjuntar algo único a tu ActiveRecord, como un cálculo personalizado que podría ser más fácil simplemente adjuntarlo al objeto y luego pasarlo a, digamos, una plantilla.

setCustomData(string $field, mixed $value)

Adjuntas los datos personalizados con el método setCustomData().

$user->setCustomData('recuento_vistas_página', $recuento_vistas_página);

Y luego simplemente lo haces referencia como una propiedad normal del objeto.

echo $usuario->recuento_vistas_página;

Eventos

Otra característica súper impresionante de esta biblioteca son los eventos. Los eventos se activan en ciertos momentos en función de ciertos métodos que llamas. Son muy, muy útiles para configurar datos automáticamente.

```markdown

Vuelo Registro Activo

Un registro activo es el mapeo de una entidad de base de datos a un objeto PHP. En pocas palabras, si tienes una tabla de usuarios en tu base de datos, puedes "traducir" una fila en esa tabla a una clase User y a un objeto $user en tu código base. Consulta ejemplo básico.

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
 * 
 * Es muy recomendable 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($conexion_base_de_datos)
    {
        // puedes configurarlo de esta manera
        parent::__construct($conexion_base_de_datos, 'users');
        // o de esta manera
        parent::__construct($conexion_base_de_datos, null, [ 'table' => 'users']);
    }
}

¡Ahora observa cómo sucede la magia!

// para sqlite
$conexion_base_de_datos = new PDO('sqlite:test.db'); // esto es solo un ejemplo, probablemente usarías una conexión de base de datos real

// para mysql
$conexion_base_de_datos = new PDO('mysql:host=localhost;dbname=test_db&charset=utf8bm4', 'usuario', 'contraseña');

// o mysqli
$conexion_base_de_datos = new mysqli('localhost', 'usuario', 'contraseña', 'test_db');
// o mysqli con creación no basada en objetos
$conexion_base_de_datos = mysqli_connect('localhost', 'usuario', 'contraseña', 'test_db');

$user = new User($conexion_base_de_datos);
$user->name = 'Bobby Tables';
$user->password = password_hash('una contraseña genial');
$user->insert();
// o $user->save();

echo $user->id; // 1

$user->name = 'Joseph Mamma';
$user->password = password_hash('¡otra contraseña genial!');
$user->insert();
// ¡no puedes usar $user->save() aquí o pensará que es una actualización!

echo $user->id; // 2

¡Y fue tan fácil agregar un nuevo usuario! Ahora que hay una fila de usuario en la base de datos, ¿cómo la extraes?

$user->find(1); // busca el id = 1 en la base de datos y devuélvelo.
echo $user->name; // 'Bobby Tables'

¿Y si quieres encontrar todos los usuarios?

$usuarios = $user->findAll();

¿Y con una condición determinada?

$usuarios = $user->like('name', '%mamma%')->findAll();

¿Ves lo divertido que es esto? ¡Instalémoslo y empecemos!

Instalación

Simplemente instálalo con Composer

composer require flightphp/active-record 

Uso

Esto se puede utilizar como una biblioteca independiente o con el Marco de PHP Flight. Completamente a tu elección.

Independiente

Solo asegúrate de pasar una conexión PDO al constructor.

$conexion_pdo = new PDO('sqlite:test.db'); // esto es solo un ejemplo, probablemente usarías una conexión de base de datos real

$Usuario = new User($conexion_pdo);

Marco PHP de Flight

Si estás utilizando el Marco PHP de Flight, puedes registrar la clase ActiveRecord como un servicio (pero honestamente no es necesario).

Flight::register('usuario', 'User', [ $conexion_pdo ]);

// luego puedes usarlo de esta manera en un controlador, una función, etc.

Flight::usuario()->find(1);

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 pasarle otros métodos auxiliares para consultar tu tabla.

// encontrar un registro con algunas condiciones previas
$user->notNull('password')->orderBy('id DESC')->find();

// encontrar un registro con 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)

Devuelve 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($conexion_pdo);
$user->name = 'demo';
$user->password = md5('demo');
$user->insert();

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, se actualizará, de lo contrario se insertará.

$user = new User($conexion_pdo);
$user->name = 'demo';
$user->password = md5('demo');
$user->save();

Nota: If you have relationships defined in the class, it will recursively save those relations as well if they have been defined, instantiated and have dirty data to update. (v0.4.0 and above)

delete(): boolean

Deletes the current record from the database.

$user->gt('id', 0)->orderBy('id desc')->find();
$user->delete();

You can also delete multiple records executing a search before hand.

$user->like('name', 'Bob%')->delete();

dirty(array $dirty = []): ActiveRecord

Dirty data refers to the data that has been changed in a record.

$user->greaterThan('id', 0)->orderBy('id desc')->find();

// nothing is "dirty" as of this point.

$user->email = 'test@example.com'; // now email is considered "dirty" since it's changed.
$user->update();
// now there is no data that is dirty because it's been updated and persisted in the database

$user->password = password_hash()'newpassword'); // now this is dirty
$user->dirty(); // passing nothing will clear all the dirty entries.
$user->update(); // nothing will update cause nothing was captured as dirty.

$user->dirty([ 'name' => 'something', 'password' => password_hash('a different password') ]);
$user->update(); // both name and password are updated.

copyFrom(array $data): ActiveRecord (v0.4.0)

This is an alias for the dirty() method. It's a little more clear what you are doing.

$user->copyFrom([ 'name' => 'something', 'password' => password_hash('a different password') ]);
$user->update(); // both name and password are updated.

isDirty(): boolean (v0.4.0)

Returns true if the current record has been changed.

$user->greaterThan('id', 0)->orderBy('id desc')->find();
$user->email = 'test@email.com';
$user->isDirty(); // true

reset(bool $include_query_data = true): ActiveRecord

Resets the current record to it's initial state. This is really good to use in loop type behaviors. If you pass true it will also reset the query data that was used to find the current object (default behavior).

$users = $user->greaterThan('id', 0)->orderBy('id desc')->find();
$user_company = new UserCompany($conexion_pdo);

foreach($users as $user) {
    $user_company->reset(); // start with a clean slate
    $user_company->user_id = $user->id;
    $user_company->company_id = $some_company_id;
    $user_company->insert();
}

getBuiltSql(): string (v0.4.1)

After you run a find(), findAll(), insert(), update(), or save() method you can get the SQL that was built and use it for debugging purposes.

SQL Query Methods

select(string $field1 [, string $field2 ... ])

You can select only a few of the columns in a table if you'd like (it is more performant on really wide tables with many columns)

$user->select('id', 'name')->find();

from(string $table)

You can technically choose another table too! Why the heck not?!

$user->select('id', 'name')->from('user')->find();

join(string $table_name, string $join_condition)

You can even join to another table in the database.

$user->join('contacts', 'contacts.user_id = users.id')->find();

where(string $where_conditions)

You can set some custom where arguments (you cannot set params in this where statement)

$user->where('id=1 AND name="demo"')->find();

Security Note - You might be tempted to do something like $user->where("id = '{$id}' AND name = '{$name}'")->find();. Please DO NOT DO THIS!!! This is susceptible to what is knows as SQL Injection attacks. There are lots of articles online, please Google "sql injection attacks php" and you'll find a lot of articles on this subject. The proper way to handle this with this library is instead of this where() method, you would do something more like $user->eq('id', $id)->eq('name', $name)->find(); If you absolutely have to do this, the PDO library has $pdo->quote($var) to escape it for you. Only after you use quote() can you use it in a where() statement.

group(string $group_by_statement)/groupBy(string $group_by_statement)

Group your results by a particular condition.

$user->select('COUNT(*) as count')->groupBy('name')->findAll();

order(string $order_by_statement)/orderBy(string $order_by_statement)

Sort the returned query a certain way.

$user->orderBy('name DESC')->find();

limit(string $limit)/limit(int $offset, int $limit)

Limit the amount of records returned. If a second int is given, it will be offset, limit just like in SQL.

$user->orderby('name DESC')->limit(0, 10)->findAll();

WHERE conditions

equal(string $field, mixed $value) / eq(string $field, mixed $value)

Where field = $value

$user->eq('id', 1)->find();

notEqual(string $field, mixed $value) / ne(string $field, mixed $value)

Where field <> $value

$user->ne('id', 1)->find();

isNull(string $field)

Where field IS NULL

$user->isNull('id')->find();

isNotNull(string $field) / notNull(string $field)

Where field IS NOT NULL

$user->isNotNull('id')->find();

greaterThan(string $field, mixed $value) / gt(string $field, mixed $value)

Where field > $value

$user->gt('id', 1)->find();

lessThan(string $field, mixed $value) / lt(string $field, mixed $value)

Where field < $value

$user->lt('id', 1)->find();

greaterThanOrEqual(string $field, mixed $value) / ge(string $field, mixed $value) / gte(string $field, mixed $value)

Where field >= $value

$user->ge('id', 1)->find();

lessThanOrEqual(string $field, mixed $value) / le(string $field, mixed $value) / lte(string $field, mixed $value)

Where field <= $value

$user->le('id', 1)->find();

like(string $field, mixed $value) / notLike(string $field, mixed $value)

Where field LIKE $value or field NOT LIKE $value

$user->like('name', 'de')->find();

in(string $field, array $values) / notIn(string $field, array $values)

Where field IN($value) or field NOT IN($value)

$user->in('id', [1, 2])->find();

between(string $field, array $values)

Where field BETWEEN $value AND $value1

$user->between('id', [1, 2])->find();

Relaciones

Puedes configurar varios tipos de relaciones utilizando esta biblioteca. Puedes establecer relaciones uno a muchos y uno a uno entre tablas. Esto requiere un poco más de configuración en la clase de antemano.

Configurar la matriz $relations no es difícil, pero adivinar la sintaxis correcta puede ser confuso.

protected array $relations = [
    // puedes nombrar la clave como quieras. Probablemente el nombre de ActiveRecord sea bueno. Ej: usuario, contacto, cliente
    'usuario' => [
        // requerido
        // self::HAS_MANY, self::HAS_ONE, self::BELONGS_TO
        self::HAS_ONE, // este es el tipo de relación

        // requerido
        'Alguna_Clase', // esta es la clase ActiveRecord "otra" a la que hará referencia esta

        // requerido
        // dependiendo del tipo de relación
        // self::HAS_ONE = la clave externa que hace referencia a la unión
        // self::HAS_MANY = la clave externa que hace referencia a la unión
        // self::BELONGS_TO = la clave local que hace referencia a la unión
        'clave_local_o_externa',
        // solo FYI, esto también se une a la clave primaria del modelo "otro"

        // 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
        'nombre_de_referencia_inversa' // esto es si quieres una referencia inversa de esta relación de nuevo a sí misma Ej: $user->contact->user;
    ];
]
class User extends ActiveRecord{
    protected array $relations = [
        'contactos' => [ self::HAS_MANY, Contacto::class, 'id_usuario' ],
        'contacto' => [ self::HAS_ONE, Contacto::class, 'id_usuario' ],
    ];

    public function __construct($conexion_base_de_datos)
    {
        parent::__construct($conexion_base_de_datos, 'usuarios');
    }
}

class Contacto extends ActiveRecord{
    protected array $relations = [
        'usuario' => [ self::BELONGS_TO, Usuario::class, 'id_usuario' ],
        'usuario_con_referencia_atras' => [ self::BELONGS_TO, Usuario::class, 'id_usuario', [], 'contacto' ],
    ];
    public function __construct($conexion_base_de_datos)
    {
        parent::__construct($conexion_base_de_datos, 'contactos');
    }
}

¡Ahora tenemos las referencias configuradas para que podamos usarlas muy fácilmente!

$user = new Usuario($conexion_pdo);

// encuentra el usuario más reciente
$user->notNull('id')->orderBy('id desc')->find();

// obtener contactos usando la relación:
foreach($user->contactos as $contacto) {
    echo $contacto->id;
}

// o podemos ir en la otra dirección.
$contacto = new Contacto();

// encuentra un contacto
$contacto->find();

// obtener usuario usando la relación:
echo $contacto->usuario->nombre; // este es el nombre del usuario

¡Bastante genial, ¿no?

Estableciendo Datos Personalizados

A veces es posible que necesites adjuntar algo único a tu ActiveRecord, como un cálculo personalizado que podría ser más fácil simplemente adjuntarlo al objeto y luego pasarlo a, digamos, una plantilla.

setCustomData(string $field, mixed $value)

Adjuntas los datos personalizados con el método setCustomData().

$user->setCustomData('recuento_vistas_página', $recuento_vistas_página);

Y luego simplemente lo haces referencia como una propiedad normal del objeto.

echo $usuario->recuento_vistas_página;

Eventos

Otra característica súper impresionante de esta biblioteca son los eventos. Los eventos se activan en ciertos momentos en función de ciertos métodos que llamas. Son muy, muy útiles para configurar datos automáticamente.

onConstruct(ActiveRecord $ActiveRecord, array &config)

Esto es realmente útil si necesitas establecer una conexión predeterminada o algo así.

// index.php or bootstrap.php
Flight::register('db', 'PDO', [ 'sqlite:test.db' ]);

//
//
//

// User.php
class User extends flight\ActiveRecord {

    protected function onConstruct(self $self, array &$config) { // don't forget the & reference
        // you could do this to automatically set the connection
        $config['connection'] = Flight::db();
        // or this
        $self->transformAndPersistConnection(Flight::db());

        ```markdown
## Contribución

Por favor, hazlo. :D

## Configuración

Cuando contribuyas, asegúrate de ejecutar `composer test-coverage` para mantener una cobertura del 100% de las pruebas (esto no es una cobertura de pruebas unitarias real, más bien pruebas de integración).

Además, asegúrate de ejecutar `composer beautify` y `composer phpcs` para corregir cualquier error de formato.

## 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

Instala con composer.

composer require latte/latte

Configuración Básica

Existen 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 característica genial de 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.
    $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 Aplicación</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 aplicación!</p>
{/block}

Entonces, cuando vayas a renderizar esto en 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 aprovechar al máximo Latte!

Awesome-plugins/awesome_plugins

Increíbles Complementos

Flight es increíblemente extensible. Hay una serie de complementos que se pueden utilizar para añadir funcionalidades a tu aplicación Flight. Algunos son compatibles oficialmente por el equipo de Flight y otros son bibliotecas micro/lite para ayudarte a empezar.

Caché

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

Cookies

Las cookies son una excelente manera de almacenar pequeños fragmentos de datos en el lado del cliente. Se pueden utilizar para almacenar preferencias de 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 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 algunas son ORM completos.

Encriptación

La encriptación es crucial para cualquier aplicación que almacene datos sensibles. Encriptar y desencriptar los datos no es muy difícil, pero almacenar correctamente la clave de encriptación puede ser difícil. Lo más importante es nunca almacenar tu clave de encriptación en un directorio público o hacer un commit a tu repositorio de código.

Sesión

Las sesiones no son realmente útiles para las API, pero para crear 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 fundamentales para cualquier aplicación web con una interfaz de usuario. Hay varias engines 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!

Examples

¿Necesitas un inicio rápido?

¡Dirígete al repositorio flightphp/skeleton para comenzar! ¡Este es un proyecto que incluye un archivo de una sola página que contiene todo lo necesario para ejecutar tu aplicación. También incluye un ejemplo más completo con controladores y vistas.

¿Necesitas Algo de Inspiración?

¡Si bien 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, por favor envía una solicitud de extracción para agregarlo a esta lista!