O Flight é um framework rápido, simples e extensível para PHP. Ele é bastante versátil e pode ser usado para construir qualquer tipo de aplicação web. Ele é construído com simplicidade em mente e é escrito de uma forma que é fácil de entender e usar.
Aqui está um pequeno artigo sobre por que você deve usar um framework. É uma boa ideia entender os benefícios de usar um framework antes de começar a usar um.
Adicionalmente, um excelente tutorial foi criado por @lubiana. Embora não entre em grandes detalhes sobre o Flight especificamente, este guia ajudará você a entender alguns dos principais conceitos envolvendo um framework e por que eles são benéficos de usar. Você pode encontrar o tutorial aqui.
Aprenda como carregar automaticamente suas próprias classes em sua aplicação.
Aprenda como gerenciar rotas para sua aplicação web. Isso também inclui agrupar rotas, parâmetros de rota e middleware.
Aprenda como usar middleware para filtrar solicitações e respostas em sua aplicação.
Aprenda como lidar com solicitações e respostas em sua aplicação.
Aprenda como enviar respostas para seus usuários.
Aprenda como usar o mecanismo de visualização integrado para renderizar seus templates HTML.
Aprenda como proteger sua aplicação contra ameaças de segurança comuns.
Aprenda como configurar o framework para sua aplicação.
Aprenda como estender o framework adicionando seus próprios métodos e classes.
Aprenda como usar o sistema de eventos para adicionar ganchos aos seus métodos e métodos internos do framework.
Aprenda como usar contêineres de injeção de dependência (DIC) para gerenciar as dependências de sua aplicação.
Saiba mais sobre os métodos principais do framework.
A compatibilidade com versões anteriores foi mantida em grande parte, mas há algumas alterações das quais você deve estar ciente ao migrar da v2 para a v3.
Há alguns problemas comuns que você pode encontrar ao usar o Flight. Esta página ajudará você a solucionar esses problemas.
Você pode parar o framework a qualquer momento chamando o método halt:
halt
Flight::halt();
Você também pode especificar um código de status HTTP opcional e uma mensagem:
HTTP
Flight::halt(200, 'Volto em breve...');
Chamar halt irá descartar qualquer conteúdo de resposta até esse ponto. Se você quiser parar o framework e exibir a resposta atual, use o método stop:
stop
Flight::stop();
Todos os erros e exceções são interceptados pelo Flight e passados para o método error. O comportamento padrão é enviar uma resposta genérica de HTTP 500 Erro Interno do Servidor com algumas informações de erro.
error
HTTP 500 Erro Interno do Servidor
Você pode substituir esse comportamento para suas próprias necessidades:
Flight::map('error', function (Throwable $error) { // Lidar com o erro echo $error->getTraceAsString(); });
Por padrão, os erros não são registrados no servidor web. Você pode habilitar isso alterando a configuração:
Flight::set('flight.log_errors', true);
Quando um URL não pode ser encontrado, o Flight chama o método notFound. O comportamento padrão é enviar uma resposta de HTTP 404 Não Encontrado com uma mensagem simples.
notFound
HTTP 404 Não Encontrado
Flight::map('notFound', function () { // Lidar com não encontrado });
A compatibilidade com versões anteriores foi em grande parte mantida, mas existem algumas alterações das quais você deve estar ciente ao migrar da v2 para a v3.
Buffer de saída é o processo em que a saída gerada por um script PHP é armazenada em um buffer (interno ao PHP) antes de ser enviada para o cliente. Isso permite que você modifique a saída antes de ser enviada para o cliente.
Em uma aplicação MVC, o Controlador é o "gerente" e ele gerencia o que a visualização faz. Ter saída gerada fora do controlador (ou, no caso do Flight, às vezes em uma função anônima) quebra o padrão MVC. Essa alteração visa estar mais em conformidade com o padrão MVC e tornar o framework mais previsível e fácil de usar.
Na v2, o buffer de saída era manipulado de forma que não fechava consistentemente seu próprio buffer de saída, o que tornava os testes unitários e o streaming mais difíceis. Para a maioria dos usuários, essa alteração pode não afetar realmente você. No entanto, se você estiver ecoando conteúdo fora de chamáveis e controladores (por exemplo, em um gancho), provavelmente terá problemas. Ecoar conteúdo em ganchos e antes do framework realmente ser executado pode ter funcionado no passado, mas não funcionará mais no futuro.
// index.php require 'vendor/autoload.php'; // apenas um exemplo define('START_TIME', microtime(true)); function hello() { echo 'Olá Mundo'; } Flight::map('hello', 'hello'); Flight::after('hello', function(){ // isso na verdade estará tudo bem echo '<p>Esta frase Olá Mundo foi trazida a você pela letra "H"</p>'; }); Flight::before('start', function(){ // coisas assim causarão um erro echo '<html><head><title>Minha Página</title></head><body>'; }); Flight::route('/', function(){ // isso na verdade está tudo bem echo 'Olá Mundo'; // Isso também deverá estar tudo bem Flight::hello(); }); Flight::after('start', function(){ // isso causará um erro echo '<div>Sua página carregou em '.(microtime(true) - START_TIME).' segundos</div></body></html>'; });
Você ainda pode manter o seu código antigo da maneira como está sem precisar reescrevê-lo para funcionar com a v3? Sim, pode! Você pode ativar o comportamento de renderização da v2 configurando a opção de configuração flight.v2.output_buffering para true. Isso permitirá que você continue a usar o comportamento de renderização antigo, mas é recomendado corrigi-lo seguindo em frente. Na v4 do framework, isso será removido.
flight.v2.output_buffering
true
// index.php require 'vendor/autoload.php'; Flight::set('flight.v2.output_buffering', true); Flight::before('start', function(){ // Agora isso estará tudo bem echo '<html><head><title>Minha Página</title></head><body>'; }); // mais código
Se você tem chamado diretamente os métodos estáticos para Dispatcher como Dispatcher::invokeMethod(), Dispatcher::execute(), etc., você precisará atualizar seu código para não chamar diretamente esses métodos. Dispatcher foi convertido para ser mais orientado a objetos para que Contêineres de Injeção de Dependência possam ser usados de maneira mais fácil. Se você precisa invocar um método de forma semelhante ao que o Dispatcher fazia, você pode usar manualmente algo como $result = $class->$method(...$params); ou call_user_func_array() em vez disso.
Dispatcher
Dispatcher::invokeMethod()
Dispatcher::execute()
$result = $class->$method(...$params);
call_user_func_array()
Você pode personalizar determinados comportamentos do Flight definindo valores de configuração através do método set.
set
A seguir está uma lista de todas as configurações disponíveis:
?string
bool
string
Content-Length
O Flight permite que você salve variáveis para que possam ser usadas em qualquer lugar da sua aplicação.
// Salve sua variável Flight::set('id', 123); // Em outro lugar na sua aplicação $id = Flight::get('id');
Para verificar se uma variável foi definida, você pode fazer:
if (Flight::has('id')) { // Faça algo }
Você pode limpar uma variável fazendo:
// Limpa a variável id Flight::clear('id'); // Limpa todas as variáveis Flight::clear();
O Flight também usa variáveis para fins de configuração.
Todos os erros e exceções são capturados pelo Flight e passados ao método error. O comportamento padrão é enviar uma resposta genérica de HTTP 500 Internal Server Error com algumas informações de erro.
HTTP 500 Internal Server Error
Você pode substituir esse comportamento conforme suas necessidades:
Quando uma URL não pode ser encontrada, o Flight chama o método notFound. O comportamento padrão é enviar uma resposta de HTTP 404 Not Found com uma mensagem simples.
HTTP 404 Not Found
Segurança é extremamente importante quando se trata de aplicações web. Você deseja garantir que sua aplicação seja segura e que os dados de seus usuários estejam protegidos. O Flight fornece uma série de recursos para ajudá-lo a proteger suas aplicações web.
Os cabeçalhos HTTP são uma das formas mais fáceis de proteger suas aplicações web. Você pode usar cabeçalhos para evitar clickjacking, XSS e outros ataques. Existem várias maneiras de adicionar esses cabeçalhos à sua aplicação.
Dois ótimos sites para verificar a segurança de seus cabeçalhos são securityheaders.com e observatory.mozilla.org.
Você pode adicionar manualmente esses cabeçalhos usando o método header no objeto Flight\Response.
header
Flight\Response
// Defina o cabeçalho X-Frame-Options para evitar clickjacking Flight::response()->header('X-Frame-Options', 'SAMEORIGIN'); // Defina o cabeçalho Content-Security-Policy para evitar XSS // Observação: esse cabeçalho pode ficar muito complexo, então você vai querer // consultar exemplos na internet para a sua aplicação Flight::response()->header("Content-Security-Policy", "default-src 'self'"); // Defina o cabeçalho X-XSS-Protection para evitar XSS Flight::response()->header('X-XSS-Protection', '1; mode=block'); // Defina o cabeçalho X-Content-Type-Options para evitar sniffing MIME Flight::response()->header('X-Content-Type-Options', 'nosniff'); // Defina o cabeçalho Referrer-Policy para controlar quanto informações de referência são enviadas Flight::response()->header('Referrer-Policy', 'no-referrer-when-downgrade'); // Defina o cabeçalho Strict-Transport-Security para forçar HTTPS Flight::response()->header('Strict-Transport-Security', 'max-age=31536000; includeSubDomains; preload'); // Defina o cabeçalho Permissions-Policy para controlar quais recursos e APIs podem ser usados Flight::response()->header('Permissions-Policy', 'geolocation=()');
Esses podem ser adicionados no início de seus arquivos bootstrap.php ou index.php.
bootstrap.php
index.php
Você também pode adicioná-los em um filtro/interceptador da seguinte maneira:
// Adicione os cabeçalhos em um 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=()'); });
Você também pode adicioná-los como uma classe de middleware. Esta é uma boa maneira de manter seu código limpo e 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 ou onde quer que tenha suas rotas // A propósito, este grupo de string vazia atua como um middleware global para // todas as rotas. Claro, você poderia fazer a mesma coisa e adicionar // isso apenas a rotas específicas. Flight::group('', function(Router $router) { $router->get('/usuarios', [ 'UserController', 'obterUsuarios' ]); // mais rotas }, [ new SecurityHeadersMiddleware() ]);
Falsificação de Solicitação entre Sites (CSRF) é um tipo de ataque em que um site malicioso pode fazer o navegador de um usuário enviar uma solicitação para o seu site. Isso pode ser usado para realizar ações no seu site sem o conhecimento do usuário. O Flight não fornece um mecanismo integrado de proteção contra CSRF, mas você pode facilmente implementar o seu próprio usando um middleware.
Primeiro, você precisa gerar um token CSRF e armazená-lo na sessão do usuário. Você pode então usar esse token em seus formulários e verificá-lo quando o formulário for enviado.
// Gerar um token CSRF e armazená-lo na sessão do usuário // (supondo que você tenha criado um objeto de sessão e o tenha anexado ao Flight) // Você só precisa gerar um único token por sessão (para funcionar // em várias guias e solicitações para o mesmo usuário) if(Flight::session()->get('csrf_token') === null) { Flight::session()->set('csrf_token', bin2hex(random_bytes(32)) ); }
<!-- Use o token CSRF em seu formulário --> <form method="post"> <input type="hidden" name="csrf_token" value="<?= Flight::session()->get('csrf_token') ?>"> <!-- outros campos do formulário --> </form>
Você também pode definir uma função personalizada para exibir o token CSRF em seus modelos Latte.
// Defina uma função personalizada para exibir o token CSRF // Observação: View foi configurado com o Latte como engine de visualização Flight::view()->addFunction('csrf', function() { $csrfToken = Flight::session()->get('csrf_token'); return new \Latte\Runtime\Html('<input type="hidden" name="csrf_token" value="' . $csrfToken . '">'); });
Agora, em seus modelos Latte, você pode usar a função csrf() para exibir o token CSRF.
csrf()
<form method="post"> {csrf()} <!-- outros campos do formulário --> </form>
Curto e simples, não é?
Você pode verificar o token CSRF usando filtros de evento:
// Este middleware verifica se a solicitação é uma solicitação POST e, se for, verifica se o token CSRF é válido Flight::before('start', function() { if(Flight::request()->method == 'POST') { // capturar o token csrf dos valores do formulário $token = Flight::request()->data->csrf_token; if($token !== Flight::session()->get('csrf_token')) { Flight::halt(403, 'Token CSRF inválido'); } } });
Ou você pode usar uma classe de 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 ou onde quer que tenha suas rotas Flight::group('', function(Router $router) { $router->get('/usuarios', [ 'UserController', 'obterUsuarios' ]); // mais rotas }, [ new CsrfMiddleware() ]);
Cross Site Scripting (XSS) é um tipo de ataque em que um site malicioso pode injetar código em seu site. A maioria dessas oportunidades vem de valores de formulários que seus usuários preencherão. Você nunca deve confiar na saída de seus usuários! Sempre assuma que todos são os melhores hackers do mundo. Eles podem injetar JavaScript ou HTML maliciosos em sua página. Esse código pode ser usado para roubar informações de seus usuários ou realizar ações em seu site. Usando a classe de visualização do Flight, você pode escapar facilmente a saída para evitar ataques XSS.
// Vamos assumir que o usuário é esperto e tenta usar isso como seu nome $nome = '<script>alert("XSS")</script>'; // Isso escapará a saída Flight::view()->set('nome', $nome); // Isso irá gerar: <script>alert("XSS")</script> // Se você usar algo como Latte registrado como sua classe de visualização, ele também escapará automaticamente isso. Flight::view()->render('modelo', ['nome' => $nome]);
Injeção de SQL é um tipo de ataque em que um usuário malicioso pode injetar código SQL em seu banco de dados. Isso pode ser usado para roubar informações de seu banco de dados ou realizar ações em seu banco de dados. Novamente, você nunca deve confiar na entrada de seus usuários! Sempre assuma que eles estão atrás de você. Você pode usar instruções preparadas em seus objetos PDO para prevenir a injeção de SQL.
PDO
// Supondo que você tenha Flight::db() registrado como seu objeto PDO $statement = Flight::db()->prepare('SELECT * FROM usuarios WHERE nome = :nome'); $statement->execute([':nome' => $nome]); $usuarios = $statement->fetchAll(); // Se você usar a classe PdoWrapper, isso pode ser facilmente feito em uma linha $usuarios = Flight::db()->fetchAll('SELECT * FROM usuarios WHERE nome = :nome', [ 'nome' => $nome ]); // Você pode fazer a mesma coisa com um objeto PDO com espaços reservados ? $statement = Flight::db()->fetchAll('SELECT * FROM usuarios WHERE nome = ?', [ $nome ]); // Apenas prometa que nunca fará algo assim... $usuarios = Flight::db()->fetchAll("SELECT * FROM usuarios WHERE nome = '{$nome}' LIMIT 5"); // porque e se $nome = "' OR 1=1; -- "; // Depois que a consulta for construída, ela ficará assim // SELECT * FROM usuarios WHERE nome = '' OR 1=1; -- LIMIT 5 // Parece estranho, mas é uma consulta válida que funcionará. Na verdade, // é um ataque de injeção de SQL muito comum que retornará todos os usuários.
Compartilhamento de Recursos de Origem Cruzada (CORS) é um mecanismo que permite que muitos recursos (por exemplo, fontes, JavaScript, etc.) em uma página da web sejam solicitados de outro domínio fora do domínio de onde o recurso se originou. O Flight não possui funcionalidades incorporadas, mas isso pode ser facilmente tratado com um gancho para ser executado antes que o método Flight::start() seja chamado.
Flight::start()
// app/utils/CorsUtil.php namespace app\utils; class CorsUtil { public function set(array $params): void { $request = Flight::request(); $response = Flight::response(); if ($request->getVar('HTTP_ORIGIN') !== '') { $this->allowOrigins(); $response->header('Access-Control-Allow-Credentials', 'true'); $response->header('Access-Control-Max-Age', '86400'); } if ($request->method === 'OPTIONS') { if ($request->getVar('HTTP_ACCESS_CONTROL_REQUEST_METHOD') !== '') { $response->header( 'Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, PATCH, OPTIONS, HEAD' ); } if ($request->getVar('HTTP_ACCESS_CONTROL_REQUEST_HEADERS') !== '') { $response->header( "Access-Control-Allow-Headers", $request->getVar('HTTP_ACCESS_CONTROL_REQUEST_HEADERS') ); } $response->status(200); $response->send(); exit; } } private function allowOrigins(): void { // personalize seus hosts permitidos aqui. $permitidos = [ 'capacitor://localhost', 'ionic://localhost', 'http://localhost', 'http://localhost:4200', 'http://localhost:8080', 'http://localhost:8100', ]; $request = Flight::request(); if (in_array($request->getVar('HTTP_ORIGIN'), $permitidos, true) === true) { $resposta = Flight::response(); $resposta->header("Access-Control-Allow-Origin", $request->getVar('HTTP_ORIGIN')); } } } // index.php ou onde quer que tenha suas rotas $CorsUtil = new CorsUtil(); Flight::before('start', [ $CorsUtil, 'setupCors' ]);
Segurança é muito importante e é fundamental garantir que suas aplicações web sejam seguras. O Flight fornece uma série de recursos para ajudá-lo a proteger suas aplicações web, mas é importante sempre estar vigilante e garantir que você esteja fazendo tudo o que pode para manter os dados de seus usuários seguros. Sempre assuma o pior e nunca confie na entrada de seus usuários. Sempre escape a saída e use instruções preparadas para evitar a injeção de SQL. Sempre use middleware para proteger suas rotas contra ataques de CSRF e CORS. Se você fizer todas essas coisas, estará bem encaminhado para construir aplicações web seguras.
O Flight permite que você substitua sua funcionalidade padrão para atender às suas próprias necessidades, sem ter que modificar nenhum código.
Por exemplo, quando o Flight não consegue corresponder a uma URL a uma rota, ele invoca o método notFound, que envia uma resposta genérica de HTTP 404. Você pode substituir esse comportamento usando o método map:
HTTP 404
map
Flight::map('notFound', function() { // Exibir página 404 personalizada include 'errors/404.html'; });
O Flight também permite que você substitua componentes principais do framework. Por exemplo, você pode substituir a classe do Roteador padrão pela sua própria classe personalizada:
// Registre sua classe personalizada Flight::register('router', MyRouter::class); // Quando o Flight carrega a instância do Roteador, ele carregará sua classe $myrouter = Flight::router();
Métodos do framework como map e register, no entanto, não podem ser substituídos. Você receberá um erro se tentar fazê-lo.
register
Nota: Quer entender mais sobre encaminhamento? Confira a página "por que um framework?" para uma explicação mais detalhada.
O encaminhamento básico no Flight é feito combinando um padrão de URL com uma função de retorno ou um array de uma classe e método.
Flight::route('/', function(){ echo 'olá mundo!'; });
As rotas são combinadas na ordem em que são definidas. A primeira rota a corresponder a uma requisição será invocada.
O callback pode ser qualquer objeto que seja invocável. Então você pode usar uma função regular:
function hello(){ echo 'olá mundo!'; } Flight::route('/', 'hello');
Você também pode usar um método estático de uma classe:
class Saudacao { public static function hello() { echo 'olá mundo!'; } } Flight::route('/', [ 'Saudacao','hello' ]);
Ou criando um objeto primeiro e depois chamando o método:
// Saudacao.php class Saudacao { public function __construct() { $this->name = 'João da Silva'; } public function hello() { echo "Olá, {$this->name}!"; } } // index.php $saudacao = new Saudacao(); Flight::route('/', [ $saudacao, 'hello' ]); // Também é possível fazer isso sem criar o objeto primeiro // Nota: Nenhum argumento será injetado no construtor Flight::route('/', [ 'Saudacao', 'hello' ]);
Se você deseja utilizar injeção de dependência via um container (PSR-11, PHP-DI, Dice, etc), o único tipo de rotas em que isso está disponível é ou criando diretamente o objeto você mesmo e usando o container para criar seu objeto ou você pode usar strings para definir a classe e método a serem chamados. Você pode ir para a página de Injeção de Dependência para mais informações.
Aqui está um exemplo rápido:
use flight\database\PdoWrapper; // Saudacao.php class Saudacao { protected PdoWrapper $pdoWrapper; public function __construct(PdoWrapper $pdoWrapper) { $this->pdoWrapper = $pdoWrapper; } public function hello(int $id) { // faça algo com $this->pdoWrapper $name = $this->pdoWrapper->fetchField("SELECT name FROM users WHERE id = ?", [ $id ]); echo "Olá, mundo! Meu nome é {$name}!"; } } // index.php // Configure o container com os parâmetros necessários // Veja a página de Injeção de Dependência para mais informações sobre PSR-11 $dice = new \Dice\Dice(); // Não se esqueça de reatribuir a variável com '$dice = '!!!!! $dice = $dice->addRule('flight\database\PdoWrapper', [ 'shared' => true, 'constructParams' => [ 'mysql:host=localhost;dbname=test', 'root', 'password' ] ]); // Registre o manipulador do container Flight::registerContainerHandler(function($class, $params) use ($dice) { return $dice->create($class, $params); }); // Rotas como de costume Flight::route('/hello/@id', [ 'Saudacao', 'hello' ]); // ou Flight::route('/hello/@id', 'Saudacao->hello'); // ou Flight::route('/hello/@id', 'Saudacao::hello'); Flight::start();
O Flight permite que você salve variáveis para que possam ser usadas em qualquer lugar de sua aplicação.
// Salve sua variável Flight::set('id', 123); // Em outro lugar de sua aplicação $id = Flight::get('id');
O Contentor de Injeção de Dependência (CID) é uma ferramenta poderosa que permite gerenciar as dependências de sua aplicação. É um conceito-chave nos frameworks PHP modernos e é usado para gerenciar a instanciação e configuração de objetos. Alguns exemplos de bibliotecas CID são: Dice, Pimple, PHP-DI e league/container.
Um CID é uma forma sofisticada de dizer que permite criar e gerenciar suas classes em um local centralizado. Isso é útil quando você precisa passar o mesmo objeto para múltiplas classes (como seus controladores). Um exemplo simples pode ajudar a tornar isso mais claro.
A maneira antiga de fazer as coisas poderia parecer assim:
require 'vendor/autoload.php'; // classe para gerenciar usuários no banco de dados class UserController { protected PDO $pdo; public function __construct(PDO $pdo) { $this->pdo = $pdo; } public function view(int $id) { $stmt = $this->pdo->prepare('SELECT * FROM users WHERE id = :id'); $stmt->execute(['id' => $id]); print_r($stmt->fetch()); } } $User = new UserController(new PDO('mysql:host=localhost;dbname=test', 'user', 'pass')); Flight::route('/user/@id', [ $UserController, 'view' ]); Flight::start();
Você pode ver a partir do código acima que estamos criando um novo objeto PDO e passando-o para nossa classe UserController. Isso é bom para uma aplicação pequena, mas à medida que a aplicação cresce, você descobrirá que está criando o mesmo objeto PDO em múltiplos lugares. É aqui que um CID se torna útil.
UserController
Aqui está o mesmo exemplo usando um CID (usando Dice):
require 'vendor/autoload.php'; // mesma classe que acima. Nada mudou 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()); } } // criar um novo contentor $container = new \Dice\Dice; // não se esqueça de reatribuí-lo a si mesmo como abaixo! $container = $container->addRule('PDO', [ // shared significa que o mesmo objeto será retornado toda vez 'shared' => true, 'constructParams' => ['mysql:host=localhost;dbname=test', 'user', 'pass' ] ]); // Isso registra o manipulador do contentor para que o Flight saiba usá-lo. Flight::registerContainerHandler(function($class, $params) use ($container) { return $container->create($class, $params); }); // agora podemos usar o contentor para criar nosso UserController Flight::route('/user/@id', [ 'UserController', 'view' ]); // ou alternativamente você pode definir a rota assim Flight::route('/user/@id', 'UserController->view'); // ou Flight::route('/user/@id', 'UserController::view'); Flight::start();
Aposto que você pode estar pensando que houve muito código extra adicionado ao exemplo. A magia acontece quando você tem outro controlador que precisa do objeto PDO.
// Se todos os seus controladores têm um construtor que precisa de um objeto PDO // cada uma das rotas abaixo terá automaticamente ele injetado!!! Flight::route('/empresa/@id', 'CompanyController->view'); Flight::route('/organizacao/@id', 'OrganizationController->view'); Flight::route('/categoria/@id', 'CategoryController->view'); Flight::route('/configuracoes', 'SettingsController->view');
O benefício adicional de utilizar um CID é que os testes unitários se tornam muito mais fáceis. Você pode criar um objeto falso e passá-lo para sua classe. Isso é um grande benefício ao escrever testes para sua aplicação!
O Flight também pode usar qualquer contentor compatível com o PSR-11. Isso significa que você pode usar qualquer contentor que implemente a interface PSR-11. Aqui está um exemplo usando o contentor PSR-11 da League:
require 'vendor/autoload.php'; // mesma classe UserController que acima $container = new \League\Container\Container(); $container->add(UserController::class)->addArgument(PdoWrapper::class); $container->add(PdoWrapper::class) ->addArgument('mysql:host=localhost;dbname=test') ->addArgument('user') ->addArgument('pass'); Flight::registerContainerHandler($container); Flight::route('/user', [ 'UserController', 'view' ]); Flight::start();
Embora isso possa ser um pouco mais verboso do que o exemplo anterior com Dice, ainda faz o trabalho com os mesmos benefícios!
Você também pode criar seu próprio manipulador CID. Isso é útil se você tiver um contentor personalizado que deseja usar que não seja PSR-11 (Dice). Veja o exemplo básico de como fazer isso.
Além disso, existem alguns padrões úteis que facilitarão sua vida ao usar o Flight.
Se você estiver usando a instância Engine em seus controladores/funções intermediárias, aqui está como você configuraria:
Engine
// Em algum lugar do seu arquivo de inicialização $engine = Flight::app(); $container = new \Dice\Dice; $container = $container->addRule('*', [ 'substitutions' => [ // Aqui é onde você passa a instância Engine::class => $engine ] ]); $engine->registerContainerHandler(function($class, $params) use ($container) { return $container->create($class, $params); }); // Agora você pode usar a instância do Engine em seus controladores/funções intermediárias class MeuControlador { public function __construct(Engine $app) { $this->app = $app; } public function index() { $this->app->render('index'); } }
Se você tem outras classes que deseja adicionar ao contentor, com Dice é fácil, pois elas serão automaticamente resolvidas pelo contentor. Aqui está um exemplo:
$container = new \Dice\Dice; // Se você não precisa injetar nada em sua classe // você não precisa definir nada! Flight::registerContainerHandler(function($class, $params) use ($container) { return $container->create($class, $params); }); class MinhaClassePersonalizada { public function parseCoisa() { return 'coisa'; } } class UserController { protected MyCustomClass $MyCustomClass; public function __construct(MyCustomClass $MyCustomClass) { $this->MyCustomClass = $MyCustomClass; } public function index() { echo $this->MyCustomClass->parseThing(); } } Flight::route('/user', 'UserController->index');
O Flight suporta middleware de rota e de grupo de rotas. O middleware é uma função que é executada antes (ou depois) da chamada de rota. Esta é uma ótima maneira de adicionar verificações de autenticação da API em seu código, ou para validar se o usuário tem permissão para acessar a rota.
Aqui está um exemplo básico:
// Se você fornecer apenas uma função anônima, ela será executada antes da chamada de rota. // não existem funções de middleware "depois" exceto para classes (veja abaixo) Flight::route('/caminho', function() { echo ' Aqui estou!'; })->addMiddleware(function() { echo 'Middleware primeiro!'; }); Flight::start(); // Isso irá exibir "Middleware primeiro! Aqui estou!"
Existem algumas notas muito importantes sobre middleware que você deve estar ciente antes de usá-los:
Flight::redirect()
function($params) { ... }
public function before($params) {}
flight\Engine
__construct()
O middleware também pode ser registrado como uma classe. Se precisar da funcionalidade "depois", você deve usar uma classe.
class MeuMiddleware { public function before($params) { echo 'Middleware primeiro!'; } public function after($params) { echo 'Middleware último!'; } } $MeuMiddleware = new MeuMiddleware(); Flight::route('/caminho', function() { echo ' Aqui estou! '; })->addMiddleware($MeuMiddleware); // também ->addMiddleware([ $MeuMiddleware, $MeuMiddleware2 ]); Flight::start(); // Isso exibirá "Middleware primeiro! Aqui estou! Middleware último!"
Digamos que você tenha um middleware de autenticação e deseje redirecionar o usuário para uma página de login se ele não estiver autenticado. Você tem algumas opções à sua disposição:
Aqui está um exemplo simples de retorno de false:
class MeuMiddleware { public function before($params) { if (isset($_SESSION['user']) === false) { return false; } // como é true, tudo continua } }
Aqui está um exemplo de redirecionamento do usuário para uma página de login:
class MeuMiddleware { public function before($params) { if (isset($_SESSION['user']) === false) { Flight::redirect('/login'); exit; } } }
Digamos que você precise retornar um erro JSON porque está construindo uma API. Você pode fazer isso assim:
class MeuMiddleware { public function before($params) { $autorizacao = Flight::request()->headers['Authorization']; if(empty($autorizacao)) { Flight::json(['erro' => 'Você precisa estar logado para acessar esta página.'], 403); exit; // ou Flight::halt(403, json_encode(['error' => 'Você precisa estar logado para acessar esta página.']); } } }
Você pode adicionar um grupo de rota e, em seguida, cada rota nesse grupo terá o mesmo middleware também. Isso é útil se você precisar agrupar um monte de rotas, por exemplo, por um middleware de autenticação para verificar a chave da API no cabeçalho.
// adicionado ao final do método do grupo Flight::group('/api', function() { // Esta rota com aparência "vazia" corresponderá na verdade a /api Flight::route('', function() { echo 'api'; }, false, 'api'); Flight::route('/usuarios', function() { echo 'usuários'; }, false, 'usuários'); Flight::route('/usuarios/@id', function($id) { echo 'usuário:'.$id; }, false, 'visualizar_usuario'); }, [ new ApiAuthMiddleware() ]);
Se você deseja aplicar um middleware global a todas as suas rotas, você pode adicionar um grupo "vazio":
// adicionado ao final do método do grupo Flight::group('', function() { Flight::route('/usuarios', function() { echo 'usuários'; }, false, 'usuários'); Flight::route('/usuarios/@id', function($id) { echo 'usuário:'.$id; }, false, 'visualizar_usuario'); }, [ new ApiAuthMiddleware() ]);
O Flight permite que você filtre métodos antes e depois de serem chamados. Não há ganchos predefinidos que você precise memorizar. Você pode filtrar qualquer um dos métodos padrão do framework, bem como quaisquer métodos personalizados que você tenha mapeado.
Uma função de filtro parece com isso:
function (array &$params, string &$output): bool { // Código do filtro }
Usando as variáveis passadas, você pode manipular os parâmetros de entrada e/ou a saída.
Você pode ter um filtro sendo executado antes de um método fazendo:
Flight::before('start', function (array &$params, string &$output): bool { // Faça algo });
Você pode ter um filtro sendo executado após um método fazendo:
Flight::after('start', function (array &$params, string &$output): bool { // Faça algo });
Você pode adicionar quantos filtros quiser a qualquer método. Eles serão chamados na ordem em que são declarados.
Aqui está um exemplo do processo de filtragem:
// Mapeie um método personalizado Flight::map('hello', function (string $name) { return "Olá, $name!"; }); // Adicione um filtro antes Flight::before('hello', function (array &$params, string &$output): bool { // Manipule o parâmetro $params[0] = 'Fred'; return true; }); // Adicione um filtro após Flight::after('hello', function (array &$params, string &$output): bool { // Manipule a saída $output .= " Tenha um bom dia!"; return true; }); // Invoque o método personalizado echo Flight::hello('Bob');
Isso deve exibir:
Olá Fred! Tenha um bom dia!
Se você definiu vários filtros, você pode interromper a cadeia retornando false em qualquer uma de suas funções de filtro:
false
Flight::before('start', function (array &$params, string &$output): bool { echo 'um'; return true; }); Flight::before('start', function (array &$params, string &$output): bool { echo 'dois'; // Isso irá encerrar a cadeia return false; }); // Isso não será chamado Flight::before('start', function (array &$params, string &$output): bool { echo 'três'; return true; });
Observação, métodos principais como map e register não podem ser filtrados porque são chamados diretamente e não invocados dinamicamente.
O Flight encapsula o pedido HTTP em um único objeto, que pode ser acessado através de:
$request = Flight::request();
O objeto de pedido fornece as seguintes propriedades:
Você pode acessar as propriedades query, data, cookies e files como arrays ou objetos.
query
data
cookies
files
Portanto, para obter um parâmetro da string de consulta, você pode fazer:
$id = Flight::request()->query['id'];
Ou você pode fazer:
$id = Flight::request()->query->id;
Para obter o corpo bruto do pedido HTTP, por exemplo, ao lidar com pedidos PUT, você pode fazer:
$body = Flight::request()->getBody();
Se você enviar um pedido com o tipo application/json e os dados {"id": 123}, eles estarão disponíveis na propriedade data:
application/json
{"id": 123}
$id = Flight::request()->data->id;
$_SERVER
Há um atalho disponível para acessar a matriz $_SERVER através do método getVar():
getVar()
$host = Flight::request()->getVar['HTTP_HOST'];
Você pode acessar os cabeçalhos do pedido usando o método getHeader() ou getHeaders():
getHeader()
getHeaders()
// Talvez você precise do cabeçalho de Autorização $host = Flight::request()->getHeader('Authorization'); // Se precisar pegar todos os cabeçalhos $headers = Flight::request()->getHeaders();
# Métodos do Framework Flight é projetado para ser fácil de usar e entender. O seguinte é o conjunto completo de métodos para o framework. Consiste em métodos principais, que são métodos estáticos regulares, e métodos extensíveis, que são métodos mapeados que podem ser filtrados ou substituídos. ## Métodos Principais ```php Flight::map(string $name, callable $callback, bool $pass_route = false) // Cria um método de framework personalizado. Flight::register(string $name, string $class, array $params = [], ?callable $callback = null) // Registra uma classe a um método de framework. Flight::before(string $name, callable $callback) // Adiciona um filtro antes de um método de framework. Flight::after(string $name, callable $callback) // Adiciona um filtro após um método de framework. Flight::path(string $path) // Adiciona um caminho para carregar classes automaticamente. Flight::get(string $key) // Obtém uma variável. Flight::set(string $key, mixed $value) // Define uma variável. Flight::has(string $key) // Verifica se uma variável está definida. Flight::clear(array|string $key = []) // Limpa uma variável. Flight::init() // Inicializa o framework com suas configurações padrão. Flight::app() // Obtém a instância do objeto de aplicativo
Flight::start() // Inicia o framework. Flight::stop() // Para o framework e envia uma resposta. Flight::halt(int $code = 200, string $message = '') // Para o framework com um código de status e mensagem opcionais. Flight::route(string $pattern, callable $callback, bool $pass_route = false) // Mapeia um padrão de URL para um retorno de chamada. Flight::group(string $pattern, callable $callback) // Cria agrupamento para urls, o padrão deve ser uma string. Flight::redirect(string $url, int $code) // Redireciona para outra URL. Flight::render(string $file, array $data, ?string $key = null) // Renderiza um arquivo de modelo. Flight::error(Throwable $error) // Envia uma resposta HTTP 500. Flight::notFound() // Envia uma resposta HTTP 404. Flight::etag(string $id, string $type = 'string') // Realiza o cache do HTTP ETag. Flight::lastModified(int $time) // Realiza o cache do HTTP de última modificação. Flight::json(mixed $data, int $code = 200, bool $encode = true, string $charset = 'utf8', int $option) // Envia uma resposta JSON. Flight::jsonp(mixed $data, string $param = 'jsonp', int $code = 200, bool $encode = true, string $charset = 'utf8', int $option) // Envia uma resposta JSONP.
Quaisquer métodos personalizados adicionados com map e register também podem ser filtrados.
# Métodos da API do Framework O Flight é projetado para ser fácil de usar e entender. O seguinte é o conjunto completo de métodos para o framework. Ele consiste em métodos principais, que são métodos estáticos regulares, e métodos extensíveis, que são métodos mapeados que podem ser filtrados ou substituídos. ## Métodos Principais Esses métodos são essenciais para o framework e não podem ser substituídos. ```php Flight::map(string $name, callable $callback, bool $pass_route = false) // Cria um método personalizado no framework. Flight::register(string $name, string $class, array $params = [], ?callable $callback = null) // Registra uma classe em um método do framework. Flight::unregister(string $name) // Cancela o registro de uma classe em um método do framework. Flight::before(string $name, callable $callback) // Adiciona um filtro antes de um método do framework. Flight::after(string $name, callable $callback) // Adiciona um filtro depois de um método do framework. Flight::path(string $path) // Adiciona um caminho para carregar automaticamente classes. Flight::get(string $key) // Obtém uma variável. Flight::set(string $key, mixed $value) // Define uma variável. Flight::has(string $key) // Verifica se uma variável está definida. Flight::clear(array|string $key = []) // Limpa uma variável. Flight::init() // Inicializa o framework com suas configurações padrão. Flight::app() // Obtém a instância do objeto de aplicação Flight::request() // Obtém a instância do objeto de solicitação Flight::response() // Obtém a instância do objeto de resposta Flight::router() // Obtém a instância do objeto de roteador Flight::view() // Obtém a instância do objeto de visualização
Flight::start() // Inicia o framework. Flight::stop() // Interrompe o framework e envia uma resposta. Flight::halt(int $code = 200, string $message = '') // Interrompe o framework com um código de status e mensagem opcional. Flight::route(string $pattern, callable $callback, bool $pass_route = false, string $alias = '') // Mapeia um padrão de URL para uma chamada de retorno. Flight::post(string $pattern, callable $callback, bool $pass_route = false, string $alias = '') // Mapeia um padrão de URL de solicitação POST para uma chamada de retorno. Flight::put(string $pattern, callable $callback, bool $pass_route = false, string $alias = '') // Mapeia um padrão de URL de solicitação PUT para uma chamada de retorno. Flight::patch(string $pattern, callable $callback, bool $pass_route = false, string $alias = '') // Mapeia um padrão de URL de solicitação PATCH para uma chamada de retorno. Flight::delete(string $pattern, callable $callback, bool $pass_route = false, string $alias = '') // Mapeia um padrão de URL de solicitação DELETE para uma chamada de retorno. Flight::group(string $pattern, callable $callback) // Cria agrupamento para URLs, o padrão deve ser uma string. Flight::getUrl(string $name, array $params = []) // Gera uma URL com base em um alias de rota. Flight::redirect(string $url, int $code) // Redireciona para outra URL. Flight::render(string $file, array $data, ?string $key = null) // Renderiza um arquivo de modelo. Flight::error(Throwable $error) // Envia uma resposta HTTP 500. Flight::notFound() // Envia uma resposta HTTP 404. Flight::etag(string $id, string $type = 'string') // Executa o cacheamento HTTP ETag. Flight::lastModified(int $time) // Executa o cacheamento HTTP modificado por último. Flight::json(mixed $data, int $code = 200, bool $encode = true, string $charset = 'utf8', int $option) // Envia uma resposta JSON. Flight::jsonp(mixed $data, string $param = 'jsonp', int $code = 200, bool $encode = true, string $charset = 'utf8', int $option) // Envia uma resposta JSONP.
Alguns programadores são veementemente contra o uso de frameworks. Eles argumentam que os frameworks são inchados, lentos e difíceis de aprender. Eles dizem que os frameworks são desnecessários e que você pode escrever um código melhor sem eles. Certamente, existem alguns pontos válidos a serem considerados sobre as desvantagens do uso de frameworks. No entanto, também existem muitas vantagens em usar frameworks.
Aqui estão algumas razões pelas quais você pode querer considerar o uso de um framework:
Flight é um micro-framework. Isso significa que é pequeno e leve. Ele não fornece tanta funcionalidade quanto os frameworks maiores como Laravel ou Symfony. No entanto, ele fornece muita funcionalidade necessária para construir aplicações web. Também é fácil de aprender e usar. Isso o torna uma boa escolha para construir aplicações web de forma rápida e fácil. Se você é novo em frameworks, Flight é um ótimo framework para iniciantes para começar. Isso ajudará você a aprender sobre as vantagens de usar frameworks sem sobrecarregá-lo com muita complexidade. Depois de ter alguma experiência com o Flight, será mais fácil passar para frameworks mais complexos como Laravel ou Symfony, no entanto, o Flight ainda pode criar uma aplicação robusta e bem-sucedida.
O roteamento é o núcleo do framework Flight, mas o que é exatamente? Roteamento é o processo de pegar uma URL e combiná-la com uma função específica em seu código. É assim que você pode fazer seu site fazer coisas diferentes com base na URL solicitada. Por exemplo, você pode querer mostrar o perfil de um usuário quando eles visitam /user/1234, mas mostrar uma lista de todos os usuários quando eles visitam /users. Tudo isso é feito através do roteamento.
/user/1234
/users
Pode funcionar assim:
http://exemplo.com/user/1234
Flight::route('/user/@id', [ 'UserController', 'viewUserProfile' ]);
viewUserProfile($id)
1234
$id
viewUserProfile()
Ter um roteador centralizado apropriado pode realmente tornar sua vida dramaticamente mais fácil! Pode ser difícil de ver no início. Aqui estão algumas razões pelas quais:
user_view
id
/admin/user/1234
Tenho certeza de que você está familiarizado com o método de criar um site script por script. Você pode ter um arquivo chamado index.php que possui um monte de declarações if para verificar a URL e executar uma função específica com base na URL. Isso é uma forma de roteamento, mas não é muito organizado e pode ficar fora de controle rapidamente. O sistema de roteamento do Flight é uma forma muito mais organizada e poderosa de lidar com o roteamento.
if
Isso?
// /user/view_profile.php?id=1234 if ($_GET['id']) { $id = $_GET['id']; viewUserProfile($id); } // /user/edit_profile.php?id=1234 if ($_GET['id']) { $id = $_GET['id']; editUserProfile($id); } // etc...
Ou isso?
// index.php Flight::route('/user/@id', [ 'UserController', 'viewUserProfile' ]); Flight::route('/user/@id/edit', [ 'UserController', 'editUserProfile' ]); // Em talvez seu app/controllers/UserController.php class UserController { public function viewUserProfile($id) { // faça algo } public function editUserProfile($id) { // faça algo } }
Com sorte, você pode começar a ver os benefícios de usar um sistema de roteamento centralizado. É muito mais fácil de gerenciar e entender a longo prazo!
O Flight fornece uma maneira simples e fácil de lidar com solicitações e respostas. Este é o núcleo do que um framework web faz. Ele recebe uma solicitação do navegador de um usuário, a processa e envia de volta uma resposta. É assim que você pode construir aplicações web que fazem coisas como mostrar o perfil de um usuário, permitir que um usuário faça login ou permitir que um usuário poste um novo post em um blog.
Uma solicitação é o que o navegador do usuário envia para o seu servidor quando eles visitam seu site. Esta solicitação contém informações sobre o que o usuário deseja fazer. Por exemplo, pode conter informações sobre qual URL o usuário deseja visitar, que dados o usuário deseja enviar para o seu servidor ou que tipo de dados o usuário deseja receber do seu servidor. É importante saber que uma solicitação é somente leitura. Você não pode alterar a solicitação, mas pode ler dela.
O Flight fornece uma maneira simples de acessar informações sobre a solicitação. Você pode acessar informações sobre a solicitação usando o método Flight::request(). Este método retorna um objeto Request que contém informações sobre a solicitação. Você pode usar este objeto para acessar informações sobre a solicitação, como a URL, o método ou os dados que o usuário enviou para o seu servidor.
Flight::request()
Request
Uma resposta é o que seu servidor envia de volta para o navegador do usuário quando eles visitam seu site. Esta resposta contém informações sobre o que seu servidor deseja fazer. Por exemplo, pode conter informações sobre que tipo de dados seu servidor deseja enviar para o usuário, que tipo de dados seu servidor deseja receber do usuário, ou que tipo de dados seu servidor deseja armazenar no computador do usuário.
O Flight fornece uma maneira simples de enviar uma resposta para o navegador do usuário. Você pode enviar uma resposta usando o método Flight::response(). Este método recebe um objeto Response como argumento e envia a resposta para o navegador do usuário. Você pode usar este objeto para enviar uma resposta para o navegador do usuário, como HTML, JSON ou um arquivo. O Flight ajuda a gerar automaticamente algumas partes da resposta para facilitar as coisas, mas, em última instância, você tem controle sobre o que envia de volta para o usuário.
Flight::response()
Response
Voo fornece suporte embutido para o caching de nível HTTP. Se a condição de caching for atendida, Voo retornará uma resposta 304 Not Modified HTTP. Da próxima vez que o cliente solicitar o mesmo recurso, eles serão convidados a usar sua versão em cache localmente.
304 Not Modified
Você pode usar o método lastModified e passar um carimbo de data/hora UNIX para definir a data e hora em que a página foi modificada pela última vez. O cliente continuará a usar seu cache até que o valor da última modificação seja alterado.
lastModified
Flight::route('/noticias', function () { Flight::lastModified(1234567890); echo 'Este conteúdo será armazenado em cache.'; });
O caching ETag é semelhante ao Última Modificação, exceto que você pode especificar qualquer ID que desejar para o recurso:
ETag
Última Modificação
Flight::route('/noticias', function () { Flight::etag('meu-id-único'); echo 'Este conteúdo será armazenado em cache.'; });
Tenha em mente que chamar lastModified ou etag definirá e verificará o valor do cache. Se o valor do cache for o mesmo entre as solicitações, Voo enviará imediatamente uma resposta HTTP 304 e interromperá o processamento.
etag
HTTP 304
O Flight ajuda a gerar parte dos cabeçalhos de respostas para você, mas você controla a maior parte do que envia de volta para o usuário. Às vezes você pode acessar o objeto Response diretamente, mas na maioria das vezes você usará a instância do Flight para enviar uma resposta.
Flight
O Flight usa ob_start() para armazenar em buffer a saída. Isso significa que você pode usar echo ou print para enviar uma resposta para o usuário e o Flight irá capturá-la e enviá-la de volta para o usuário com os cabeçalhos apropriados.
echo
print
// Isso enviará "Olá, Mundo!" para o navegador do usuário Flight::route('/', function() { echo "Olá, Mundo!"; }); // HTTP/1.1 200 OK // Tipo de Conteúdo: text/html // // Olá, Mundo!
Como alternativa, você pode chamar o método write() para adicionar ao corpo também.
write()
// Isso enviará "Olá, Mundo!" para o navegador do usuário Flight::route('/', function() { // detalhado, mas funciona às vezes quando você precisa Flight::response()->write("Olá, Mundo!"); // se você quiser recuperar o corpo que definiu até este ponto // você pode fazer isso assim $corpo = Flight::response()->getBody(); });
Você pode definir o código de status da resposta usando o método status:
status
Flight::route('/@id', function($id) { if($id == 123) { Flight::response()->status(200); echo "Olá, Mundo!"; } else { Flight::response()->status(403); echo "Proibido"; } });
Se quiser obter o código de status atual, você pode usar o método status sem argumentos:
Flight::response()->status(); // 200
Você pode executar um callback no corpo da resposta usando o método addResponseBodyCallback:
addResponseBodyCallback
Flight::route('/usuarios', function() { $bd = Flight::db(); $usuarios = $bd->fetchAll("SELECT * FROM usuarios"); Flight::render('tabela_usuarios', ['usuarios' => $usuarios]); }); // Isso irá compactar todas as respostas para qualquer rota Flight::response()->addResponseBodyCallback(function($corpo) { return gzencode($corpo, 9); });
Você pode adicionar vários callbacks e eles serão executados na ordem em que foram adicionados. Como isso pode aceitar qualquer callable, pode aceitar um array de classe [ $classe, 'método' ], um closure $strReplace = function($corpo) { str_replace('olá', 'aí', $corpo); };, ou um nome de função 'minificar' se você tivesse uma função para minificar seu código html, por exemplo.
[ $classe, 'método' ]
$strReplace = function($corpo) { str_replace('olá', 'aí', $corpo); };
'minificar'
Nota: Callbacks de rota não funcionarão se você estiver usando a opção de configuração flight.v2.output_buffering.
Se você quiser que isso se aplique apenas a uma rota específica, você pode adicionar o callback na rota em si:
Flight::route('/usuarios', function() { $bd = Flight::db(); $usuarios = $bd->fetchAll("SELECT * FROM usuarios"); Flight::render('tabela_usuarios', ['usuarios' => $usuarios]); // Isso irá compactar apenas a resposta para esta rota Flight::response()->addResponseBodyCallback(function($corpo) { return gzencode($corpo, 9); }); });
Você também pode usar middleware para aplicar o callback a todas as rotas via middleware:
// MinifyMiddleware.php class MinifyMiddleware { public function before() { Flight::response()->addResponseBodyCallback(function($corpo) { // Isto é um return $this->minify($corpo); }); } protected function minify(string $corpo): string { // minificar o corpo return $corpo; } } // index.php Flight::group('/usuarios', function() { Flight::route('', function() { /* ... */ }); Flight::route('/@id', function($id) { /* ... */ }); }, [ new MinifyMiddleware() ]);
Você pode definir um cabeçalho como o tipo de conteúdo da resposta usando o método header:
// Isso enviará "Olá, Mundo!" para o navegador do usuário em texto simples Flight::route('/', function() { Flight::response()->header('Content-Type', 'text/plain'); echo "Olá, Mundo!"; });
O Flight fornece suporte para envio de respostas JSON e JSONP. Para enviar uma resposta JSON você passa alguns dados para serem codificados em JSON:
Flight::json(['id' => 123]);
Você também pode passar um código de status como segundo argumento:
Flight::json(['id' => 123], 201);
Você também pode passar um argumento para a última posição para habilitar a impressão simples:
Flight::json(['id' => 123], 200, true, 'utf-8', JSON_PRETTY_PRINT);
Se você estiver alterando as opções passadas para Flight::json() e quiser uma sintaxe mais simples, você pode apenas remapear o método JSON:
Flight::json()
Flight::map('json', function($dados, $codigo = 200, $opções = 0) { Flight::_json($dados, $codigo, true, 'utf-8', $opções); } // E agora pode ser usado assim Flight::json(['id' => 123], 200, JSON_PRETTY_PRINT);
Se quiser enviar uma resposta JSON e interromper a execução, você pode usar o método jsonHalt. Isso é útil para casos em que você está verificando talvez algum tipo de autorização e se o usuário não estiver autorizado, você pode enviar imediatamente uma resposta JSON, limpar o conteúdo do corpo existente e interromper a execução.
jsonHalt
Flight::route('/usuarios', function() { $autorizado = algumaVerificaçãoDeAutorização(); // Verifique se o usuário está autorizado if($autorizado === false) { Flight::jsonHalt(['erro' => 'Não autorizado'], 401); } // Continue com o restante da rota });
Para solicitações JSONP, você pode opcionalmente passar o nome do parâmetro de consulta que você está usando para definir sua função de retorno de chamada:
Flight::jsonp(['id' => 123], 'q');
Assim, ao fazer uma solicitação GET usando ?q=MinhaFunc, você deverá receber a saída:
?q=MinhaFunc
MinhaFunc({"id":123});
Se você não passar um nome de parâmetro de consulta, ele será padrão para jsonp.
jsonp
Você pode redirecionar a solicitação atual usando o método redirect() e passando em uma nova URL:
redirect()
Flight::redirect('/nova/localizacao');
Por padrão, o Flight envia um código de status HTTP 303 ("Veja Outro"). Você pode opcionalmente definir um código personalizado:
Flight::redirect('/nova/localizacao', 401);
Você pode parar o framework em qualquer ponto chamando o método halt:
Flight::halt(200, 'Voltamos em breve...');
Chamar halt descartará qualquer conteúdo de resposta até aquele ponto. Se você quiser parar o framework e exibir a resposta atual, use o método stop:
O Flight fornece suporte embutido para o cache de nível HTTP. Se a condição de cache for atendida, o Flight retornará uma resposta HTTP 304 Não Modificado. A próxima vez que o cliente solicitar o mesmo recurso, ele será solicitado a usar sua versão em cache localmente.
304 Não Modificado
Se você deseja armazenar em cache toda a sua resposta, você pode usar o método cache() e passar o tempo de cache.
cache()
// Isso armazenará em cache a resposta por 5 minutos Flight::route('/noticias', function () { Flight::response()->cache(time() + 300); echo 'Este conteúdo será armazenado em cache.'; }); // Alternativamente, você pode usar uma string que passaria // para o método strtotime() Flight::route('/noticias', function () { Flight::response()->cache('+5 minutos'); echo 'Este conteúdo será armazenado em cache.'; });
Você pode usar o método lastModified e passar um carimbo de data UNIX para definir a data e hora em que uma página foi modificada pela última vez. O cliente continuará a usar seu cache até o valor da última modificação ser alterado.
O cache ETag é semelhante ao Modificado pela última vez, exceto que você pode especificar qualquer ID desejado para o recurso:
Modificado pela última vez
Lembre-se que chamar lastModified ou etag definirá e verificará o valor de cache. Se o valor da cache for o mesmo entre as solicitações, o Flight enviará imediatamente uma resposta HTTP 304 e interromperá o processamento.
Em vez de executar o Flight como uma classe estática global, você pode executá-lo opcionalmente como uma instância de objeto.
require 'flight/autoload.php'; $app = Flight::app(); $app->route('/', function () { echo 'olá mundo!'; }); $app->start();
Portanto, em vez de chamar o método estático, você chamaria o método de instância com o mesmo nome no objeto Engine.
Você pode redirecionar a solicitação atual usando o método redirect e passando uma nova URL:
redirect
Flight::redirect('/novo/local');
Por padrão, o Flight envia um código de status HTTP 303. Você pode opcionalmente definir um código personalizado:
Flight::redirect('/novo/local', 401);
Flight fornece alguma funcionalidade básica de modelagem por padrão. Para exibir uma vista chame o método render com o nome do arquivo de modelo e dados de modelo opcionais:
render
Flight::render('hello.php', ['name' => 'Bob']);
Os dados do modelo que você passa são automaticamente injetados no modelo e podem ser referenciados como uma variável local. Os arquivos de modelo são simplesmente arquivos PHP. Se o conteúdo do arquivo de modelo hello.php for:
hello.php
Olá, <?= $name ?>!
A saída seria:
Olá, Bob!
Você também pode definir manualmente variáveis de visualização usando o método set:
Flight::view()->set('name', 'Bob');
A variável name agora está disponível em todas as suas visualizações. Então você pode simplesmente fazer:
name
Flight::render('hello');
Observe que ao especificar o nome do modelo no método render, você pode omitir a extensão .php.
.php
Por padrão, o Flight procurará um diretório views para arquivos de modelo. Você pode definir um caminho alternativo para seus modelos configurando o seguinte:
views
Flight::set('flight.views.path', '/caminho/para/views');
É comum que sites tenham um único arquivo de modelo de layout com conteúdo alternante. Para renderizar conteúdo a ser usado em um layout, você pode passar um parâmetro opcional para o método render.
Flight::render('header', ['heading' => 'Olá'], 'headerContent'); Flight::render('body', ['body' => 'Mundo'], 'bodyContent');
Sua visualização então terá variáveis salvas chamadas headerContent e bodyContent. Você pode então renderizar seu layout fazendo:
headerContent
bodyContent
Flight::render('layout', ['title' => 'Página Inicial']);
Se os arquivos de modelo forem assim:
header.php:
header.php
<h1><?= $heading ?></h1>
body.php:
body.php
<div><?= $body ?></div>
layout.php:
layout.php
<html> <head> <title><?= $title ?></title> </head> <body> <?= $headerContent ?> <?= $bodyContent ?> </body> </html>
<html> <head> <title>Página Inicial</title> </head> <body> <h1>Olá</h1> <div>Mundo</div> </body> </html>
O Flight permite que você substitua a engine de visualização padrão simplesmente registrando sua própria classe de visualização. Veja como você usaria o Smarty template engine para suas visualizações:
// Carregar biblioteca do Smarty require './Smarty/libs/Smarty.class.php'; // Registrar Smarty como a classe de visualização // Também passar uma função de retorno de chamada para configurar o Smarty ao carregar Flight::register('view', Smarty::class, [], function (Smarty $smarty) { $smarty->setTemplateDir('./templates/'); $smarty->setCompileDir('./templates_c/'); $smarty->setConfigDir('./config/'); $smarty->setCacheDir('./cache/'); }); // Atribuir dados do modelo Flight::view()->assign('name', 'Bob'); // Exibir o modelo Flight::view()->display('hello.tpl');
Para completude, você também deve substituir o método de renderização padrão do Flight:
Flight::map('render', function(string $template, array $data): void { Flight::view()->assign($data); Flight::view()->display($template); });
O Flight fornece alguma funcionalidade básica de modelagem por padrão.
Se você precisar de necessidades de modelagem mais complexas, consulte exemplos do Smarty e Latte na seção Visualizações Personalizadas.
Para exibir um modelo de visualização, chame o método render com o nome do arquivo de modelo e dados de modelo opcionais:
A variável name agora está disponível em todas as suas visualizações. Portanto, você pode simplesmente fazer:
Observe que ao especificar o nome do modelo no método de renderização, você pode omitir a extensão .php.
É comum que os sites tenham um único arquivo de modelo de layout com conteúdo alternante. Para renderizar conteúdo a ser usado em um layout, você pode passar um parâmetro opcional para o método render.
Sua visualização terá então variáveis salvas chamadas headerContent e bodyContent. Você pode então renderizar seu layout fazendo:
O Flight permite que você substitua o mecanismo de visualização padrão simplesmente registrando sua própria classe de visualização.
Veja como você usaria o Smarty mecanismo de modelo para suas visualizações:
// Carregar biblioteca Smarty requerer './Smarty/libs/Smarty.class.php'; // Registrar o Smarty como a classe de visualização // Passe também uma função de retorno de chamada para configurar o Smarty ao carregar Flight::register('view', Smarty::class, [], function (Smarty $smarty) { $smarty->setTemplateDir('./templates/'); $smarty->setCompileDir('./templates_c/'); $smarty->setConfigDir('./config/'); }); // Atribuir dados de modelo Flight::view()->assign('name', 'Bob'); // Exibir o modelo Flight::view()->display('hello.tpl');
Flight::map('render', função (string $template, array $data): vazio { Flight::view()->assign($data); Flight::view()->display($template); });
Veja como você usaria o Latte mecanismo de modelo para suas visualizações:
// Registrar o Latte como a classe de visualização // Passe também uma função de retorno de chamada para configurar o Latte ao carregar Flight::register('view', Latte\Engine::class, [], function (Latte\Engine $latte) { // Aqui é onde o Latte irá armazenar em cache seus modelos para acelerar as coisas // Uma coisa legal sobre o Latte é que ele atualiza automaticamente seu // cache quando você faz alterações em seus modelos! $latte->setTempDirectory(__DIR__ . '/../cache/'); // Diga ao Latte onde o diretório raiz de suas visualizações estará. $latte->setLoader(new \Latte\Loaders\FileLoader(__DIR__ . '/../views/')); }); // E envolva-o para que você possa usar Flight::render() corretamente Flight::map('render', função (string $template, array $data): vazio { // Isso é como $latte_engine->render($template, $data); echo Flight::view()->render($template, $data); });
O Flight foi projetado para ser um framework extensível. O framework vem com um conjunto de métodos e componentes padrão, mas permite que você mapeie seus próprios métodos, registre suas próprias classes ou até mesmo substitua classes e métodos existentes.
Se você está procurando por um DIC (Dependency Injection Container), consulte a página do Dependency Injection Container.
Para mapear seu próprio método customizado simples, você usa a função map:
// Mapeie seu método Flight::map('hello', function (string $name) { echo "olá $name!"; }); // Chame seu método customizado Flight::hello('Bob');
Isso é usado mais quando você precisa passar variáveis para o seu método para obter um valor esperado. O uso do método register() como abaixo é mais para passar configuração e então chamar sua classe pré-configurada.
register()
Para registrar sua própria classe e configurá-la, você usa a função register:
// Registre sua classe Flight::register('user', User::class); // Obtenha uma instância da sua classe $user = Flight::user();
O método de registro também permite que você passe parâmetros para o construtor da sua classe. Portanto, ao carregar sua classe customizada, ela será pré-inicializada. Você pode definir os parâmetros do construtor passando um array adicional. Aqui está um exemplo de carregamento de uma conexão de banco de dados:
// Registrar classe com parâmetros do construtor Flight::register('db', PDO::class, ['mysql:host=localhost;dbname=test', 'usuario', 'senha']); // Obter uma instância da sua classe // Isso criará um objeto com os parâmetros definidos // // new PDO('mysql:host=localhost;dbname=test', 'usuario', 'senha'); // $db = Flight::db(); // e se você precisar dele mais tarde no seu código, basta chamar o mesmo método novamente class SomeController { public function __construct() { $this->db = Flight::db(); } }
Se você passar um parâmetro de retorno de chamada adicional, ele será executado imediatamente após a construção da classe. Isso permite que você execute quaisquer procedimentos de configuração para o seu novo objeto. A função de retorno de chamada recebe um parâmetro, uma instância do novo objeto.
// O callback receberá o objeto que foi construído Flight::register( 'db', PDO::class, ['mysql:host=localhost;dbname=test', 'usuario', 'senha'], function (PDO $db) { $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); } );
Por padrão, toda vez que você carrega sua classe, você obterá uma instância compartilhada. Para obter uma nova instância de uma classe, basta passar false como parâmetro:
// Instância compartilhada da classe $compartilhado = Flight::db(); // Nova instância da classe $novo = Flight::db(false);
Lembre-se de que métodos mapeados têm precedência sobre classes registradas. Se você declarar ambos com o mesmo nome, apenas o método mapeado será invocado.
O Flight permite que você substitua sua funcionalidade padrão para atender às suas próprias necessidades, sem precisar modificar nenhum código.
Por exemplo, quando o Flight não consegue corresponder a uma URL a uma rota, ele invoca o método notFound que envia uma resposta genérica HTTP 404. Você pode substituir esse comportamento usando o método map:
Flight::map('notFound', function() { // Exibir página de erro personalizada 404 include 'errors/404.html'; });
O Flight também permite que você substitua componentes essenciais do framework. Por exemplo, você pode substituir a classe de Roteador padrão por sua própria classe personalizada:
// Registre sua classe personalizada Flight::register('router', MyRouter::class); // Quando o Flight carregar a instância do Roteador, ele carregará sua classe $meuRoteador = Flight::router();
No entanto, métodos do framework como map e register não podem ser substituídos. Você receberá um erro se tentar fazer isso.
# JSON Flight fornece suporte para o envio de respostas JSON e JSONP. Para enviar uma resposta JSON, você passa alguns dados a serem codificados em JSON: ```php Flight::json(['id' => 123]);
Para solicitações JSONP, você pode passar opcionalmente o nome do parâmetro de consulta que está usando para definir sua função de retorno de chamada:
Portanto, ao fazer uma solicitação GET usando ?q=my_func, você deve receber a saída:
?q=my_func
my_func({"id":123});
O carregamento automático é um conceito em PHP onde você especifica um diretório ou diretórios para carregar classes. Isso é muito mais benéfico do que usar require ou include para carregar classes. Também é um requisito para usar pacotes do Composer.
require
include
Por padrão, qualquer classe Flight é carregada automaticamente para você graças ao composer. No entanto, se você deseja carregar suas próprias classes, pode usar o método Flight::path para especificar um diretório para carregar classes.
Flight::path
Vamos supor que temos uma árvore de diretórios como a seguinte:
# Caminho de Exemplo /home/user/project/my-flight-project/ ├── app │ ├── cache │ ├── config │ ├── controllers - contém os controladores para este projeto │ ├── translations │ ├── UTILS - contém classes apenas para esta aplicação (em maiúsculas de propósito para um exemplo posterior) │ └── views └── public └── css └── js └── index.php
Você pode especificar cada diretório para carregar da seguinte forma:
/** * public/index.php */ // Adicione um caminho ao carregador automático Flight::path(__DIR__.'/../app/controllers/'); Flight::path(__DIR__.'/../app/utils/'); /** * app/controllers/MyController.php */ // nenhum espaço de nomes necessário // Todas as classes carregadas automaticamente são recomendadas para serem Pascal Case (cada palavra em maiúscula, sem espaços) // A partir da versão 3.7.2, você pode usar Pascal_Snake_Case para os nomes de suas classes executando Loader::setV2ClassLoading(false); class MyController { public function index() { // faça algo } }
Se você tiver espaços de nomes, na verdade se torna muito fácil implementar isso. Você deve usar o método Flight::path() para especificar o diretório raiz (não o diretório do documento ou a pasta public/) de sua aplicação.
Flight::path()
public/
/** * public/index.php */ // Adicione um caminho ao carregador automático Flight::path(__DIR__.'/../');
Agora é assim que o seu controlador pode ser parecido. Observe o exemplo abaixo, mas preste atenção nos comentários para informações importantes.
/** * app/controllers/MyController.php */ // espaços de nomes são necessários // os espaços de nomes são iguais à estrutura de diretórios // os espaços de nomes devem seguir o mesmo caso que a estrutura de diretórios // os espaços de nomes e diretórios não podem ter nenhum sublinhado (a menos que Loader::setV2ClassLoading(false) seja definido) namespace app\controllers; // Todas as classes carregadas automaticamente são recomendadas para ser Pascal Case (cada palavra em maiúscula, sem espaços) // A partir da versão 3.7.2, você pode usar Pascal_Snake_Case para os nomes de suas classes executando Loader::setV2ClassLoading(false); class MyController { public function index() { // faça algo } }
E se você quisesse carregar automaticamente uma classe em seu diretório de utils, você faria basicamente a mesma coisa:
/** * app/UTILS/ArrayHelperUtil.php */ // o espaço de nomes deve corresponder à estrutura e ao caso do diretório (observe que o diretório UTILS está em maiúsculas // como na árvore de arquivos acima) namespace app\UTILS; class ArrayHelperUtil { public function changeArrayCase(array $array) { // faça algo } }
A partir da versão 3.7.2, você pode usar Pascal_Snake_Case para os nomes de suas classes executando Loader::setV2ClassLoading(false);. Isso permitirá que você use sublinhados em seus nomes de classes. Isso não é recomendado, mas está disponível para aqueles que precisam.
Loader::setV2ClassLoading(false);
/** * public/index.php */ // Adicione um caminho ao carregador automático Flight::path(__DIR__.'/../app/controllers/'); Flight::path(__DIR__.'/../app/utils/'); Loader::setV2ClassLoading(false); /** * app/controllers/My_Controller.php */ // nenhum espaço de nomes necessário class My_Controller { public function index() { // faça algo } }
Esta página irá ajudar você a resolver problemas comuns que podem surgir ao usar o Flight.
Se você estiver vendo um erro 404 Não Encontrado (mas jura pela sua vida que realmente está lá e não é um erro de digitação), isso na verdade pode ser um problema com você retornando um valor no final da sua rota em vez de apenas ecoá-lo. O motivo para isso é intencional, mas pode pegar alguns desenvolvedores de surpresa.
Flight::route('/hello', function(){ // Isso pode causar um erro 404 Não Encontrado return 'Olá Mundo'; }); // O que você provavelmente deseja Flight::route('/hello', function(){ echo 'Olá Mundo'; });
O motivo para isso é por causa de um mecanismo especial incorporado no roteador que trata a saída de retorno como um sinal de "ir para a próxima rota". Você pode ver o comportamento documentado na seção de Roteamento.
Se estiver a usar Composer, pode executar o seguinte comando:
composer require flightphp/core
OU então pode baixar os arquivos diretamente e extrair para o seu diretório web.
Para o Apache, edite o seu ficheiro .htaccess com o seguinte:
.htaccess
RewriteEngine On RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule ^(.*)$ index.php [QSA,L]
Nota: Se necessitar de utilizar o Flight numa subpasta, adicione a linha RewriteBase /subdir/ logo após RewriteEngine On. Nota: Se quiser proteger todos os ficheiros do servidor, como um ficheiro de bd ou env. Coloque isto no seu ficheiro .htaccess:
Nota: Se necessitar de utilizar o Flight numa subpasta, adicione a linha RewriteBase /subdir/ logo após RewriteEngine On.
RewriteBase /subdir/
RewriteEngine On
Nota: Se quiser proteger todos os ficheiros do servidor, como um ficheiro de bd ou env. Coloque isto no seu ficheiro .htaccess:
RewriteEngine On RewriteRule ^(.*)$ index.php
Para o Nginx, adicione o seguinte à declaração do seu servidor:
server { location / { try_files $uri $uri/ /index.php; } }
<?php // Se estiver a usar o Composer, requer o autoloader. require 'vendor/autoload.php'; // se não estiver a usar o Composer, carregue o framework diretamente // require 'flight/Flight.php'; // Depois defina uma rota e atribua uma função para lidar com o pedido. Flight::route('/', function () { echo 'hello world!'; }); // Por fim, inicie o framework. Flight::start();
Direitos autorais © 2023 @mikecao, @n0nag0n
2023
@mikecao, @n0nag0n
É concedida permissão, gratuitamente, a qualquer pessoa que obtenha uma cópia deste software e documentação associada (os "Software"), para lidar com o Software sem restrições, incluindo, sem limitação, os direitos de usar, copiar, modificar, mesclar, publicar, distribuir, sublicenciar e/ou vender cópias do Software, e permitir às pessoas a quem o Software é fornecido, que o façam, sujeito às seguintes condições:
O aviso de direitos autorais acima e este aviso de permissão devem ser incluídos em todas as cópias ou porções substanciais do Software.
O SOFTWARE É FORNECIDO "COMO ESTÁ", SEM GARANTIA DE QUALQUER TIPO, EXPRESSA OU IMPLÍCITA, INCLUINDO, MAS NÃO SE LIMITANDO ÀS GARANTIAS DE COMERCIABILIDADE, ADEQUAÇÃO A UMA FINALIDADE ESPECÍFICA E NÃO VIOLAÇÃO. EM NENHUM CASO OS AUTORES OU TITULARES DE DIREITOS AUTORAIS SERÃO RESPONSÁVEIS POR QUALQUER REIVINDICAÇÃO, DANOS OU OUTRA RESPONSABILIDADE, SEJA EM UMA AÇÃO DE CONTRATO, DELITO OU OUTRO, DECORRENTE DE, FORA DE OU EM CONEXÃO COM O SOFTWARE OU O USO OU OUTRAS TRANSAÇÕES NO SOFTWARE.
O Flight é um framework rápido, simples e extensível para PHP. É bastante versátil e pode ser usado para construir qualquer tipo de aplicação web. É construído com simplicidade em mente e é escrito de forma fácil de entender e usar.
O Flight é um ótimo framework para iniciantes que são novos no PHP e desejam aprender como construir aplicações web. Também é um ótimo framework para desenvolvedores experientes que desejam ter mais controle sobre suas aplicações web. É projetado para facilitar a construção de uma API RESTful, uma aplicação web simples ou uma aplicação web complexa.
<?php // se instalado com o composer require 'vendor/autoload.php'; // ou se instalado manualmente por arquivo zip // require 'flight/Flight.php'; Flight::route('/', function() { echo 'olá mundo!'; }); Flight::route('/json', function() { Flight::json(['olá' => 'mundo']); }); Flight::start();
Simples o suficiente, certo? Saiba mais sobre o Flight na documentação!
Há um aplicativo de exemplo que pode ajudá-lo a começar com o Framework Flight. Visite flightphp/skeleton para instruções sobre como começar! Você também pode visitar a página de exemplos para se inspirar em algumas coisas que você pode fazer com o Flight.
Estamos no Matrix! Converse conosco em #flight-php-framework:matrix.org.
Existem duas maneiras de contribuir com o Flight:
O Flight requer PHP 7.4 ou superior.
Nota: O PHP 7.4 é suportado porque no momento da escrita (2024) o PHP 7.4 é a versão padrão para algumas distribuições LTS do Linux. Forçar a mudança para o PHP >8 causaria muitos problemas para esses usuários. O framework também suporta o PHP >8.
O Flight é lançado sob a licença MIT.
overclokk/cookie é uma biblioteca simples para gerenciar cookies em seu aplicativo.
A instalação é simples com o composer.
composer require overclokk/cookie
O uso é tão simples quanto registrar um novo método na classe Flight.
use Overclokk\Cookie\Cookie; /* * Defina em seu arquivo de inicialização ou public/index.php */ Flight::register('cookie', Cookie::class); /** * ExampleController.php */ class ExampleController { public function login() { // Defina um cookie // você vai querer que isso seja falso para obter uma nova instância // use o comentário abaixo se quiser autocompletar /** @var \Overclokk\Cookie\Cookie $cookie */ $cookie = Flight::cookie(false); $cookie->set( 'stay_logged_in', // nome do cookie '1', // o valor que você deseja definir 86400, // número de segundos que o cookie deve durar '/', // caminho em que o cookie estará disponível 'example.com', // domínio em que o cookie estará disponível true, // o cookie só será transmitido por uma conexão HTTPS segura true // o cookie só estará disponível por meio do protocolo HTTP ); // opcionalmente, se você quiser manter os valores padrão // e ter uma maneira rápida de definir um cookie por um longo tempo $cookie->forever('stay_logged_in', '1'); } public function home() { // Verifique se você tem o cookie if (Flight::cookie()->has('stay_logged_in')) { // colocá-los na área do painel, por exemplo. Flight::redirect('/dashboard'); } } }
defuse/php-encryption é uma biblioteca que pode ser usada para criptografar e descriptografar dados. Começar a usar é bastante simples para começar a criptografar e descriptografar dados. Eles têm um ótimo tutorial que ajuda a explicar o básico de como usar a biblioteca, bem como importantes implicações de segurança relacionadas à criptografia.
composer require defuse/php-encryption
Em seguida, você precisará gerar uma chave de criptografia.
vendor/bin/generate-defuse-key
Isso vai gerar uma chave que você precisará manter em segurança. Você poderia guardar a chave em seu arquivo app/config/config.php no array no final do arquivo. Embora não seja o local perfeito, é pelo menos algo.
app/config/config.php
Agora que você tem a biblioteca e uma chave de criptografia, você pode começar a criptografar e descriptografar dados.
use Defuse\Crypto\Crypto; use Defuse\Crypto\Key; /* * Defina em seu arquivo de inicialização ou public/index.php */ // Método de criptografia Flight::map('encrypt', function($dados_brutos) { $chave_criptografia = /* $config['encryption_key'] ou um file_get_contents de onde você colocou a chave */; return Crypto::encrypt($dados_brutos, Key::loadFromAsciiSafeString($chave_criptografia)); }); // Método de descriptografia Flight::map('decrypt', function($dados_criptografados) { $chave_criptografia = /* $config['encryption_key'] ou um file_get_contents de onde você colocou a chave */; try { $dados_brutos = Crypto::decrypt($dados_criptografados, Key::loadFromAsciiSafeString($chave_criptografia)); } catch (Defuse\Crypto\Exception\WrongKeyOrModifiedCiphertextException $ex) { // Um ataque! Ou a chave errada foi carregada, ou o texto cifrado foi // alterado desde que foi criado - corrompido no banco de dados ou // intencionalmente modificado por Eve tentando realizar um ataque. // ... trate este caso de uma maneira adequada à sua aplicação ... } return $dados_brutos; }); Flight::route('/encrypt', function() { $dados_criptografados = Flight::encrypt('Isto é um segredo'); echo $dados_criptografados; }); Flight::route('/decrypt', function() { $dados_criptografados = '...'; // Obtenha os dados criptografados de algum lugar $dados_descriptografados = Flight::decrypt($dados_criptografados); echo $dados_descriptografados; });
Classe de cache em arquivo PHP leve, simples e independente
Vantagens
Instale via composer:
composer require wruczek/php-file-cache
O uso é bastante direto.
use Wruczek\PhpFileCache\PhpFileCache; $app = Flight::app(); // Você passa o diretório em que o cache será armazenado para o construtor $app->register('cache', PhpFileCache::class, [ __DIR__ . '/../cache/' ], function(PhpFileCache $cache) { // Isso garante que o cache seja usado somente em modo de produção // AMBIENTE é uma constante definida em seu arquivo de inicialização ou em outro lugar em seu aplicativo $cache->setDevMode(ENVIRONMENT === 'development'); });
Então você pode usá-lo em seu código assim:
// Obter instância de cache $cache = Flight::cache(); $data = $cache->refreshIfExpired('simple-cache-test', function () { return date("H:i:s"); // retornar dados a serem armazenados em cache }, 10); // 10 segundos // ou $data = $cache->retrieve('simple-cache-test'); if(empty($data)) { $data = date("H:i:s"); $cache->store('simple-cache-test', $data, 10); // 10 segundos }
Visite https://github.com/Wruczek/PHP-File-Cache para ver a documentação completa e certifique-se de verificar a pasta de exemplos.
O Flight é incrivelmente extensível. Existem vários plugins que podem ser usados para adicionar funcionalidades à sua aplicação Flight. Alguns são oficialmente suportados pela Equipe do Flight e outros são bibliotecas micro/lite para ajudá-lo a começar.
O armazenamento em cache é uma ótima maneira de acelerar a sua aplicação. Existem diversas bibliotecas de cache que podem ser usadas com o Flight.
Depurar é crucial quando você está desenvolvendo no seu ambiente local. Existem alguns plugins que podem aprimorar a sua experiência de depuração.
Os bancos de dados são a base da maioria das aplicações. É assim que você armazena e recupera dados. Algumas bibliotecas de banco de dados são simplesmente wrappers para escrever consultas e outras são ORMs completos.
As sessões não são realmente úteis para APIs, mas para desenvolver uma aplicação web, as sessões podem ser cruciais para manter o estado e as informações de login.
A criação de modelos é essencial para qualquer aplicação web com uma interface de usuário. Existem várias engines de templates que podem ser usadas com o Flight.
Tem um plugin que gostaria de compartilhar? Envie uma solicitação pull para adicioná-lo à lista!
Gestor de Sessão PHP (não bloqueante, flash, segmento, criptografia de sessão). Usa PHP open_ssl para criptografia/opcional descriptografia de dados de sessão. Suporta Arquivo, MySQL, Redis e Memcached.
Instale com o composer.
composer require ghostff/session
Você não precisa passar nada para usar as configurações padrão com sua sessão. Você pode ler sobre mais configurações no Github Readme.
use Ghostff\Session\Session; require 'vendor/autoload.php'; $app = Flight::app(); $app->register('session', Session::class); // uma coisa para lembrar é que você deve confirmar sua sessão em cada carregamento de página // ou você precisará executar auto_commit em sua configuração.
Aqui está um exemplo simples de como você pode usar isso.
Flight::route('POST /login', function() { $session = Flight::session(); // faça sua lógica de login aqui // valide a senha, etc. // se o login for bem-sucedido $session->set('is_logged_in', true); $session->set('user', $user); // toda vez que escrever na sessão, você deve confirmar deliberate. $session->commit(); }); // Esta verificação poderia estar na lógica da página restrita, ou envolta com middleware. Flight::route('/alguma-página-restrita', function() { $session = Flight::session(); if(!$session->get('is_logged_in')) { Flight::redirect('/login'); } // faça sua lógica da página restrita aqui }); // a versão de middleware Flight::route('/alguma-página-restrita', function() { // lógica regular da página })->addMiddleware(function() { $session = Flight::session(); if(!$session->get('is_logged_in')) { Flight::redirect('/login'); } });
Aqui está um exemplo mais complexo de como você pode usar isso.
use Ghostff\Session\Session; require 'vendor/autoload.php'; $app = Flight::app(); // defina um caminho personalizado para o arquivo de configuração da sua sessão e forneça uma sequência aleatória para o id da sessão $app->register('session', Session::class, [ 'caminho/para/session_config.php', bin2hex(random_bytes(32)) ], function(Session $session) { // ou você pode substituir manualmente as opções de configuração $session->updateConfiguration([ // se deseja armazenar seus dados de sessão em um banco de dados (bom se desejar algo como funcionalidade "sair de todos os dispositivos") Session::CONFIG_DRIVER => Ghostff\Session\Drivers\MySql::class, Session::CONFIG_ENCRYPT_DATA => true, Session::CONFIG_SALT_KEY => hash('sha256', 'minha-super-senha-s3cr3ta'), // por favor, altere isso para ser algo diferente Session::CONFIG_AUTO_COMMIT => true, // faça isso somente se for necessário e/ou for difícil confirmar() sua sessão. // além disso, você poderia fazer Flight::after('start', function() { Flight::session()->commit(); }); Session::CONFIG_MYSQL_DS => [ 'driver' => 'mysql', # driver de banco de dados para dns do PDO eg(mysql:host=...;dbname=...) 'host' => '127.0.0.1', # host do banco de dados 'db_name' => 'nome_do_meu_banco_de_dados', # nome do banco de dados 'db_table' => 'sessoes', # tabela do banco de dados 'db_user' => 'root', # nome de usuário do banco de dados 'db_pass' => '', # senha do banco de dados 'persistent_conn'=> false, # Evite o overhead de estabelecer uma nova conexão toda vez que um script precisa falar com um banco de dados, resultando em um aplicativo web mais rápido. ENCONTRE O LADO RUIM SOZINHO ] ]); } );
Você está definindo seus dados de sessão e eles não estão persistindo entre as solicitações? Você pode ter esquecido de confirmar os dados de sua sessão. Você pode fazer isso chamando $session->commit() depois de definir seus dados de sessão.
$session->commit()
Flight::route('POST /login', function() { $session = Flight::session(); // faça sua lógica de login aqui // valide a senha, etc. // se o login for bem-sucedido $session->set('is_logged_in', true); $session->set('user', $user); // toda vez que escrever na sessão, você deve confirmar deliberate. $session->commit(); });
Outra maneira de contornar isso é quando você configura o serviço de sessão, você deve definir auto_commit como true em sua configuração. Isso irá confirmar automaticamente seus dados de sessão após cada solicitação.
auto_commit
$app->register('session', Session::class, [ 'caminho/para/session_config.php', bin2hex(random_bytes(32)) ], function(Session $session) { $session->updateConfiguration([ Session::CONFIG_AUTO_COMMIT => true, ]); } );
Além disso, você poderia fazer Flight::after('start', function() { Flight::session()->commit(); }); para confirmar seus dados de sessão após cada solicitação.
Flight::after('start', function() { Flight::session()->commit(); });
Visite o Github Readme para documentação completa. As opções de configuração estão bem documentadas no arquivo default_config.php em si. O código é simples de entender se você quiser examinar este pacote você mesmo.
A pista é uma aplicação CLI que ajuda a gerenciar suas aplicações Flight. Pode gerar controladores, exibir todas as rotas e muito mais. É baseado na excelente biblioteca adhocore/php-cli.
composer require flightphp/runway
Da primeira vez que você executar a pista, ele irá guiá-lo através de um processo de configuração e criar um arquivo de configuração .runway.json na raiz do seu projeto. Este arquivo conterá algumas configurações necessárias para a pista funcionar corretamente.
.runway.json
A pista possui vários comandos que você pode usar para gerenciar sua aplicação Flight. Existem duas maneiras fáceis de usar a Pista.
php runway [comando]
vendor/bin/runway [comando]
Para qualquer comando, você pode passar a flag --help para obter mais informações sobre como usar o comando.
--help
php runway routes --help
Aqui estão alguns exemplos:
Com base na configuração em seu arquivo .runway.json, a localização padrão gerará um controlador para você no diretório app/controllers/.
app/controllers/
php runway make:controller MyController
Com base na configuração em seu arquivo .runway.json, a localização padrão gerará um controlador para você no diretório app/records/.
app/records/
php runway make:record users
Se por acaso você tiver a tabela users com o seguinte esquema: id, name, email, created_at, updated_at, um arquivo semelhante ao seguinte será criado no arquivo app/records/UserRecord.php:
users
email
created_at
updated_at
app/records/UserRecord.php
<?php declare(strict_types=1); namespace app\records; /** * Classe ActiveRecord para a tabela de usuários. * @link https://docs.flightphp.com/awesome-plugins/active-record * * @property int $id * @property string $name * @property string $email * @property string $created_at * @property string $updated_at */ class UserRecord extends \flight\ActiveRecord { /** * @var array $relations Defina os relacionamentos para o modelo * https://docs.flightphp.com/awesome-plugins/active-record#relationships */ protected array $relations = []; /** * Construtor * @param mixed $databaseConnection A conexão com o banco de dados */ public function __construct($databaseConnection) { parent::__construct($databaseConnection, 'users'); } }
Isso exibirá todas as rotas atualmente registradas no Flight.
php runway routes
Se você deseja visualizar apenas rotas específicas, pode passar uma flag para filtrar as rotas.
# Mostrar apenas rotas GET php runway routes --get # Mostrar apenas rotas POST php runway routes --post # etc.
Se estiver criando um pacote para o Flight, ou deseja adicionar seus próprios comandos personalizados em seu projeto, você pode fazer isso criando um diretório src/commands/, flight/commands/, app/commands/ ou commands/ para seu projeto/pacote.
src/commands/
flight/commands/
app/commands/
commands/
Para criar um comando, você simplesmente estende a classe AbstractBaseCommand e implementa no mínimo um método __construct e um método execute.
AbstractBaseCommand
__construct
execute
<?php declare(strict_types=1); namespace flight\commands; class ExampleCommand extends AbstractBaseCommand { /** * Construtor * * @param array<string,mixed> $config Configuração JSON de .runway-config.json */ public function __construct(array $config) { parent::__construct('make:example', 'Criar um exemplo para a documentação', $config); $this->argument('<funny-gif>', 'O nome do gif engraçado'); } /** * Executa a função * * @return void */ public function execute(string $controller) { $io = $this->app()->io(); $io->info('Criando exemplo...'); // Faça algo aqui $io->ok('Exemplo criado!'); } }
Consulte a Documentação do adhocore/php-cli para obter mais informações sobre como criar seus próprios comandos personalizados em sua aplicação Flight!
=====
Este é um conjunto de extensões para tornar o trabalho com o Flight um pouco mais rico.
$_GET
$_POST
$_FILES
$_SESSION
Este é o Painel
E cada painel exibe informações muito úteis sobre sua aplicação!
Execute composer require flightphp/tracy-extensions --dev e você está pronto!
composer require flightphp/tracy-extensions --dev
Há muito pouca configuração que você precisa fazer para começar. Você precisará iniciar o depurador Tracy antes de usar isso https://tracy.nette.org/en/guide:
<?php use Tracy\Debugger; use flight\debug\tracy\TracyExtensionLoader; // código bootstrap require __DIR__ . '/vendor/autoload.php'; Debugger::enable(); // Você pode precisar especificar seu ambiente com Debugger::enable(Debugger::DEVELOPMENT) // se você usa conexões de banco de dados em seu aplicativo, há um // wrapper PDO necessário para usar APENAS NO DESENVOLVIMENTO (por favor, não em produção!) // Tem os mesmos parâmetros de uma conexão PDO regular $pdo = new PdoQueryCapture('sqlite:test.db', 'user', 'pass'); // ou se você conectar isso ao framework Flight Flight::register('db', PdoQueryCapture::class, ['sqlite:test.db', 'user', 'pass']); // agora sempre que você fizer uma consulta, irá capturar o tempo, consulta e parâmetros // Isso conecta os pontos if(Debugger::$showBar === true) { // Isso precisa ser falso ou Tracy não pode realmente renderizar :( Flight::set('flight.content_length', false); new TracyExtensionLoader(Flight::app()); } // mais código Flight::start();
Se você tiver um manipulador de sessão personalizado (como ghostff/session), você pode passar qualquer array de dados de sessão para Tracy e ele os exibirá automaticamente para você. Você passa com a chave session_data no segundo parâmetro do construtor TracyExtensionLoader.
session_data
TracyExtensionLoader
use Ghostff\Session\Session; require 'vendor/autoload.php'; $app = Flight::app(); $app->register('session', Session::class); if(Debugger::$showBar === true) { // Isso precisa ser falso ou Tracy não pode realmente renderizar :( Flight::set('flight.content_length', false); new TracyExtensionLoader(Flight::app(), [ 'session_data' => Flight::session()->getAll() ]); } // rotas e outras coisas... Flight::start();
Se você tiver o Latte instalado em seu projeto, você pode usar o painel Latte para analisar seus modelos. Você pode passar a instância do Latte para o construtor TracyExtensionLoader com a chave latte no segundo parâmetro.
latte
use Latte\Engine; require 'vendor/autoload.php'; $app = Flight::app(); $app->register('latte', Engine::class, [], function($latte) { $latte->setTempDirectory(__DIR__ . '/temp'); // aqui é onde você adiciona o Painel do Latte ao Tracy $latte->addExtension(new Latte\Bridges\Tracy\TracyExtension); }); if(Debugger::$showBar === true) { // Isso precisa ser falso ou Tracy não pode realmente renderizar :( Flight::set('flight.content_length', false); new TracyExtensionLoader(Flight::app()); }
Tracy é um incrível manipulador de erros que pode ser usado com Flight. Possui vários painéis que podem ajudá-lo a depurar sua aplicação. É também muito fácil de estender e adicionar seus próprios painéis. A equipe do Flight criou alguns painéis especificamente para projetos do Flight com o plugin flightphp/tracy-extensions.
Instale com o compositor. E na verdade, você vai querer instalar isso sem a versão de desenvolvimento, já que o Tracy vem com um componente de tratamento de erros de produção.
composer require tracy/tracy
Existem algumas opções de configuração básicas para começar. Você pode ler mais sobre elas na Documentação do Tracy.
require 'vendor/autoload.php'; use Tracy\Debugger; // Habilitar Tracy Debugger::enable(); // Debugger::enable(Debugger::DEVELOPMENT) // às vezes você precisa ser explícito (também Debugger::PRODUCTION) // Debugger::enable('23.75.345.200'); // você também pode fornecer um array de endereços IP // Aqui é onde os erros e exceções serão registrados. Certifique-se de que este diretório exista e seja gravável. Debugger::$logDirectory = __DIR__ . '/../log/'; Debugger::$strictMode = true; // exibir todos os erros // Debugger::$strictMode = E_ALL & ~E_DEPRECATED & ~E_USER_DEPRECATED; // todos os erros exceto avisos obsoletos if (Debugger::$showBar) { $app->set('flight.content_length', false); // se a barra do Debugger estiver visível, então o comprimento do conteúdo não pode ser definido pelo Flight // Isso é específico para a Extensão do Tracy para o Flight se você incluiu isso // caso contrário, comente isso. new TracyExtensionLoader($app); }
Ao depurar seu código, existem algumas funções muito úteis para exibir dados para você.
bdump($var)
dumpe($var)
Um registro ativo está mapeando uma entidade do banco de dados para um objeto PHP. Falando claramente, se você tem uma tabela de usuários no seu banco de dados, você pode "traduzir" uma linha dessa tabela para uma classe User e um objeto $user no seu código-fonte. Veja exemplo básico.
User
$user
Vamos assumir que você tenha a seguinte tabela:
CREATE TABLE users ( id INTEGER PRIMARY KEY, name TEXT, password TEXT );
Agora você pode configurar uma nova classe para representar essa tabela:
/** * Uma classe ActiveRecord geralmente é singular * * É altamente recomendado adicionar as propriedades da tabela como comentários aqui * * @property int $id * @property string $name * @property string $password */ class User extends flight\ActiveRecord { public function __construct($conexao_banco_de_dados) { // você pode configurar assim parent::__construct($conexao_banco_de_dados, 'users'); // ou assim parent::__construct($conexao_banco_de_dados, null, [ 'table' => 'users']); } }
Agora veja a mágica acontecer!
// para sqlite $conexao_banco_de_dados = new PDO('sqlite:test.db'); // isso é apenas um exemplo, normalmente você usaria uma conexão de banco de dados real // para mysql $conexao_banco_de_dados = new PDO('mysql:host=localhost;dbname=test_db&charset=utf8bm4', 'nome_usuario', 'senha'); // ou mysqli $conexao_banco_de_dados = new mysqli('localhost', 'nome_usuario', 'senha', 'test_db'); // ou mysqli com criação não baseada em objeto $conexao_banco_de_dados = mysqli_connect('localhost', 'nome_usuario', 'senha', 'test_db'); $user = new User($conexao_banco_de_dados); $user->name = 'Bobby Tables'; $user->password = password_hash('alguma senha legal'); $user->insert(); // ou $user->save(); echo $user->id; // 1 $user->name = 'Joseph Mamma'; $user->password = password_hash('outra senha legal novamente!!!'); $user->insert(); // não é possível usar $user->save() aqui, senão ele pensará que é uma atualização! echo $user->id; // 2
E foi tão fácil adicionar um novo usuário! Agora que há uma linha de usuário no banco de dados, como você a extrai?
$user->find(1); // encontra id = 1 no banco de dados e retorna echo $user->name; // 'Bobby Tables'
E se você quiser encontrar todos os usuários?
$users = $user->findAll();
E com uma determinada condição?
$users = $user->like('name', '%mamma%')->findAll();
Veja como isso é divertido? Vamos instalá-lo e começar!
Simplesmente instale com o Composer
composer require flightphp/active-record
Isso pode ser usado como uma biblioteca independente ou com o Framework PHP Flight. Completamente com você.
Apenas certifique-se de passar uma conexão PDO para o construtor.
$conexao_pdo = new PDO('sqlite:test.db'); // isso é apenas um exemplo, normalmente você usaria uma conexão de banco de dados real $User = new User($pdo_connection);
Se você estiver usando o Framework PHP Flight, você pode registrar a classe ActiveRecord como um serviço (mas honestamente você não precisa).
Flight::register('user', 'User', [ $conexao_pdo ]); // então você pode usá-lo assim em um controlador, uma função, etc. Flight::user()->find(1);
find($id = null) : boolean|ActiveRecord
Encontra um registro e atribui ao objeto atual. Se você passar um $id de algum tipo, ele fará uma consulta na chave primária com esse valor. Se nada for passado, ele encontrará apenas o primeiro registro da tabela.
Além disso, você pode passar outros métodos auxiliares para consultar sua tabela.
// encontrar um registro com algumas condições antecipadamente $user->notNull('password')->orderBy('id DESC')->find(); // encontrar um registro por um id específico $id = 123; $user->find($id);
findAll(): array<int,ActiveRecord>
Encontra todos os registros na tabela que você especificar.
$user->findAll();
isHydrated(): boolean
Retorna true se o registro atual foi preenchido (buscado do banco de dados).
$user->find(1); // se um registro for encontrado com dados... $user->isHydrated(); // true
insert(): boolean|ActiveRecord
Insere o registro atual no banco de dados.
$user = new User($conexao_banco_de_dados); $user->name = 'demo'; $user->password = md5('demo'); $user->insert();
Se você tiver uma chave primária baseada em texto (como um UUID), você pode definir o valor da chave primária antes de inserir de duas maneiras.
$user = new User($conexao_banco_de_dados, [ 'primaryKey' => 'uuid' ]); $user->uuid = 'algum-uuid'; $user->name = 'demo'; $user->password = md5('demo'); $user->insert(); // ou $user->save();
ou você pode ter a chave primária gerada automaticamente para você por meio de eventos.
class User extends flight\ActiveRecord { public function __construct($conexao_banco_de_dados) { parent::__construct($conexao_banco_de_dados, 'users', [ 'primaryKey' => 'uuid' ]); // você também pode definir a chave primária assim em vez do array acima. $this->primaryKey = 'uuid'; } protected function beforeInsert(self $self) { $self->uuid = uniqid(); // ou como você precisa gerar seus IDs exclusivos } }
Se você não definir a chave primária antes de inserir, ela será definida como rowid e o banco de dados a gerará para você, mas não persistirá porque esse campo pode não existir em sua tabela. Por isso é recomendado usar o evento para lidar automaticamente com isso para você.
rowid
update(): boolean|ActiveRecord
Atualiza o registro atual no banco de dados.
$user->greaterThan('id', 0)->orderBy('id desc')->find(); $user->email = 'teste@example.com'; $user->update();
save(): boolean|ActiveRecord
Insere ou atualiza o registro atual no banco de dados. Se o registro tiver um id, ele será atualizado, caso contrário será inserido.
$user = new User($conexao_banco_de_dados); $user->name = 'demo'; $user->password = md5('demo'); $user->save();
Nota: Se você tiver relacionamentos definidos na classe, ele salvará recursivamente essas relações também se tiverem sido definidas, instanciadas e tiverem dados sujos para atualizar. (v0.4.0 e acima)
delete(): boolean
Exclui o registro atual do banco de dados.
$user->gt('id', 0)->orderBy('id desc')->find(); $user->delete();
Você também pode excluir vários registros executando uma pesquisa antes.
$user->like('name', 'Bob%')->delete();
dirty(array $dirty = []): ActiveRecord
Dados sujos referem-se aos dados que foram alterados em um registro.
$user->greaterThan('id', 0)->orderBy('id desc')->find(); // nada está "sujo" até este ponto. $user->email = 'teste@example.com'; // agora o email é considerado "sujo" porque foi alterado. $user->update(); // agora não há dados sujos porque foram atualizados e persistidos no banco de dados $user->password = password_hash()'nova_senha'); // agora isso está sujo $user->dirty(); // passar nada limpará todas as entradas sujas. $user->update(); // nada será atualizado porque nada foi capturado como sujo. $user->dirty([ 'name' => 'algo', 'password' => password_hash('outra senha') ]); $user->update(); // tanto o nome quanto a senha são atualizados.
copyFrom(array $data): ActiveRecord
Este é um alias para o método dirty(). É um pouco mais claro o que você está fazendo.
dirty()
$user->copyFrom([ 'name' => 'algo', 'password' => password_hash('outra senha') ]); $user->update(); // tanto o nome quanto a senha são atualizados.
isDirty(): boolean
Retorna true se o registro atual foi alterado.
$user->greaterThan('id', 0)->orderBy('id desc')->find(); $user->email = 'teste@email.com'; $user->isDirty(); // true
reset(bool $include_query_data = true): ActiveRecord
Redefine o registro atual para seu estado inicial. Isso é realmente bom de usar em comportamentos de loop. Se você passar true, também redefinirá os dados da consulta que foram usados para encontrar o objeto atual (comportamento padrão).
$users = $user->greaterThan('id', 0)->orderBy('id desc')->find(); $user_company = new UserCompany($conexao_banco_de_dados); foreach($users as $user) { $user_company->reset(); // comece com uma tela limpa $user_company->user_id = $user->id; $user_company->company_id = $algum_id_empresa; $user_company->insert(); }
getBuiltSql(): string
Depois de executar um método find(), findAll(), insert(), update(), ou save() você pode obter o SQL que foi construído e usá-lo para fins de depuração.
find()
findAll()
insert()
update()
save()
select(string $field1 [, string $field2 ... ])
Você pode selecionar apenas algumas das colunas em uma tabela se desejar (é mais eficiente em tabelas muito largas com muitas colunas)
$user->select('id', 'name')->find();
from(string $table)
Você pode escolher outra tabela também! Por que diabos não?!
$user->select('id', 'name')->from('user')->find();
join(string $table_name, string $join_condition)
Você também pode juntar-se a outra tabela no banco de dados.
$user->join('contatos', 'contatos.id_usuario = usuarios.id')->find();
where(string $where_conditions)
Você pode definir alguns argumentos where personalizados (você não pode definir parâmetros nesta instrução where)
$user->where('id=1 AND name="demo"')->find();
Nota de Segurança - Você pode ser tentado a fazer algo como $user->where("id = '{$id}' AND name = '{$name}'")->find();. POR FAVOR NÃO FAÇA ISSO!!! Isso é suscetível ao que é conhecido como ataques de injeção de SQL. Há muitos artigos online, por favor pesquise "ataques de injeção de sql php" e você encontrará muitos artigos sobre esse assunto. A maneira correta de lidar com isso com esta biblioteca é em vez do método where(), você faria algo mais como $user->eq('id', $id)->eq('name', $name)->find(); Se você absolutamente tiver que fazer isso, a biblioteca PDO tem $pdo->quote($var) para escapar isso para você. Somente após usar quote() você pode usá-lo em uma declaração where().
$user->where("id = '{$id}' AND name = '{$name}'")->find();
where()
$user->eq('id', $id)->eq('name', $name)->find();
$pdo->quote($var)
quote()
group(string $group_by_statement)/groupBy(string $group_by_statement)
Agrupe seus resultados por uma condição específica.
$user->select('COUNT(*) as count')->groupBy('name')->findAll();
order(string $order_by_statement)/orderBy(string $order_by_statement)
Ordene a consulta retornada de uma maneira específica.
$user->orderBy('name DESC')->find();
limit(string $limit)/limit(int $offset, int $limit)
Limite a quantidade de registros retornados. Se um segundo inteiro for fornecido, ele será um deslocamento, limitando apenas como no SQL.
$user->orderby('name DESC')->limit(0, 10)->findAll();
equal(string $field, mixed $value) / eq(string $field, mixed $value)
Onde field = $value
field = $value
$user->eq('id', 1)->find();
notEqual(string $field, mixed $value) / ne(string $field, mixed $value)
Onde field <> $value
field <> $value
$user->ne('id', 1)->find();
isNull(string $field)
Onde field IS NULL
field IS NULL
$user->isNull('id')->find();
isNotNull(string $field) / notNull(string $field)
Onde field IS NOT NULL
field IS NOT NULL
$user->isNotNull('id')->find();
greaterThan(string $field, mixed $value) / gt(string $field, mixed $value)
Onde field > $value
field > $value
$user->gt('id', 1)->find();
lessThan(string $field, mixed $value) / lt(string $field, mixed $value)
Onde field < $value
field < $value
$user->lt('id', 1)->find();
greaterThanOrEqual(string $field, mixed $value) / ge(string $field, mixed $value) / gte(string $field, mixed $value)
Onde field >= $value
field >= $value
$user->ge('id', 1)->find();
lessThanOrEqual(string $field, mixed $value) / le(string $field, mixed $value) / lte(string $field, mixed $value)
Onde field <= $value
field <= $value
$user->le('id', 1)->find();
like(string $field, mixed $value) / notLike(string $field, mixed $value)
Onde field LIKE $value ou field NOT LIKE $value
field LIKE $value
field NOT LIKE $value
$user->like('name', 'de')->find();
in(string $field, array $values) / notIn(string $field, array $values)
Onde field IN($value) ou field NOT IN($value)
field IN($value)
field NOT IN($value)
$user->in('id', [1, 2])->find();
between(string $field, array $values)
Onde field BETWEEN $value AND $value1
field BETWEEN $value AND $value1
$user->between('id', [1, 2])->find();
Você pode definir vários tipos de relacionamentos usando esta biblioteca. Você pode definir relacionamentos um para muitos e um para um entre tabelas. Isso requer uma configuração um pouco extra na classe antecipadamente.
Configurar o $relations array não é difícil, mas adivinhar a sintaxe correta pode ser confuso.
$relations
protected array $relations = [ // você pode nomear a chave como desejar. O nome do ActiveRecord é provavelmente bom. Ex: usuário, contato, cliente 'utilisateur' => [ // obrigatório // self::HAS_MANY, self::HAS_ONE, self::BELONGS_TO self::HAS_ONE, // este é o tipo de relacionamento // obrigatório 'Some_Class', // esta é a classe ActiveRecord "outra" que será referenciada // obrigatório // dependendo do tipo de relacionamento // self::HAS_ONE = a chave estrangeira que referencia o join // self::HAS_MANY = a chave estrangeira que referencia o join // self::BELONGS_TO = a chave local que referencia o join 'chave_local_ou_estrageira', // só para informação, isso também só junta à chave primária do modelo "outro" // opcional [ 'eq' => [ 'id_cliente', 5 ], 'select' => 'COUNT(*) as count', 'limit' 5 ], // condições adicionais que você deseja ao juntar a relação // $registro->eq('id_cliente', 5)->select('COUNT(*) as count')->limit(5)) // opcional 'nome_referencia_de_retorno' // isso é se você quiser fazer referência a esse relacionamento de volta para si mesmo Ex: $user->contato->usuário; ]; ]
class User extends ActiveRecord{ protected array $relations = [ 'contatos' => [ self::HAS_MANY, Contato::class, 'id_usuario' ], 'contato' => [ self::HAS_ONE, Contato::class, 'id_usuario' ], ]; public function __construct($conexao_banco_de_dados) { parent::__construct($conexao_banco_de_dados, 'usuarios'); } } class Contato extends ActiveRecord{ protected array $relations = [ 'usuario' => [ self::BELONGS_TO, User::class, 'id_usuario' ], 'usuario_com_referencia_de_volta' => [ self::BELONGS_TO, User::class, 'id_usuario', [], 'contato' ], ]; public function __construct($conexao_banco_de_dados) { parent::__construct($conexao_banco_de_dados, 'contatos'); } }
Agora que as referências estão configuradas, podemos usá-las muito facilmente!
$user = new User($conexao_pdo); // encontrar o usuário mais recente. $user->notNull('id')->orderBy('id desc')->find(); // obter contatos usando a relação: foreach($user->contatos as $contato) { echo $contato->id; } // ou podemos ir pelo outro caminho. $contato = new Contato(); // encontrar um contato $contato->find(); // obter usuário usando a relação: echo $contato->usuario->nome; // este é o nome do usuário
Muito legal, né?
Às vezes, você pode precisar anexar algo único ao seu ActiveRecord, como um cálculo personalizado que pode ser mais fácil de anexar ao objeto para ser passado, por exemplo, para um modelo.
setCustomData(string $field, mixed $value)
Você anexa os dados personalizados com o método setCustomData().
setCustomData()
$user->setCustomData('contagem_visualizacoes_pagina', $contagem_visualizacoes_pagina);
E então você simplesmente o referencia como uma propriedade normal do objeto.
echo $user->contagem_visualizacoes_pagina;
Uma característica muito incrível sobre esta biblioteca é sobre os eventos. Os eventos são acionados em determinados momentos com base em certos métodos que você chama. Eles são muito, muito úteis na configuração de dados automaticamente para você.
onConstruct(ActiveRecord $ActiveRecord, array &config)
Isso é realmente útil se você precisar definir uma conexão padrão ou algo assim.
// index.php ou bootstrap.php Flight::register('db', 'PDO', [ 'sqlite:test.db' ]); // // // // User.php class User extends flight\ActiveRecord { protected function onConstruct(self $self, array &$config) { // não esqueça a referência de & // você poderia fazer isso para definir automaticamente a conexão $config['connection'] = Flight::db(); // ou isso $self->transformAndPersistConnection(Flight::db()); // Você também pode definir o nome da tabela desta forma. $config['table'] = 'usuarios'; } }
beforeFind(ActiveRecord $ActiveRecord)
Isso provavelmente só é útil se você precisar de uma manipulação da consulta toda vez.
class User extends flight\ActiveRecord { public function __construct($conexao_banco_de_dados) { parent::__construct($conexao_banco_de_dados, 'usuarios'); } protected function beforeFind(self $self) { // sempre executar id >= 0 se for do seu interesse $self->gte('id', 0); } }
afterFind(ActiveRecord $ActiveRecord)
Este é provavelmente mais útil se você precisar executar alguma lógica sempre que este registro for buscado. Você precisa descriptografar algo? Precisa executar uma consulta de contagem personalizada toda vez (não performático, mas tanto faz)?
class User extends flight\ActiveRecord { public function __construct($conexao_banco_de_dados) { parent::__construct($conexao_banco_de_dados, 'usuarios'); } protected function afterFind(self $self) { // descriptografando algo $self->secreto = suaFuncaoDescriptografar($self->secreto, $alguma_chave); // talvez armazenando algo personalizado como uma consulta??? $self->setCustomData('contagem_visualizacoes', $self->selecionar('COUNT(*) contagem')->de('visualizacoes_usuarios')->eq('id_usuario', $self->id)['contagem']; } }
beforeFindAll(ActiveRecord $ActiveRecord)
class User extends flight\ActiveRecord { public function __construct($conexao_banco_de_dados) { parent::__construct($conexao_banco_de_dados, 'usuarios'); } protected function beforeFindAll(self $self) { // sempre executar id >= 0 se for do seu interesse $self->gte('id', 0); } }
afterFindAll(array<int,ActiveRecord> $results)
Similar a afterFind() mas você faz isso para todos os registros!
afterFind()
class User extends flight\ActiveRecord { public function __construct($conexao_banco_de_dados) { parent::__construct($conexao_banco_de_dados, 'usuarios'); } protected function afterFindAll(array $results) { foreach($results as $self) { // faça algo legal como em afterFind() } } }
beforeInsert(ActiveRecord $ActiveRecord)
Realmente útil se você precisa que alguns valores padrão sejam definidos sempre.
class User extends flight\ActiveRecord { public function __construct($conexao_banco_de_dados) { parent::__construct($conexao_banco_de_dados, 'usuarios'); } protected function beforeInsert(self $self) { // definir alguns valores padrão if(!$self->data_criacao) { $self->data_criacao = gmdate('Y-m-d'); } if(!$self->senha) { $self->senha = password_hash((string) microtime(true)); } } }
afterInsert(ActiveRecord $ActiveRecord)
Talvez você tenha um caso de uso para alterar dados após a inserção?
class User extends flight\ActiveRecord { public function __construct($conexao_banco_de_dados) { parent::__construct($conexao_banco_de_dados, 'usuarios'); } protected function afterInsert(self $self) { // faça o que quiser Flight::cache()->set('id_insercao_mais_recente', $self->id); // ou qualquer outra coisa.... } }
beforeUpdate(ActiveRecord $ActiveRecord)
Realmente útil se você precisa que alguns valores padrão sejam definidos sempre em uma atualização.
class User extends flight\ActiveRecord { public function __construct($conexao_banco_de_dados) { parent::__construct($conexao_banco_de_dados, 'usuarios'); } protected function beforeInsert(self $self) { // definir alguns valores padrão if(!$self->data_atualizacao) { $self->data_atualizacao = gmdate('Y-m-d'); } } }
afterUpdate(ActiveRecord $ActiveRecord)
Talvez você tenha um caso de uso para alterar dados após uma atualização?
class User extends flight\ActiveRecord { public function __construct($conexao_banco_de_dados) { parent::__construct($conexao_banco_de_dados, 'usuarios'); } protected function afterInsert(self $self) { // faça o que quiser Flight::cache()->set('id_usuario_atualizado_recentemente', $self->id); // ou qualquer outra coisa.... } }
beforeSave(ActiveRecord $ActiveRecord)/afterSave(ActiveRecord $ActiveRecord)
Isso é útil se você quer que eventos aconteçam tanto quando inserções quanto atualizações acontecem. Vou poupar a longa explicação, mas tenho certeza de que você pode adivinhar do que se trata.
class User extends flight\ActiveRecord { public function __construct($conexao_banco_de_dados) { parent::__construct($conexao_banco_de_dados, 'usuarios'); } protected function beforeSave(self $self) { $self->ultima_atualizacao = gmdate('Y-m-d H:i:s'); } }
beforeDelete(ActiveRecord $ActiveRecord)/afterDelete(ActiveRecord $ActiveRecord)
Não tenho certeza do que você gostaria de fazer aqui, mas sem julgamentos aqui! Vá em frente!
class User extends flight\ActiveRecord { public function __construct($conexao_banco_de_dados) { parent::__construct($conexao_banco_de_dados, 'usuarios'); } protected function beforeDelete(self $self) { echo 'Ele foi um bravo soldado... :cry-face:'; } }
Quando você está usando esta biblioteca, você pode definir a conexão do banco de dados de algumas maneiras diferentes. Você pode definir a conexão no construtor, você pode configurá-la via uma variável de configuração $config['connection'] ou você pode configurá-la via setDatabaseConnection() (v0.4.1).
$config['connection']
setDatabaseConnection()
$conexao_pdo = new PDO('sqlite:test.db'); // por exemplo $user = new User($conexao_pdo); // ou $user = new User(null, [ 'connection' => $conexao_pdo ]); // ou $user = new User(); $user->setDatabaseConnection($conexao_pdo);
Se você precisar atualizar a conexão do banco de dados, por exemplo, se estiver executando um script CLI de longa execução e precisar atualizar a conexão periodicamente, você pode redefinir a conexão com $seu_registro->setDatabaseConnection($conexao_pdo).
$seu_registro->setDatabaseConnection($conexao_pdo)
Por favor faça. :D
Ao contribuir, certifique-se de executar composer test-coverage para manter 100% de cobertura dos testes (isso não é uma cobertura verdadeira de teste unitário, mais como testes de integração).
composer test-coverage
Também certifique-se de executar composer beautify e composer phpcs para corrigir quaisquer erros de lint.
composer beautify
composer phpcs
MIT
Latte é um mecanismo de modelagem completo que é muito fácil de usar e se aproxima mais da sintaxe do PHP do que o Twig ou o Smarty. Também é muito fácil de ampliar e adicionar seus próprios filtros e funções.
composer require latte/latte
Existem algumas opções de configuração básicas para começar. Você pode ler mais sobre elas na Documentação do Latte.
use Latte\Engine as LatteEngine; require 'vendor/autoload.php'; $app = Flight::app(); $app->register('latte', LatteEngine::class, [], function(LatteEngine $latte) use ($app) { // Aqui é onde o Latte irá armazenar em cache seus modelos para acelerar as coisas // Uma coisa legal sobre o Latte é que ele atualiza automaticamente seu // cache quando você faz alterações em seus modelos! $latte->setTempDirectory(__DIR__ . '/../cache/'); // Diga ao Latte onde o diretório raiz para suas visualizações estará. $latte->setLoader(new \Latte\Loaders\FileLoader($app->get('flight.views.path'))); });
Aqui está um exemplo simples de um arquivo de layout. Este é o arquivo que será usado para envolver todas as suas outras visualizações.
<!-- app/views/layout.latte --> <!doctype html> <html lang="pt"> <head> <title>{$title ? $title . ' - '}My App</title> <link rel="stylesheet" href="style.css"> </head> <body> <header> <nav> <!-- seus elementos de navegação aqui --> </nav> </header> <div id="content"> <!-- Aqui está a mágica --> {block content}{/block} </div> <div id="footer"> © Direitos Autorais </div> </body> </html>
E agora temos seu arquivo que será renderizado dentro desse bloco de conteúdo:
<!-- app/views/home.latte --> <!-- Isso diz ao Latte que este arquivo está "dentro" do arquivo layout.latte --> {extends layout.latte} <!-- Este é o conteúdo que será renderizado dentro do layout dentro do bloco de conteúdo --> {block content} <h1>Página Inicial</h1> <p>Bem-vindo ao meu aplicativo!</p> {/block}
Então, quando você for renderizar isso dentro de sua função ou controlador, você faria algo assim:
// rota simples Flight::route('/', function () { Flight::latte()->render('home.latte', [ 'title' => 'Página Inicial' ]); }); // ou se estiver usando um controlador Flight::route('/', [HomeController::class, 'index']); // HomeController.php class HomeController { public function index() { Flight::latte()->render('home.latte', [ 'title' => 'Página Inicial' ]); } }
Veja a Documentação do Latte para mais informações sobre como usar o Latte ao máximo!
Flight é incrivelmente extensível. Existem vários plugins que podem ser usados para adicionar funcionalidades à sua aplicação do Flight. Alguns são oficialmente suportados pela Equipe do Flight e outros são bibliotecas micro/lite para ajudá-lo a começar.
O cache é uma ótima maneira de acelerar sua aplicação. Existem várias bibliotecas de cache que podem ser usadas com o Flight.
Aplicações CLI são uma ótima maneira de interagir com sua aplicação. Você pode usá-las para gerar controladores, exibir todas as rotas e muito mais.
Cookies são uma ótima maneira de armazenar pequenos pedaços de dados no lado do cliente. Eles podem ser usados para armazenar preferências do usuário, configurações da aplicação e muito mais.
A depuração é crucial ao desenvolver em seu ambiente local. Existem alguns plugins que podem aprimorar sua experiência de depuração.
Bancos de dados são essenciais para a maioria das aplicações. É assim que você armazena e recupera dados. Algumas bibliotecas de banco de dados são simplesmente wrappers para escrever consultas e outras são ORMs completos.
A criptografia é crucial para qualquer aplicação que armazena dados sensíveis. Criptografar e descriptografar os dados não é tão difícil, mas armazenar corretamente a chave de criptografia pode ser difícil. A coisa mais importante é nunca armazenar sua chave de criptografia em um diretório público ou incluí-la em seu repositório de código.
As sessões não são realmente úteis para APIs, mas para construir uma aplicação web, as sessões podem ser cruciais para manter o estado e as informações de login.
A criação de modelos é fundamental para qualquer aplicação web com uma UI. Existem várias engines de templating que podem ser usadas com o Flight.
Tem um plugin que gostaria de compartilhar? Envie um pull request para adicioná-lo à lista!
Você tem duas opções para começar com o Flight:
Embora estes não sejam oficialmente patrocinados pela equipe do Flight, eles podem lhe dar ideias sobre como estruturar seus próprios projetos construídos com o Flight!
Se você tem um projeto que deseja compartilhar, por favor envie uma solicitação de pull request para adicioná-lo a esta lista!