Learn/flight_vs_laravel
Flight vs Laravel
O que é Laravel?
Laravel é um framework completo com todos os recursos e um ecossistema focado em desenvolvedores impressionante, mas a um custo em desempenho e complexidade. O objetivo do Laravel é que o desenvolvedor tenha o mais alto nível de produtividade e torne tarefas comuns fáceis. Laravel é uma ótima escolha para desenvolvedores que desejam construir uma aplicação web completa e empresarial. Isso vem com algumas compensações, especificamente em termos de desempenho e complexidade. Aprender os conceitos iniciais do Laravel pode ser fácil, mas ganhar proficiência no framework pode levar algum tempo.
Há também tantos módulos do Laravel que os desenvolvedores frequentemente sentem que a única maneira de resolver problemas é por meio desses módulos, quando na verdade você poderia simplesmente usar outra biblioteca ou escrever seu próprio código.
Prós em comparação com Flight
- Laravel tem um enorme ecossistema de desenvolvedores e módulos que podem ser usados para resolver problemas comuns.
- Laravel tem um ORM completo que pode ser usado para interagir com seu banco de dados.
- Laravel tem uma insana quantidade de documentação e tutoriais que podem ser usados para aprender o framework. Isso pode ser bom para mergulhar nos detalhes ou ruim porque há tanto para percorrer.
- Laravel tem um sistema de autenticação integrado que pode ser usado para proteger sua aplicação.
- Laravel tem podcasts, conferências, reuniões, vídeos e outros recursos que podem ser usados para aprender o framework.
- Laravel é voltado para um desenvolvedor experiente que deseja construir uma aplicação web completa e empresarial.
Contras em comparação com Flight
- Laravel tem muito mais acontecendo sob o capô do que Flight. Isso vem a um custo dramático em termos de desempenho. Veja os benchmarks do TechEmpower para mais informações.
- Flight é voltado para um desenvolvedor que deseja construir uma aplicação web leve, rápida e fácil de usar.
- Flight é voltado para simplicidade e facilidade de uso.
- Uma das principais características do Flight é que ele faz o possível para manter a compatibilidade retroativa. Laravel causa muita frustração entre versões principais.
- Flight é destinado a desenvolvedores que estão se aventurando no mundo dos frameworks pela primeira vez.
- Flight não tem dependências, enquanto Laravel tem uma quantidade atroz de dependências
- Flight também pode fazer aplicações de nível empresarial, mas não tem tanto código boilerplate quanto Laravel. Também exigirá mais disciplina por parte do desenvolvedor para manter as coisas organizadas e bem estruturadas.
- Flight dá ao desenvolvedor mais controle sobre a aplicação, enquanto Laravel tem uma quantidade enorme de mágica nos bastidores que pode ser frustrante.
Learn/migrating_to_v3
Migração para a v3
A compatibilidade retroativa foi mantida na maior parte, mas há algumas alterações das quais você deve estar ciente ao migrar da v2 para a v3. Há algumas mudanças que conflitaram demais com padrões de design, então alguns ajustes tiveram que ser feitos.
Comportamento de Buffer de Saída
v3.5.0
Output buffering é o processo onde a saída gerada por um script PHP é armazenada em um buffer (interno ao PHP) antes de ser enviada ao cliente. Isso permite que você modifique a saída antes de ela ser enviada ao cliente.
Em uma aplicação MVC, o Controller é o "gerenciador" e ele gerencia o que a view faz. Ter saída gerada fora do controller (ou no caso do Flight, às vezes uma função anônima) quebra o padrão MVC. Essa mudança é para estar mais alinhada com o padrão MVC e tornar o framework mais previsível e fácil de usar.
Na v2, o output buffering era tratado de uma forma onde não fechava consistentemente seu próprio buffer de saída, o que tornava testes unitários e streaming mais difíceis. Para a maioria dos usuários, essa mudança pode não afetá-lo de fato. No entanto, se você estiver ecoando conteúdo fora de callables e controllers (por exemplo, em um hook), você provavelmente vai encontrar problemas. Ecoar conteúdo em hooks, e antes do framework realmente executar, pode ter funcionado no passado, mas não funcionará daqui para frente.
Onde você pode ter problemas
// index.php
require 'vendor/autoload.php';
// apenas um exemplo
define('START_TIME', microtime(true));
function hello() {
echo 'Hello World';
}
Flight::map('hello', 'hello');
Flight::after('hello', function(){
// isso na verdade estará bem
echo '<p>This Hello World phrase was brought to you by the letter "H"</p>';
});
Flight::before('start', function(){
// coisas como essa causarão um erro
echo '<html><head><title>My Page</title></head><body>';
});
Flight::route('/', function(){
// isso na verdade está apenas bem
echo 'Hello World';
// Isso também deve estar bem
Flight::hello();
});
Flight::after('start', function(){
// isso causará um erro
echo '<div>Your page loaded in '.(microtime(true) - START_TIME).' seconds</div></body></html>';
});
Ativando o Comportamento de Renderização da v2
Você ainda pode manter seu código antigo como está sem fazer uma reescrita para torná-lo compatível com a v3? Sim, você pode! Você pode ativar o
comportamento de renderização da v2 definindo a opção de configuração flight.v2.output_buffering como true. Isso permitirá que você continue a
usar o antigo comportamento de renderização, mas é recomendado corrigi-lo daqui para frente. Na v4 do framework, isso será removido.
// index.php
require 'vendor/autoload.php';
Flight::set('flight.v2.output_buffering', true);
Flight::before('start', function(){
// Agora isso estará bem
echo '<html><head><title>My Page</title></head><body>';
});
// mais código
Mudanças no Dispatcher
v3.7.0
Se você tiver chamado diretamente 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. O Dispatcher foi convertido para ser mais orientado a objetos, de modo
que Contêineres de Injeção de Dependência possam ser usados de forma mais fácil. Se você precisar invocar um método de forma similar ao que o Dispatcher fazia, você
pode usar manualmente algo como $result = $class->$method(...$params); ou call_user_func_array() em vez disso.
Mudanças em halt() stop() redirect() e error()
v3.10.0
O comportamento padrão antes da 3.10.0 era limpar tanto os headers quanto o corpo da resposta. Isso foi alterado para limpar apenas o corpo da resposta.
Se você precisar limpar os headers também, você pode usar Flight::response()->clear().
Learn/configuration
Configuração
Visão Geral
O Flight fornece uma maneira simples de configurar vários aspectos do framework para atender às necessidades da sua aplicação. Alguns são definidos por padrão, mas você pode sobrescrevê-los conforme necessário. Você também pode definir suas próprias variáveis para serem usadas em toda a sua aplicação.
Configuração clara e em camadas (padrões de arquivo + segredos de ambiente) também ajuda ferramentas de codificação de IA: agentes aprendem um lugar para literais e um lugar para segredos, em vez de inventar leituras de $_ENV dentro dos controladores.
Entendimento
Você pode personalizar certos comportamentos do Flight definindo valores de configuração através do método set.
Flight::set('flight.log_errors', true);
Em uma aplicação estruturada (incluindo o skeleton), normalmente você carrega as configurações do projeto de app/config/config.php e então aplica as chaves relevantes ao Engine (por exemplo, flight.base_url, flight.views.path). Você também pode injetar um pequeno objeto de configuração nos controladores em vez de ler globais em todos os lugares — mais amigável para testes e para agentes que seguem AGENTS.md.
Uso Básico
Opções de Configuração do Flight
A seguir está uma lista de todas as configurações disponíveis:
- flight.base_url
?string- Substitui a URL base da requisição se o Flight estiver rodando em um subdiretório. (padrão: null) - flight.case_sensitive
bool- Correspondência sensível a maiúsculas/minúsculas para URLs. (padrão: false) - flight.handle_errors
bool- Permite que o Flight lide com todos os erros internamente. (padrão: true)- Se você quiser que o Flight lide com erros em vez do comportamento padrão do PHP, isso precisa ser true.
- Se você tiver o Tracy instalado, defina isso como false para que o Tracy possa lidar com erros.
- Se você tiver o plugin APM instalado, defina isso como true para que o APM possa registrar os erros.
- flight.log_errors
bool- Registra erros no arquivo de log de erros do servidor web. (padrão: false)- Se você tiver o Tracy instalado, o Tracy registrará erros com base nas configurações do Tracy, não nesta configuração.
- flight.debug
bool- Exibe informações detalhadas de erro (mensagem de exceção, código e rastreamento de pilha) no navegador quando um erro ocorre. (padrão: false)- Nunca habilite isso em produção — isso vaza detalhes internos da aplicação. Use apenas para desenvolvimento local ou staging.
- Quando
false, um genérico500 Internal Server Erroré exibido. Combine comflight.log_errorspara capturar erros no lado do servidor.
- flight.allow_method_override
bool- Permite que o método HTTP seja sobrescrito através do cabeçalho de requisiçãoX-HTTP-Method-Overrideou um campo_methodno corpo POST. (padrão: true)- Definir isso como
falseé recomendado para aplicações que não precisam de falsificação de método baseada em formulário HTML, pois impede que clientes forjem requisiçõesDELETEouPUTatravés de um formulário POST padrão. - Veja Segurança para mais detalhes.
- Definir isso como
- flight.views.path
string- Diretório contendo os arquivos de template de view. (padrão: ./views) - flight.views.extension
string- Extensão de arquivo de template de view. (padrão:.php; o skeleton oficial define isso como.twigao usar Twig) - flight.content_length
bool- Define o cabeçalhoContent-Length. (padrão: true)- Se você estiver usando Tracy, isso precisa ser definido como false para que o Tracy renderize corretamente.
- flight.v2.output_buffering
bool- Usa o buffer de saída legado. Veja migrando para v3. (padrão: false)
Configuração do Loader
Existe também outra configuração para o loader. Isso permitirá que você autocarregue classes com _ no nome da classe.
// Habilita o carregamento de classes com sublinhados
// Padrão é true
Loader::$v2ClassLoading = false;
Lembre-se de que o autoloading também depende das maiúsculas/minúsculas das pastas correspondentes aos seus namespaces — especialmente com o layout App\ + app/Controller/ do skeleton.
Configuração do projeto e .env (padrão do skeleton)
O núcleo do Flight não requer arquivos .env. Muitas aplicações usam apenas um array de configuração PHP. O skeleton oficial divide a configuração em camadas para que segredos fiquem fora do git enquanto o Runway ainda pode reescrever com segurança configurações literais:
.env/ ambiente real — segredos e sobrescritas de deploy (ignorados pelo git).app/config/config.php— padrões de array PHP literais (copiados deconfig_sample.php). Prefira sem expressões$_ENV[...]dentro deste arquivo: ferramentas comorunway config:setpodem reescrevê-lo com valores estáticos e podem gravar segredos no arquivo.- Mesclar na inicialização — o ambiente vence para chaves mapeadas; o código da aplicação lê um objeto de configuração ou
$app->get(), não$_ENVnos controladores.
Exemplo de formato de config_sample.php / config.php (simplificado):
<?php
// Apenas literais — segredos pertencem ao .env no fluxo do skeleton
return [
'app' => [
'env' => 'development',
'debug' => true,
'base_url' => '/',
'timezone' => 'UTC',
],
'database' => [
'driver' => 'sqlite', // ou mysql, ou '' para desativar
'host' => 'localhost',
'dbname' => '',
'user' => '',
'password' => '',
'file_path' => __DIR__ . '/../../database.sqlite',
],
// ...
];
# .env.example → .env (skeleton)
APP_ENV=development
APP_DEBUG=true
FLIGHT_BASE_URL=/
DB_DRIVER=sqlite
# DB_PASSWORD=...
Essa divisão é proposital para projetos amigáveis à IA: as instruções podem dizer "padrões em config.php, segredos em .env, injete Config / Engine — nunca invente acesso a env em um controlador". Aplicações existentes podem ignorar o .env completamente e manter um único arquivo de configuração.
Variáveis
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 da 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();
Nota: Só porque você pode definir uma variável não significa que deva. Use esse recurso com moderação. A razão é que tudo armazenado aqui se torna uma variável global. Variáveis globais são ruins porque podem ser alteradas de qualquer lugar na sua aplicação, dificultando rastrear bugs. Além disso, isso pode complicar coisas como testes unitários. Prefira injeção via construtor (como no skeleton + configuração Dice) para serviços e configurações que os controladores precisam.
Erros e Exceções
Todos os erros e exceções são capturados pelo Flight e passados para o método error se flight.handle_errors estiver definido como true.
O comportamento padrão é enviar uma resposta genérica HTTP 500 Internal Server Error com algumas informações de erro.
Você pode sobrescrever 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);
404 Não Encontrado
Quando uma URL não pode ser encontrada, o Flight chama o método notFound. O comportamento padrão é enviar uma resposta HTTP 404 Not Found com uma mensagem simples.
Você pode sobrescrever esse comportamento para suas próprias necessidades:
Flight::map('notFound', function () {
// Lidar com não encontrado
});
Veja Também
- Instalação - Configuração do skeleton,
.enve layout de inicialização. - Autoloading - Namespaces e maiúsculas/minúsculas de pastas.
- Estendendo o Flight - Como estender e personalizar a funcionalidade principal do Flight.
- Testes Unitários - Como escrever testes unitários para sua aplicação Flight.
- IA & Experiência do Desenvolvedor -
AGENTS.mde instruções consistentes do projeto. - Tracy - Um plugin para tratamento avançado de erros e depuração.
- Extensões Tracy - Extensões para integrar Tracy com Flight.
- APM - Um plugin para monitoramento de desempenho de aplicações e rastreamento de erros.
- Segurança - Flags de endurecimento e tratamento de segredos.
Solução de Problemas
- Se você estiver tendo problemas para descobrir todos os valores da sua configuração, você pode fazer
var_dump(Flight::get()); - Se o Runway ou a ferramenta de deploy reescreveu
config.php, confirme que os segredos não foram commitados — mantenha-os no.envou no ambiente real ao usar o padrão do skeleton.
Changelog
- Docs — Documentação do estilo skeleton de configuração / camadas
.enve extensão de view Twig padrão para novos projetos. - v3.18.1 - Adicionadas as opções de configuração
flight.debugeflight.allow_method_override. - v3.5.0 - Adicionada a configuração para
flight.v2.output_bufferingpara suportar o comportamento de buffer de saída legado. - v2.0 - Configurações principais adicionadas.
Learn/ai
IA e Experiência do Desenvolvedor com Flight
Visão geral
O Flight é projetado para trabalhar com ferramentas de codificação de IA—não contra elas. Uma API pequena e previsível, um layout de aplicação claro no esqueleto oficial e arquivos de instrução específicos do projeto fazem com que assistentes como GitHub Copilot, Cursor, Windsurf, Claude Code e Gemini possam seguir os mesmos padrões que você escreveria manualmente.
Com os comandos integrados do Runway para conectar a provedores de LLM e gerar instruções de projeto, o Flight ajuda você e sua equipe a obter ajuda consistente e relevante sem colar o mesmo contexto em cada chat.
Compreensão
Assistentes de codificação de IA são mais úteis quando entendem o contexto, as convenções e os objetivos do seu projeto. Os auxiliares de IA do Flight permitem:
- Conectar seu projeto a provedores populares de LLM (OpenAI, Grok, Claude, etc.)
- Gerar e atualizar instruções específicas do projeto para que todos recebam a mesma orientação
- Manter o código escrito à mão e o gerado por IA em um único layout (especialmente com o esqueleto)
Esses recursos vêm com o CLI principal do Flight (via Runway) e já estão configurados no iniciador oficial flightphp/skeleton.
O que o esqueleto oferece para IA
O iniciador oficial trata AGENTS.md como a fonte da verdade para ferramentas de IA:
| Arquivo | Função |
|---|---|
AGENTS.md (raiz do projeto) |
Regras globais, fluxo de inicialização, namespaces, DI, "o que não fazer" |
AGENTS.md com escopo em app/, migrations/, tests/, etc. |
Dicas leves e específicas da pasta ao trabalhar nessa árvore |
SECURITY.md |
Segredos, cabeçalhos, XSS/SQL, denúncia—a segurança permanece deliberada e separada |
Não há um arquivo de estilo separado para Copilot / Cursor / Gemini / Windsurf no esqueleto. Aponte seu assistente para o AGENTS.md raiz (e deixe-o seguir os links para os arquivos com escopo). Os humanos podem ignorar esses arquivos por completo e usar o README; o layout é o mesmo de qualquer forma.
A documentação ensina APIs; o esqueleto ensina layout. Exemplos curtos de
Flight::nestes documentos são ótimos para aprender. Em uma aplicação esqueleto, prefira classesApp\…, injeção de construtor e$this->appem vez da fachada estática dentro dos controladores. Consulte Instalação e Autoload.
Uso básico
Configurando credenciais de LLM
O comando ai:init orienta você na conexão do seu projeto a um provedor de LLM.
php runway ai:init
Você será solicitado a:
- Escolher seu provedor (OpenAI, Grok, Claude, etc.)
- Inserir sua chave de API
- Definir a URL base e o nome do modelo
Isso cria as credenciais usadas para solicitações posteriores de LLM (por exemplo, para gerar instruções).
Exemplo:
Bem-vindo ao AI Init!
Qual API de LLM você deseja usar? [1] openai, [2] grok, [3] claude: 1
Digite a URL base para a API de LLM [https://api.openai.com]:
Digite sua chave de API para openai: sk-...
Digite o nome do modelo que você deseja usar (ex.: gpt-4, claude-3-opus, etc.) [gpt-4o]:
Credenciais salvas em .runway-creds.json
Gerando instruções de IA específicas para o projeto
O comando ai:generate-instructions cria ou atualiza instruções para assistentes de codificação de IA, adaptadas ao seu projeto.
php runway ai:generate-instructions
Você responderá a algumas perguntas (descrição, banco de dados, templates, segurança, tamanho da equipe, etc.). O Flight usa seu provedor de LLM para gerar instruções e as escreve principalmente em:
AGENTS.mdna raiz do projeto (independente de ferramenta; o que o esqueleto oficial e a maioria dos agentes modernos esperam)
Dependendo da versão do CLI e das opções, o comando também pode escrever cópias específicas para fluxos de trabalho mais antigos (por exemplo, arquivos de regras para Copilot, Cursor, Windsurf ou Gemini). Para novos projetos a partir do esqueleto, trate AGENTS.md (além de quaisquer arquivos AGENTS.md com escopo que você mantenha em app/) como a fonte única da verdade—não mantenha cinco arquivos de instrução divergentes manualmente.
Exemplo:
Descreva para que serve seu projeto? Minha API incrível
Qual banco de dados você planeja usar? MySQL
Qual mecanismo de templates HTML você planeja usar (se houver)? twig
A segurança é um elemento importante deste projeto? (s/n) s
...
Instruções de IA atualizadas com sucesso.
Agora as ferramentas de IA podem sugerir código que corresponda à sua pilha e ao seu layout reais—não a um tutorial genérico de PHP.
Uso avançado
- Personalize credenciais ou caminhos de saída com opções de comando (veja
--helpem cada comando). - Os auxiliares funcionam com qualquer provedor de LLM que fale uma API compatível com OpenAI.
- Execute novamente
ai:generate-instructionsconforme o projeto evolui para manter os agentes sincronizados. - No esqueleto, mantenha a política de segurança em
SECURITY.mde o layout de codificação emAGENTS.mdpara que nenhum documento vire uma mistura de tudo. - Prefira docs.flightphp.com e o servidor MCP do Flight quando os agentes precisarem de detalhes da API; verifique métodos inventados em
vendor/flightphp/core.
Veja também
- Flight Skeleton – Iniciador oficial com
AGENTS.md, Twig, SimplePdo e Dice configurados para uma estrutura amigável à IA - Instalação – Layout recomendado com
create-project - Autoload – As maiúsculas/minúsculas das pastas correspondem aos namespaces (
App\Controller↔app/Controller/) - CLI Runway – CLI que alimenta os comandos
ai:*e de scaffolding - Segurança – Padrões seguros que agentes (e humanos) não devem enfraquecer
Solução de problemas
- Se aparecer "Missing .runway-creds.json", execute
php runway ai:initprimeiro. - Certifique-se de que sua chave de API seja válida e tenha acesso ao modelo selecionado.
- Se as instruções não estiverem sendo atualizadas, verifique as permissões de arquivo no diretório do seu projeto.
- Se um agente inventar APIs do Flight ou o layout de pastas errado, aponte-o para o
AGENTS.mdraiz e para este site de documentação; o layout do esqueleto vence para código emapp/.
Changelog
- v3.18.4 –
ai:generate-instructionsgrava as instruções do projeto emAGENTS.mdna raiz do projeto. - v3.16.0 – Adicionados os comandos de CLI
ai:initeai:generate-instructionspara integração com IA.
Learn/unit_testing_and_solid_principles
Este artigo foi originalmente publicado no Airpair em 2015. Todo o crédito é dado ao Airpair e a Brian Fenton, que originalmente escreveu este artigo, embora o site não esteja mais disponível e o artigo só exista dentro da Wayback Machine. Este artigo foi adicionado ao site para fins de aprendizado e educacionais para a comunidade PHP em geral.
1 Configuração e instalação
1.1 Mantenha atualizado
Vamos destacar isso desde o início - um número deprimentemente pequeno de instalações de PHP no mundo real está atualizado ou mantido atualizado. Seja devido a restrições de hospedagem compartilhada, padrões que ninguém pensa em alterar, ou falta de tempo/orçamento para testes de atualização, os humildes binários do PHP tendem a ficar para trás. Então, uma prática recomendada clara que precisa de mais ênfase é sempre usar uma versão atual do PHP (5.6.x conforme este artigo). Além disso, é importante agendar atualizações regulares tanto do PHP quanto de quaisquer extensões ou bibliotecas de fornecedores que você possa estar usando. As atualizações trazem novos recursos de linguagem, velocidade aprimorada, menor uso de memória e atualizações de segurança. Quanto mais frequentemente você atualizar, menos doloroso o processo se torna.
1.2 Defina padrões sensíveis
O PHP faz um trabalho decente ao definir bons padrões fora da caixa com seus arquivos php.ini.development e php.ini.production, mas podemos fazer melhor. Por exemplo, eles não definem uma data/horário para nós. Isso faz sentido do ponto de vista de distribuição, mas sem um, o PHP lançará um erro E_WARNING toda vez que chamarmos uma função relacionada a data/hora. Aqui estão algumas configurações recomendadas:
- date.timezone - escolha da lista de fusos horários suportados
- session.savepath - se estivermos usando arquivos para sessões e não algum outro manipulador de salvamento, defina isso para algo fora de /tmp. Deixar isso como /tmp pode ser arriscado em um ambiente de hospedagem compartilhada, pois /tmp_ geralmente tem permissões amplamente abertas. Mesmo com o bit sticky definido, qualquer um com acesso para listar o conteúdo desse diretório pode aprender todos os seus IDs de sessão ativos.
- session.cookie_secure - algo óbvio, ative isso se você estiver servindo seu código PHP por HTTPS.
- session.cookie_httponly - defina isso para impedir que cookies de sessão do PHP sejam acessíveis via JavaScript
- Mais... use uma ferramenta como iniscan para testar sua configuração quanto a vulnerabilidades comuns
1.3 Extensões
Também é uma boa ideia desabilitar (ou pelo menos não habilitar) extensões que você não vai usar, como drivers de banco de dados. Para ver o que está habilitado, execute o comando phpinfo() ou vá para uma linha de comando e execute isso.
$ php -i
As informações são as mesmas, mas phpinfo() adiciona formatação HTML. A versão CLI é mais fácil de redirecionar para grep para encontrar informações específicas. Ex.
$ php -i | grep error_log
Uma ressalva desse método: é possível ter configurações diferentes do PHP aplicadas à versão voltada para a web e à versão CLI.
2 Use Composer
Isso pode surpreender, mas uma das melhores práticas para escrever PHP moderno é escrever menos dele. Embora seja verdade que uma das melhores maneiras de se tornar bom em programação é fazer isso, há um grande número de problemas que já foram resolvidos no espaço PHP, como roteamento, bibliotecas básicas de validação de entrada, conversão de unidades, camadas de abstração de banco de dados, etc... Basta ir para Packagist e navegar. Você provavelmente descobrirá que partes significativas do problema que você está tentando resolver já foram escritas e testadas.
Embora seja tentador escrever todo o código você mesmo (e não há nada de errado em escrever seu próprio framework ou biblioteca como uma experiência de aprendizado), você deve lutar contra esses sentimentos de "Não Inventado Aqui" e se poupar de muito tempo e dor de cabeça. Siga a doutrina do PIE em vez disso - Orgulhosamente Inventado Em Outro Lugar. Além disso, se você escolher escrever seu próprio algo, não o libere a menos que ele faça algo significativamente diferente ou melhor do que as ofertas existentes.
Composer é um gerenciador de pacotes para PHP, semelhante ao pip no Python, gem no Ruby e npm no Node. Ele permite que você defina um arquivo JSON que lista as dependências do seu código e tentará resolver esses requisitos para você, baixando e instalando os pacotes de código necessários.
2.1 Instalando Composer
Estamos assumindo que isso é um projeto local, então vamos instalar uma instância do Composer apenas para o projeto atual. Navegue para o diretório do seu projeto e execute isso:
$ curl -sS https://getcomposer.org/installer | php
Lembre-se de que direcionar qualquer download diretamente para um interpretador de script (sh, ruby, php, etc...) é um risco de segurança, então leia o código de instalação e garanta que você esteja confortável com ele antes de executar qualquer comando como esse.
Por conveniência (se você preferir digitar composer install em vez de php composer.phar install), você pode usar este comando para instalar uma cópia única do composer globalmente:
$ mv composer.phar /usr/local/bin/composer
$ chmod +x composer
Você pode precisar executar esses com sudo, dependendo das suas permissões de arquivo.
2.2 Usando Composer
O Composer tem duas categorias principais de dependências que ele pode gerenciar: "require" e "require-dev". Dependências listadas como "require" são instaladas em todos os lugares, mas dependências "require-dev" são instaladas apenas quando solicitadas especificamente. Geralmente, essas são ferramentas para quando o código está em desenvolvimento ativo, como PHP_CodeSniffer. A linha abaixo mostra um exemplo de como instalar Guzzle, uma biblioteca HTTP popular.
$ php composer.phar require guzzle/guzzle
Para instalar uma ferramenta apenas para fins de desenvolvimento, adicione a flag --dev:
$ php composer.phar require --dev 'sebastian/phpcpd'
Isso instala PHP Copy-Paste Detector, outra ferramenta de qualidade de código como uma dependência apenas para desenvolvimento.
2.3 Install vs update
Quando executamos composer install pela primeira vez, ele instalará quaisquer bibliotecas e suas dependências de que precisamos, com base no arquivo composer.json. Quando isso é feito, o composer cria um arquivo de bloqueio, previsivelmente chamado composer.lock. Esse arquivo contém uma lista das dependências que o composer encontrou para nós e suas versões exatas, com hashes. Então, qualquer vez futura que executarmos composer install, ele olhará no arquivo de bloqueio e instalará aquelas versões exatas.
composer update é um pouco diferente. Ele ignorará o arquivo composer.lock (se presente) e tentará encontrar as versões mais atualizadas de cada uma das dependências que ainda satisfazem as restrições em composer.json. Ele então escreve um novo composer.lock quando terminar.
2.4 Autoload
Tanto o composer install quanto o composer update gerarão um autoloader para nós que diz ao PHP onde encontrar todos os arquivos necessários para usar as bibliotecas que acabamos de instalar. Para usá-lo, basta adicionar esta linha (geralmente a um arquivo de bootstrap que é executado em cada solicitação):
require 'vendor/autoload.php';
3 Siga bons princípios de design
3.1 SOLID
SOLID é um mnemônico para nos lembrar de cinco princípios-chave no bom design de software orientado a objetos.
3.1.1 S - Princípio da Responsabilidade Única
Isso afirma que as classes devem ter apenas uma responsabilidade, ou dito de outra forma, elas devem ter apenas um motivo para mudar. Isso se encaixa bem com a filosofia Unix de muitas ferramentas pequenas, fazendo uma coisa bem. Classes que fazem apenas uma coisa são muito mais fáceis de testar e depurar, e menos propensas a surpreendê-lo. Você não quer que uma chamada de método para uma classe Validator atualize registros no banco de dados. Aqui está um exemplo de violação de SRP, do tipo que você veria comumente em um aplicativo baseado no padrão ActiveRecord.
class Person extends Model
{
public $name;
public $birthDate;
protected $preferences;
public function getPreferences() {}
public function save() {}
}
Então isso é um modelo de entidade bastante básico. No entanto, uma dessas coisas não pertence aqui. A única responsabilidade de um modelo de entidade deve ser o comportamento relacionado à entidade que ele representa; ele não deve ser responsável por persistir a si mesmo.
class Person extends Model
{
public $name;
public $birthDate;
protected $preferences;
public function getPreferences() {}
}
class DataStore
{
public function save(Model $model) {}
}
Isso é melhor. O modelo Person está de volta a fazer apenas uma coisa, e o comportamento de salvamento foi movido para um objeto de persistência. Observe também que eu só adicionei um type hint em Model, não em Person. Voltaremos a isso quando chegarmos às partes L e D do SOLID.
3.1.2 O - Princípio Aberto/Fechado
Há um teste incrível para isso que resume bem o que esse princípio é: pense em um recurso para implementar, provavelmente o mais recente que você trabalhou ou está trabalhando. Você pode implementar esse recurso no seu código existente APENAS adicionando novas classes e não alterando nenhuma classe existente no seu sistema? Sua configuração e código de fiação recebem um pouco de perdão, mas na maioria dos sistemas isso é surpreendentemente difícil. Você tem que depender muito de despacho polimórfico e a maioria dos códigos não está configurada para isso. Se você estiver interessado nisso, há uma boa palestra do Google no YouTube sobre polimorfismo e escrever código sem Ifs que aprofunda isso. Como bônus, a palestra é dada por Miško Hevery, que muitos podem conhecer como o criador do AngularJs.
3.1.3 L - Princípio da Substituição de Liskov
Esse princípio é nomeado para Barbara Liskov e é impresso abaixo:
"Objetos em um programa devem ser substituíveis por instâncias de seus subtipos sem alterar a correção desse programa."
Isso parece bom, mas é mais claramente ilustrado com um exemplo.
abstract class Shape
{
public function getHeight();
public function setHeight($height);
public function getLength();
public function setLength($length);
}
Isso vai representar nossa forma básica de quatro lados. Nada chique aqui.
class Square extends Shape
{
protected $size;
public function getHeight() {
return $this->size;
}
public function setHeight($height) {
$this->size = $height;
}
public function getLength() {
return $this->size;
}
public function setLength($length) {
$this->size = $length;
}
}
Aqui está nossa primeira forma, o Quadrado. Uma forma bem direta, certo? Você pode assumir que há um construtor onde definimos as dimensões, mas você vê aqui desta implementação que o comprimento e a altura sempre serão os mesmos. Quadrados são assim.
class Rectangle extends Shape
{
protected $height;
protected $length;
public function getHeight() {
return $this->height;
}
public function setHeight($height) {
$this->height = $height;
}
public function getLength() {
return $this->length;
}
public function setLength($length) {
$this->length = $length;
}
}
Então aqui temos uma forma diferente. Ainda tem as mesmas assinaturas de método, ainda é uma forma de quatro lados, mas e se começarmos a tentar usá-las no lugar uma da outra? Agora, de repente, se mudarmos a altura da nossa Shape, não podemos mais assumir que o comprimento da nossa forma corresponderá. Violamos o contrato que tínhamos com o usuário quando demos a eles nossa forma Quadrado.
Isso é um exemplo clássico de violação do LSP e precisamos desse tipo de princípio para fazer o melhor uso de um sistema de tipos. Até mesmo o duck typing não nos dirá se o comportamento subjacente é diferente, e como não podemos saber isso sem vê-lo quebrar, é melhor garantir que não seja diferente em primeiro lugar.
3.1.3 I - Princípio da Segregação de Interface
Esse princípio diz para favorecer muitas interfaces pequenas e refinadas em vez de uma grande. Interfaces devem ser baseadas em comportamento em vez de "é uma dessas classes". Pense nas interfaces que vêm com o PHP. Traversable, Countable, Serializable, coisas assim. Elas anunciam capacidades que o objeto possui, não o que ele herda. Então, mantenha suas interfaces pequenas. Você não quer uma interface com 30 métodos, 3 é uma meta muito melhor.
3.1.4 D - Princípio da Inversão de Dependência
Você provavelmente ouviu falar disso em outros lugares que falaram sobre Injeção de Dependência, mas Inversão de Dependência e Injeção de Dependência não são exatamente a mesma coisa. Inversão de dependência é realmente apenas uma forma de dizer que você deve depender de abstrações no seu sistema e não dos seus detalhes. O que isso significa para você no dia a dia?
Não use diretamente mysqli_query() em todo o seu código, use algo como DataStore->query() em vez disso.
O cerne desse princípio é sobre abstrações. É mais sobre dizer "use um adaptador de banco de dados" em vez de depender de chamadas diretas a coisas como mysqli_query. Se você está usando mysqli_query diretamente em metade das suas classes, você está amarrando tudo diretamente ao seu banco de dados. Nada contra o MySQL aqui, mas se você está usando mysqli_query, esse tipo de detalhe de baixo nível deve ser escondido em apenas um lugar e então essa funcionalidade deve ser exposta via um wrapper genérico.
Agora eu sei que isso é um exemplo um pouco batido se você pensar sobre isso, porque o número de vezes que você vai realmente mudar completamente o motor do seu banco de dados após o produto estar em produção é muito, muito baixo. Eu escolhi isso porque imaginei que as pessoas estariam familiarizadas com a ideia do seu próprio código. Além disso, mesmo se você tiver um banco de dados que sabe que vai manter, esse objeto wrapper abstrato permite que você corrija bugs, mude o comportamento ou implemente recursos que você deseja que o seu banco de dados escolhido tivesse. Ele também torna o teste unitário possível onde chamadas de baixo nível não o fariam.
4 Calistenia de objetos
Isso não é um mergulho completo nesses princípios, mas os dois primeiros são fáceis de lembrar, fornecem bom valor e podem ser aplicados imediatamente a praticamente qualquer base de código.
4.1 Não mais que um nível de indentação por método
Isso é uma forma útil de pensar sobre decompor métodos em pedaços menores, deixando você com código que é mais claro e autodocumentado. Quanto mais níveis de indentação você tiver, mais o método está fazendo e mais estado você tem que rastrear na sua cabeça enquanto trabalha com ele.
Logo de cara eu sei que as pessoas vão objetar a isso, mas isso é apenas uma diretriz/heurística, não uma regra rígida e rápida. Eu não espero que ninguém imponha regras do PHP_CodeSniffer para isso (embora pessoas tenham).
Vamos passar por uma amostra rápida do que isso pode parecer:
public function transformToCsv($data)
{
$csvLines = array();
$csvLines[] = implode(',', array_keys($data[0]));
foreach ($data as $row) {
if (!$row) {
continue;
}
$csvLines[] = implode(',', $row);
}
return $csvLines;
}
Embora isso não seja um código terrível (é tecnicamente correto, testável, etc...), podemos fazer muito mais para torná-lo claro. Como reduzir os níveis de aninhamento aqui?
Sabemos que precisamos simplificar muito o conteúdo do loop foreach (ou removê-lo completamente), então vamos começar aí.
if (!$row) {
continue;
}
Essa primeira parte é fácil. Tudo o que isso está fazendo é ignorar linhas vazias. Podemos encurtar esse processo inteiro usando uma função integrada do PHP antes de chegarmos ao loop.
$data = array_filter($data);
foreach ($data as $row) {
$csvLines[] = implode(',', $row);
}
Agora temos nosso único nível de aninhamento. Mas olhando para isso, tudo o que estamos fazendo é aplicar uma função a cada item em um array. Nós nem precisamos do loop foreach para fazer isso.
$data = array_filter($data);
$csvLines = array_map(function($row) {
return implode(',', $row);
}, $data);
Agora não temos aninhamento algum, e o código provavelmente será mais rápido, pois estamos fazendo todo o looping com funções nativas em C em vez de PHP. Temos que nos envolver um pouco para passar a vírgula para implode, então você poderia argumentar que parar no passo anterior é muito mais compreensível.
4.2 Tente não usar else
Isso realmente lida com duas ideias principais. A primeira é múltiplos statements de return de um método. Se você tiver informações suficientes para tomar uma decisão sobre o resultado do método, vá em frente e tome essa decisão e retorne. A segunda é uma ideia conhecida como Guard Clauses. Essas são basicamente verificações de validação combinadas com retornos iniciais, geralmente perto do topo de um método. Deixe-me mostrar o que quero dizer.
public function addThreeInts($first, $second, $third) {
if (is_int($first)) {
if (is_int($second)) {
if (is_int($third)) {
$sum = $first + $second + $third;
} else {
return null;
}
} else {
return null;
}
} else {
return null;
}
return $sum;
}
Então isso é bem direto novamente, ele adiciona 3 ints juntos e retorna o resultado, ou null se qualquer dos parâmetros não for um inteiro. Ignorando o fato de que podemos combinar todas essas verificações em uma única linha com operadores AND, acho que você pode ver como a estrutura if/else aninhada torna o código mais difícil de seguir. Agora olhe para este exemplo em vez disso.
public function addThreeInts($first, $second, $third) {
if (!is_int($first)) {
return null;
}
if (!is_int($second)) {
return null;
}
if (!is_int($third)) {
return null;
}
return $first + $second + $third;
}
Para mim, esse exemplo é muito mais fácil de seguir. Aqui estamos usando cláusulas de guarda para verificar nossas asserções iniciais sobre os parâmetros que estamos passando e saindo imediatamente do método se eles não passarem. Também não temos mais a variável intermediária para rastrear a soma o caminho todo pelo método. Nesse caso, verificamos que já estamos no caminho feliz e podemos apenas fazer o que viemos aqui para fazer. Novamente, podemos fazer todas essas verificações em um único if, mas o princípio deve estar claro.
5 Testes unitários
Testes unitários é a prática de escrever testes pequenos que verificam o comportamento no seu código. Eles são quase sempre escritos na mesma linguagem que o código (nesse caso PHP) e são destinados a ser rápidos o suficiente para rodar a qualquer momento. Eles são extremamente valiosos como uma ferramenta para melhorar o seu código. Além dos benefícios óbvios de garantir que o seu código esteja fazendo o que você acha que está, testes unitários podem fornecer feedback de design muito útil também. Se um pedaço de código é difícil de testar, isso frequentemente destaca problemas de design. Eles também dão a você uma rede de segurança contra regressões e isso permite que você refatore muito mais frequentemente e evolua o seu código para um design mais limpo.
5.1 Ferramentas
Há várias ferramentas de testes unitários por aí no PHP, mas de longe a mais comum é PHPUnit. Você pode instalá-lo baixando um PHAR diretamente, ou instalá-lo com composer. Como estamos usando composer para tudo mais, vamos mostrar esse método. Além disso, como o PHPUnit provavelmente não vai ser implantado para produção, podemos instalá-lo como uma dependência de desenvolvimento com o seguinte comando:
composer require --dev phpunit/phpunit
5.2 Testes são uma especificação
O papel mais importante dos testes unitários no seu código é fornecer uma especificação executável do que o código é suposto fazer. Mesmo se o código de teste estiver errado ou o código tiver bugs, o conhecimento do que o sistema é suposto fazer é inestimável.
5.3 Escreva seus testes primeiro
Se você teve a chance de ver um conjunto de testes escrito antes do código e um escrito após o código ter sido finalizado, eles são notavelmente diferentes. Os testes "após" são muito mais preocupados com os detalhes de implementação da classe e garantindo que tenham boa cobertura de linhas, enquanto os testes "antes" são mais sobre verificar o comportamento externo desejado. Isso é realmente o que nos importa com testes unitários de qualquer maneira, é garantir que a classe exiba o comportamento certo. Testes focados na implementação na verdade tornam o refatoramento mais difícil porque eles quebram se os internos das classes mudarem e você acabou de se custar os benefícios de ocultação de informação da POO.
5.4 O que faz um bom teste unitário
Bons testes unitários compartilham muitas das seguintes características:
- Rápidos - devem rodar em milissegundos.
- Sem acesso à rede - devem ser capazes de desligar o wireless/desconectar e todos os testes ainda passarem.
- Acesso limitado ao sistema de arquivos - isso adiciona à velocidade e flexibilidade se implantando código para outros ambientes.
- Sem acesso ao banco de dados - evita atividades custosas de configuração e desmontagem.
- Teste apenas uma coisa de cada vez - um teste unitário deve ter apenas um motivo para falhar.
- Bem nomeados - veja 5.2 acima.
- Principalmente objetos falsos - os únicos "reais" objetos em testes unitários devem ser o objeto que estamos testando e objetos de valor simples. O resto deve ser alguma forma de test double
Há razões para ir contra algumas dessas, mas como diretrizes gerais elas vão servir bem a você.
5.5 Quando testar é doloroso
Testes unitários forçam você a sentir a dor do mau design no início - Michael Feathers
Quando você está escrevendo testes unitários, você está forçando a si mesmo a realmente usar a classe para realizar coisas. Se você escrever testes no final, ou pior, apenas jogar o código por cima do muro para QA ou quem quer que escreva testes, você não obtém nenhum feedback sobre como a classe realmente se comporta. Se estamos escrevendo testes e a classe é um saco de usar, vamos descobrir enquanto estamos escrevendo, o que é quase o momento mais barato para consertar.
Se uma classe é difícil de testar, é um defeito de design. Diferentes defeitos se manifestam de maneiras diferentes, no entanto. Se você tiver que fazer um monte de mocking, sua classe provavelmente tem muitas dependências ou seus métodos estão fazendo muito. Quanto mais configuração você tiver que fazer para cada teste, mais provável é que seus métodos estejam fazendo muito. Se você tiver que escrever cenários de teste realmente complicados para exercer o comportamento, os métodos da classe provavelmente estão fazendo muito. Se você tiver que cavar dentro de um monte de métodos privados e estado para testar coisas, talvez haja outra classe tentando sair. Testes unitários são muito bons em expor "classes iceberg" onde 80% do que a classe faz está escondido em código protegido ou privado. Eu costumava ser um grande fã de tornar o máximo possível protegido, mas agora percebi que eu estava apenas tornando minhas classes individuais responsáveis por muito, e a solução real era quebrar a classe em pedaços menores.
Escrito por Brian Fenton - Brian Fenton tem sido um desenvolvedor PHP por 8 anos no Meio-Oeste e na Baía, atualmente na Thismoment. Ele se concentra em artesanato de código e princípios de design. Blog em www.brianfenton.us, Twitter em @brianfenton. Quando ele não está ocupado sendo pai, ele gosta de comida, cerveja, jogos e aprendizado.
Learn/security
Segurança
Visão Geral
Segurança é um assunto importante quando se trata de aplicações web. Você quer ter certeza de que sua aplicação seja segura e que os dados dos seus usuários estejam protegidos. O Flight fornece uma série de recursos para ajudar você a proteger suas aplicações web.
O skeleton oficial também inclui um SECURITY.md dedicado e middleware de cabeçalhos de segurança para que ferramentas de codificação por IA (e humanos) tenham um lugar deliberado para segredos, cabeçalhos e regras de XSS/SQL — separado do estilo geral de codificação em AGENTS.md.
Entendimento
Existem várias ameaças de segurança comuns que você deve conhecer ao construir aplicações web. Algumas das ameaças mais comuns incluem:
- Falsificação de Solicitação entre Sites (CSRF)
- Script entre Sites (XSS)
- Injeção de SQL
- Compartilhamento de Recursos entre Origens (CORS)
Templates ajudam com XSS ao escapar a saída por padrão (Twig e Latte fazem isso; aproveite essa vantagem). Sessions podem ajudar com CSRF armazenando um token CSRF na sessão do usuário, como descrito abaixo. Usar prepared statements com PDO — ou auxiliares no SimplePdo — ajuda a prevenir injeção de SQL. CORS pode ser tratado com um hook simples antes de Flight::start() ser chamado.
Todos esses métodos trabalham juntos para ajudar a manter suas aplicações web seguras. Deve estar sempre em primeiro lugar na sua mente aprender e entender as boas práticas de segurança. Não peça a um assistente de IA para "desabilitar CSP" ou enfraquecer cabeçalhos apenas para fazer uma página carregar sem entender a compensação.
Uso Básico
Cabeçalhos
Cabeçalhos HTTP são uma das maneiras mais fáceis de proteger suas aplicações web. Você pode usar cabeçalhos para prevenir 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 dos seus cabeçalhos são securityheaders.com e observatory.mozilla.org. Depois de configurar o código abaixo, você pode facilmente verificar se seus cabeçalhos estão funcionando nesses dois sites.
O skeleton inclui App\Middleware\SecurityHeadersMiddleware (CSP com nonce por requisição, frame options, HSTS e mais). Prefira estender isso deliberadamente em vez de desativar cabeçalhos.
Adicionar Manualmente
Você pode adicionar esses cabeçalhos manualmente usando o método header no objeto Flight\Response.
// Define o cabeçalho X-Frame-Options para prevenir clickjacking
Flight::response()->header('X-Frame-Options', 'SAMEORIGIN');
// Define o cabeçalho Content-Security-Policy para prevenir XSS
// Nota: este cabeçalho pode ficar bastante complexo, então você vai querer
// consultar exemplos na internet para a sua aplicação
Flight::response()->header("Content-Security-Policy", "default-src 'self'");
// Define o cabeçalho X-XSS-Protection para prevenir XSS
Flight::response()->header('X-XSS-Protection', '1; mode=block');
// Define o cabeçalho X-Content-Type-Options para prevenir detecção de MIME (MIME sniffing)
Flight::response()->header('X-Content-Type-Options', 'nosniff');
// Define o cabeçalho Referrer-Policy para controlar quanta informação de referência é enviada
Flight::response()->header('Referrer-Policy', 'no-referrer-when-downgrade');
// Define o cabeçalho Strict-Transport-Security para forçar HTTPS
Flight::response()->header('Strict-Transport-Security', 'max-age=31536000; includeSubDomains; preload');
// Define 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 topo dos seus arquivos routes.php ou index.php.
Adicionar como um Filtro
Você também pode adicioná-los em um filtro/hook como o seguinte:
// 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=()');
});
Adicionar como um Middleware
Você também pode adicioná-los como uma classe de middleware, o que oferece a maior flexibilidade para quais rotas aplicar isso. Em geral, esses cabeçalhos devem ser aplicados a todas as respostas HTML e de API.
Caminho e namespace no estilo do skeleton (a pasta deve corresponder a App\Middleware):
// app/Middleware/SecurityHeadersMiddleware.php
namespace App\Middleware;
use flight\Engine;
class SecurityHeadersMiddleware
{
protected Engine $app;
public function __construct(Engine $app)
{
$this->app = $app;
}
public function before(array $params): void
{
$response = $this->app->response();
// Prefira um nonce CSP do bootstrap quando você tiver scripts inline (o skeleton define csp_nonce)
$nonce = $this->app->get('csp_nonce');
$csp = $nonce
? "default-src 'self'; script-src 'self' 'nonce-{$nonce}'; style-src 'self' 'nonce-{$nonce}'"
: "default-src 'self'";
$response->header('X-Frame-Options', 'SAMEORIGIN');
$response->header('Content-Security-Policy', $csp);
$response->header('X-XSS-Protection', '1; mode=block');
$response->header('X-Content-Type-Options', 'nosniff');
$response->header('Referrer-Policy', 'no-referrer-when-downgrade');
$response->header('Strict-Transport-Security', 'max-age=31536000; includeSubDomains; preload');
$response->header('Permissions-Policy', 'geolocation=()');
}
}
// app/config/routes.php — grupo de string vazia = middleware global para todas as rotas
use App\Middleware\SecurityHeadersMiddleware;
use flight\net\Router;
$router->group('', function (Router $router) {
$router->get('/users', [ \App\Controller\UserController::class, 'getUsers' ]);
// mais rotas
}, [SecurityHeadersMiddleware::class]);
Projetos mais antigos ainda podem usar app/middlewares e app\middlewares; isso funciona se as pastas corresponderem. Novos aplicativos skeleton usam app/Middleware/ e App\Middleware. Veja Autoloading.
Falsificação de Solicitação entre Sites (CSRF)
Falsificação de Solicitação entre Sites (CSRF) é um tipo de ataque em que um site malicioso pode fazer o navegador do usuário enviar uma solicitação para o seu site. Isso pode ser usado para executar ações no seu site sem o conhecimento do usuário. O Flight não fornece um mecanismo de proteção CSRF embutido, mas você pode implementar facilmente o seu próprio usando middleware.
Configuração
Primeiro, você precisa gerar um token CSRF e armazená-lo na sessão do usuário. Você pode usar esse token nos seus formulários e verificá-lo quando o formulário for enviado. Vamos usar o plugin flightphp/session para gerenciar sessões.
// Gera um token CSRF e o armazena na sessão do usuário
// (supondo que você criou um objeto de sessão e o anexou ao Flight)
// veja a documentação da sessão para mais informações
Flight::register('session', flight\Session::class);
// Você só precisa gerar um único token por sessão (para que funcione
// em várias abas e requisições para o mesmo usuário)
if(Flight::session()->get('csrf_token') === null) {
Flight::session()->set('csrf_token', bin2hex(random_bytes(32)) );
}
Usando o Template Padrão do PHP no Flight
<!-- Use o token CSRF no seu formulário -->
<form method="post">
<input type="hidden" name="csrf_token" value="<?= Flight::session()->get('csrf_token') ?>">
<!-- outros campos do formulário -->
</form>
Usando Twig (padrão do skeleton)
Registre uma função do Twig ou passe o token para cada visualização de formulário. Exemplo mínimo com global + campo de formulário:
// Ao configurar o Twig (ex.: services.php)
$twig->addGlobal('csrf_token', $app->session()->get('csrf_token'));
{# app/views/form.twig #}
<form method="post">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
{# outros campos #}
</form>
Usando Latte
Você também pode configurar uma função personalizada para exibir o token CSRF nos seus templates Latte.
Flight::map('render', function(string $template, array $data, ?string $block): void {
$latte = new Latte\Engine;
// outras configurações...
// Define uma função personalizada para exibir o token CSRF
$latte->addFunction('csrf', function() {
$csrfToken = Flight::session()->get('csrf_token');
return new \Latte\Runtime\Html('<input type="hidden" name="csrf_token" value="' . $csrfToken . '">');
});
$latte->render($finalPath, $data, $block);
});
E agora, nos seus templates Latte, você pode usar a função csrf() para exibir o token CSRF.
<form method="post">
{csrf()}
<!-- outros campos do formulário -->
</form>
Verificando o Token CSRF
Você pode verificar o token CSRF usando vários métodos.
Middleware
// app/Middleware/CsrfMiddleware.php
namespace App\Middleware;
use flight\Engine;
class CsrfMiddleware
{
protected Engine $app;
public function __construct(Engine $app)
{
$this->app = $app;
}
public function before(array $params): void
{
if($this->app->request()->method == 'POST') {
$token = $this->app->request()->data->csrf_token;
if($token !== $this->app->session()->get('csrf_token')) {
$this->app->halt(403, 'Invalid CSRF token');
}
}
}
}
// routes.php
use App\Middleware\CsrfMiddleware;
$router->group('', function ($router) {
$router->get('/users', [ \App\Controller\UserController::class, 'getUsers' ]);
// mais rotas
}, [CsrfMiddleware::class]);
Filtros de Evento
// Este middleware verifica se a requisição é POST e, se for, verifica se o token CSRF é válido
Flight::before('start', function() {
if(Flight::request()->method == 'POST') {
// captura o token CSRF dos valores do formulário
$token = Flight::request()->data->csrf_token;
if($token !== Flight::session()->get('csrf_token')) {
Flight::halt(403, 'Invalid CSRF token');
// ou para uma resposta JSON
Flight::jsonHalt(['error' => 'Invalid CSRF token'], 403);
}
}
});
Script entre Sites (XSS)
Script entre Sites (XSS) é um tipo de ataque em que uma entrada de formulário maliciosa pode injetar código no seu site. A maioria dessas oportunidades vem de valores de formulários que seus usuários finais preencherão. Você nunca deve confiar na saída dos seus usuários! Sempre assuma que todos eles são os melhores hackers do mundo. Eles podem injetar JavaScript ou HTML malicioso na sua página. Esse código pode ser usado para roubar informações dos seus usuários ou executar ações no seu site. Usando a classe de visualização do Flight ou um mecanismo de templates como Twig ou Latte, você pode facilmente escapar a saída para prevenir ataques XSS.
// Vamos supor que o usuário é esperto e tenta usar isso como nome
$name = '<script>alert("XSS")</script>';
// Isso escapará a saída
Flight::view()->set('name', $name);
// Isso gerará: <script>alert("XSS")</script>
// Twig (padrão do skeleton) e Latte escapam automaticamente por padrão — prefira-os ao echo PHP cru
Flight::render('template', ['name' => $name]);
// Twig: {{ name }} → escapado
// Evite saída |raw / sem escape, a menos que o conteúdo seja totalmente confiável
Injeção de SQL
Injeção de SQL é um tipo de ataque em que um usuário malicioso pode injetar código SQL no seu banco de dados. Isso pode ser usado para roubar informações do seu banco de dados ou executar ações no seu banco. Novamente, você nunca deve confiar na entrada dos seus usuários! Sempre assuma que eles estão em busca de causar danos. Use prepared statements — os auxiliares do SimplePdo tornam esse o caminho padrão.
// Supondo que você tenha Flight::db() registrado como SimplePdo (ou injete SimplePdo no controlador)
$statement = Flight::db()->prepare('SELECT * FROM users WHERE username = :username');
$statement->execute([':username' => $username]);
$users = $statement->fetchAll();
// SimplePdo (preferido) — linhas únicas com parâmetros vinculados
$users = Flight::db()->fetchAll('SELECT * FROM users WHERE username = :username', [ 'username' => $username ]);
// Mesma ideia com placeholders ?
$users = Flight::db()->fetchAll('SELECT * FROM users WHERE username = ?', [ $username ]);
Em controladores no estilo skeleton, prefira injeção de SimplePdo no construtor em vez de Flight::db() para que testes e código gerado por IA permaneçam consistentes (DIC).
Exemplo Inseguro
Abaixo está o motivo pelo qual usamos prepared statements para nos proteger de exemplos inocentes como o seguinte:
// o usuário final preenche um formulário web.
// para o valor do formulário, o hacker insere algo assim:
$username = "' OR 1=1; -- ";
$sql = "SELECT * FROM users WHERE username = '$username' LIMIT 5";
$users = Flight::db()->fetchAll($sql);
// Depois que a consulta é construída, fica assim
// SELECT * FROM users WHERE username = '' 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.
var_dump($users); // isso exibirá todos os usuários no banco de dados, não apenas o único nome de usuário
Segredos e configuração
- Coloque segredos em
.env(ou no ambiente real), não em exemplos commitados deconfig.php. - Regra do skeleton: valores padrão literais em
config.php; mescle o ambiente no bootstrap; não leia$_ENVdentro dos controladores — injete a configuração em vez disso. Veja Configuration. - Nunca envie para o repositório chaves de API, senhas de banco de dados ou chaves de criptografia de sessão. Aponte as ferramentas de IA para
SECURITY.mdpara que elas não inventem atalhos inseguros.
Validação de Callback JSONP
Se você usar o método Flight::jsonp() do Flight, esteja ciente de que o Flight valida o nome do parâmetro de callback JSONP contra uma lista de permissões estrita com regex (/^[A-Za-z_$][\w$.]{0,127}$/). Qualquer nome de callback que não corresponda a esse padrão fará o Flight lançar uma exceção, prevenindo a injeção de JavaScript arbitrário por meio de um valor de callback malicioso.
Essa validação é embutida e não requer configuração adicional, mas é bom saber disso ao depurar erros inesperados de endpoints JSONP.
CORS
Compartilhamento de Recursos entre Origens (CORS) é um mecanismo que permite que muitos recursos (ex.: fontes, JavaScript, etc.) em uma página web sejam solicitados de outro domínio fora do domínio de onde o recurso se originou. O Flight não possui funcionalidade embutida, mas isso pode ser facilmente tratado com um hook para executar antes que o método Flight::start() seja chamado.
// app/Utils/CorsUtil.php (skeleton: pasta Utils em PascalCase → App\Utils)
namespace App\Utils;
use flight\Engine;
class CorsUtil
{
protected Engine $app;
public function __construct(Engine $app)
{
$this->app = $app;
}
public function set(array $params = []): void
{
$request = $this->app->request();
$response = $this->app->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.
$allowed = [
'capacitor://localhost',
'ionic://localhost',
'http://localhost',
'http://localhost:4200',
'http://localhost:8080',
'http://localhost:8100',
];
$request = $this->app->request();
if (in_array($request->getVar('HTTP_ORIGIN'), $allowed, true) === true) {
$response = $this->app->response();
$response->header("Access-Control-Allow-Origin", $request->getVar('HTTP_ORIGIN'));
}
}
}
// bootstrap / rotas — executa antes do start
$app = Flight::app();
$cors = new \App\Utils\CorsUtil($app);
$app->before('start', [ $cors, 'set' ]);
Reforço da Configuração do Flight
O Flight expõe várias configurações do motor que têm implicações diretas na segurança. Configurá-las corretamente é uma das maneiras mais fáceis de reforçar sua aplicação.
flight.allow_method_override
Por padrão, o Flight permite que clientes sobrescrevam o método HTTP de uma requisição usando o cabeçalho X-HTTP-Method-Override ou um campo _method no corpo de um POST. Embora isso seja útil para formulários HTML que só podem enviar GET/POST, pode ser perigoso se você não estiver esperando — um atacante pode forjar requisições DELETE ou PUT por meio de um formulário comum.
Se sua aplicação não depende desse comportamento (ex.: você está construindo uma API consumida por clientes modernos ou frontends JavaScript que podem enviar qualquer verbo HTTP), você deve desativá-lo:
// No seu arquivo index.php ou bootstrap, antes de Flight::start()
Flight::set('flight.allow_method_override', false);
O valor padrão é true para compatibilidade com versões anteriores, mas defini-lo como false é fortemente recomendado para qualquer aplicação que não precise explicitamente do recurso de sobrescrita.
flight.debug
O Flight tem uma configuração flight.debug que controla se informações detalhadas de erro (mensagem de exceção, código e rastreamento de pilha completo) são renderizadas no navegador quando uma exceção não tratada ocorre. O padrão é false, o que significa que apenas uma mensagem genérica 500 Internal Server Error é exibida — nenhum detalhe interno é vazado para o cliente.
Nunca ative isso em um servidor de produção. Use apenas localmente ou em um ambiente de homologação:
// Seguro apenas para desenvolvimento local — NUNCA em produção
Flight::set('flight.debug', true);
Quando flight.debug é false (o padrão), você ainda pode capturar erros habilitando flight.log_errors:
// Registra erros no servidor sem expô-los ao cliente
Flight::set('flight.debug', false);
Flight::set('flight.log_errors', true);
Configuração recomendada para produção
// index.php ou aplicado a partir da configuração do aplicativo / bootstrap
Flight::set('flight.allow_method_override', false);
Flight::set('flight.debug', false);
Flight::set('flight.log_errors', true);
Tratamento de Erros
Oculte detalhes sensíveis de erro em produção para evitar vazar informações para atacantes. Em produção, registre erros em vez de exibi-los com display_errors definido como 0.
// No seu bootstrap.php ou index.php
// adicione isso ao seu app/config/config.php
$environment = ENVIRONMENT;
if ($environment === 'production') {
ini_set('display_errors', 0); // Desativa a exibição de erros
ini_set('log_errors', 1); // Registra erros em vez disso
ini_set('error_log', '/path/to/error.log');
}
// Nas suas rotas ou controladores
// Use Flight::halt() para respostas de erro controladas
Flight::halt(403, 'Access denied');
Sanitização de Entrada
Nunca confie na entrada do usuário. Sanitize-a usando filter_var antes de processar para evitar que dados maliciosos entrem. Prefira ler a entrada via $app->request() (ou Flight::request()) em vez de $_GET / $_POST brutos no código da aplicação.
// Vamos supor uma requisição $_POST com $_POST['input'] e $_POST['email']
// Sanitiza uma entrada de string
$clean_input = filter_var(Flight::request()->data->input, FILTER_SANITIZE_STRING);
// Sanitiza um email
$clean_email = filter_var(Flight::request()->data->email, FILTER_SANITIZE_EMAIL);
Hash de Senhas
Armazene senhas com segurança e verifique-as com segurança usando as funções embutidas do PHP, como password_hash e password_verify. Senhas nunca devem ser armazenadas em texto puro, nem devem ser criptografadas com métodos reversíveis. O hash garante que, mesmo que seu banco de dados seja comprometido, as senhas reais permaneçam protegidas.
$password = Flight::request()->data->password;
// Gere o hash de uma senha ao armazená-la (ex.: durante o registro)
$hashed_password = password_hash($password, PASSWORD_DEFAULT);
// Verifique uma senha (ex.: durante o login)
if (password_verify($password, $stored_hash)) {
// Senha corresponde
}
Limitação de Taxa
Proteja contra ataques de força bruta ou ataques de negação de serviço limitando as taxas de requisição com um cache.
// Supondo que você tenha o flightphp/cache instalado e registrado
// Usando flightphp/cache em um filtro
Flight::before('start', function() {
$cache = Flight::cache();
$ip = Flight::request()->ip;
$key = "rate_limit_{$ip}";
$attempts = (int) $cache->retrieve($key);
if ($attempts >= 10) {
Flight::halt(429, 'Too many requests');
}
$cache->set($key, $attempts + 1, 60); // Redefine após 60 segundos
});
Veja Também
- Sessions - Como gerenciar sessões de usuário com segurança.
- Templates - Escapagem automática do Twig/Latte e XSS.
- SimplePdo - Auxiliares de banco de dados com prepared statements.
- PdoWrapper - Obsoleto; use SimplePdo para novos códigos.
- Middleware - Como usar middleware para simplificar o processo de adicionar cabeçalhos de segurança.
- Configuration -
.envvs configuração literal, flags de produção. - AI & Developer Experience - Mantenha a política de segurança em
SECURITY.mdpara agentes. - Responses - Como personalizar respostas HTTP com cabeçalhos de segurança.
- Requests - Como lidar e sanitizar a entrada do usuário.
- filter_var - Função do PHP para sanitização de entrada.
- password_hash - Função do PHP para hash seguro de senhas.
- password_verify - Função do PHP para verificar senhas com hash.
Solução de Problemas
- Consulte a seção "Veja Também" acima para obter informações de solução de problemas relacionadas a problemas com componentes do Flight Framework.
- Se CSP bloquear seus scripts, adicione um nonce (padrão do skeleton) ou coloque origens específicas na lista de permissões — não defina
script-src *sem um plano.
Changelog
- Docs – Skeleton
App\Middleware, notas de CSRF/XSS no Twig, SimplePdo, segredos/.enveSECURITY.mdpara projetos compatíveis com IA. - v3.18.1 - Adicionada a seção Reforço da Configuração do Flight cobrindo
flight.allow_method_override,flight.debuge validação de callback JSONP. - v3.1.0 - Adicionadas seções sobre CORS, Tratamento de Erros, Sanitização de Entrada, Hash de Senhas e Limitação de Taxa.
- v2.0 - Adicionado escape para as visualizações padrão para prevenir XSS.
Learn/routing
Roteamento
Visão Geral
O roteamento no Flight PHP mapeia padrões de URL para funções de retorno ou métodos de classe, permitindo um tratamento de requisições rápido e simples. Ele é projetado para ter sobrecarga mínima, ser amigável para iniciantes e extensível sem dependências externas.
Entendendo
O roteamento é o mecanismo central que conecta requisições HTTP à lógica da sua aplicação no Flight. Ao definir rotas, você especifica como diferentes URLs acionam códigos específicos, seja por meio de funções, métodos de classe ou ações de controladores. O sistema de roteamento do Flight é flexível, suportando padrões básicos, parâmetros nomeados, expressões regulares e recursos avançados como injeção de dependência e roteamento de recursos. Essa abordagem mantém seu código organizado e fácil de manter, permanecendo rápido e simples para iniciantes e extensível para usuários avançados.
Nota: Quer entender mais sobre roteamento? Acesse a página "por que um framework?" para uma explicação mais detalhada.
Uso Básico
Definindo uma Rota Simples
O roteamento básico no Flight é feito combinando um padrão de URL com uma função de retorno ou um array de classe e método.
Flight::route('/', function(){
echo 'hello world!';
});
As rotas são correspondidas na ordem em que são definidas. A primeira rota que corresponder a uma requisição será invocada.
Usando Funções como Retornos de Chamada
O retorno de chamada pode ser qualquer objeto que seja chamável. Então você pode usar uma função normal:
function hello() {
echo 'hello world!';
}
Flight::route('/', 'hello');
Usando Classes e Métodos como um Controlador
Você também pode usar um método (estático ou não) de uma classe:
class GreetingController {
public function hello() {
echo 'hello world!';
}
}
Flight::route('/', [ 'GreetingController','hello' ]);
// ou
Flight::route('/', [ GreetingController::class, 'hello' ]); // método preferido
// ou
Flight::route('/', [ 'GreetingController::hello' ]);
// ou
Flight::route('/', [ 'GreetingController->hello' ]);
Ou criando um objeto primeiro e depois chamando o método:
use flight\Engine;
// GreetingController.php
class GreetingController
{
protected Engine $app
public function __construct(Engine $app) {
$this->app = $app;
$this->name = 'John Doe';
}
public function hello() {
echo "Hello, {$this->name}!";
}
}
// index.php
$app = Flight::app();
$greeting = new GreetingController($app);
Flight::route('/', [ $greeting, 'hello' ]);
Nota: Por padrão, quando um controlador é chamado dentro do framework, a classe
flight\Engineé sempre injetada, a menos que você especifique por meio de um contêiner de injeção de dependência
Roteamento Específico por Método
Por padrão, os padrões de rota são correspondidos a todos os métodos de requisição. Você pode responder a métodos específicos colocando um identificador antes da URL.
Flight::route('GET /', function () {
echo 'I received a GET request.';
});
Flight::route('POST /', function () {
echo 'I received a POST request.';
});
// Você não pode usar Flight::get() para rotas, pois esse é um método
// para obter variáveis, não para criar uma rota.
Flight::post('/', function() { /* código */ });
Flight::patch('/', function() { /* código */ });
Flight::put('/', function() { /* código */ });
Flight::delete('/', function() { /* código */ });
Você também pode mapear vários métodos para um único retorno de chamada usando um delimitador |:
Flight::route('GET|POST /', function () {
echo 'I received either a GET or a POST request.';
});
Tratamento Especial para Requisições HEAD e OPTIONS
O Flight fornece tratamento integrado para requisições HTTP HEAD e OPTIONS:
Requisições HEAD
- Requisições HEAD são tratadas exatamente como requisições
GET, mas o Flight remove automaticamente o corpo da resposta antes de enviá-la ao cliente. - Isso significa que você pode definir uma rota para
GET, e requisições HEAD para a mesma URL retornarão apenas cabeçalhos (sem conteúdo), conforme esperado pelos padrões HTTP.
Flight::route('GET /info', function() {
echo 'This is some info!';
});
// Uma requisição HEAD para /info retornará os mesmos cabeçalhos, mas sem corpo.
Requisições OPTIONS
Requisições OPTIONS são tratadas automaticamente pelo Flight para qualquer rota definida.
- Quando uma requisição OPTIONS é recebida, o Flight responde com um status
204 No Contente um cabeçalhoAllowlistando todos os métodos HTTP suportados para aquela rota. - Você não precisa definir uma rota separada para OPTIONS.
// Para uma rota definida como:
Flight::route('GET|POST /users', function() { /* ... */ });
// Uma requisição OPTIONS para /users responderá com:
//
// Status: 204 No Content
// Allow: GET, POST, HEAD, OPTIONS
Usando o Objeto Router
Além disso, você pode obter o objeto Router que possui alguns métodos auxiliares para você usar:
$router = Flight::router();
// mapeia todos os métodos assim como Flight::route()
$router->map('/', function() {
echo 'hello world!';
});
// Requisição GET
$router->get('/users', function() {
echo 'users';
});
$router->post('/users', function() { /* código */});
$router->put('/users/update/@id', function() { /* código */});
$router->delete('/users/@id', function() { /* código */});
$router->patch('/users/@id', function() { /* código */});
Expressões Regulares (Regex)
Você pode usar expressões regulares em suas rotas:
Flight::route('/user/[0-9]+', function () {
// Isso corresponderá a /user/1234
});
Embora este método esteja disponível, é recomendado usar parâmetros nomeados, ou parâmetros nomeados com expressões regulares, pois são mais legíveis e fáceis de manter.
Parâmetros Nomeados
Você pode especificar parâmetros nomeados em suas rotas, que serão passados para sua função de retorno. Isso é mais para legibilidade da rota do que qualquer outra coisa. Consulte a seção abaixo sobre a importante ressalva.
Flight::route('/@name/@id', function (string $name, string $id) {
echo "hello, $name ($id)!";
});
Você também pode incluir expressões regulares com seus parâmetros nomeados usando o delimitador ::
Flight::route('/@name/@id:[0-9]{3}', function (string $name, string $id) {
// Isso corresponderá a /bob/123
// Mas não corresponderá a /bob/12345
});
Nota: A correspondência de grupos de regex
()com parâmetros posicionais não é suportada. Ex::'\(
Ressalva Importante
Embora no exemplo acima pareça que @name está diretamente ligado à variável $name, não está. A ordem dos parâmetros na função de retorno é o que determina o que é passado para ela. Se você trocar a ordem dos parâmetros na função de retorno, as variáveis também serão trocadas. Aqui está um exemplo:
Flight::route('/@name/@id', function (string $id, string $name) {
echo "hello, $name ($id)!";
});
E se você acessar a seguinte URL: /bob/123, a saída seria hello, 123 (bob)!. Por favor, tenha cuidado ao configurar suas rotas e suas funções de retorno!
Parâmetros Opcionais
Você pode especificar parâmetros nomeados que são opcionais para correspondência, envolvendo segmentos entre parênteses.
Flight::route(
'/blog(/@year(/@month(/@day)))',
function(?string $year, ?string $month, ?string $day) {
// Isso corresponderá às seguintes URLs:
// /blog/2012/12/10
// /blog/2012/12
// /blog/2012
// /blog
}
);
Quaisquer parâmetros opcionais que não forem correspondidos serão passados como NULL.
Roteamento Curinga
A correspondência é feita apenas em segmentos individuais de URL. Se você quiser corresponder a vários segmentos, pode usar o curinga *.
Flight::route('/blog/*', function () {
// Isso corresponderá a /blog/2000/02/01
});
Para rotear todas as requisições para um único retorno de chamada, você pode fazer:
Flight::route('*', function () {
// Faça algo
});
Manipulador de 404 Não Encontrado
Por padrão, se uma URL não puder ser encontrada, o Flight enviará uma resposta HTTP 404 Not Found muito simples e básica. Se você quiser ter uma resposta 404 mais personalizada, pode mapear seu próprio método notFound:
Flight::map('notFound', function() {
$url = Flight::request()->url;
// Você também poderia usar Flight::render() com um modelo personalizado.
$output = <<<HTML
<h1>My Custom 404 Not Found</h1>
<h3>The page you have requested {$url} could not be found.</h3>
HTML;
$this->response()
->clearBody()
->status(404)
->write($output)
->send();
});
Manipulador de Método Não Encontrado
Por padrão, se uma URL for encontrada, mas o método não for permitido, o Flight enviará uma resposta HTTP 405 Method Not Allowed muito simples e básica (Ex: Método Não Permitido. Métodos Permitidos são: GET, POST). Ele também incluirá um cabeçalho Allow com os métodos permitidos para aquela URL.
Se você quiser ter uma resposta 405 mais personalizada, pode mapear seu próprio método methodNotFound:
use flight\net\Route;
Flight::map('methodNotFound', function(Route $route) {
$url = Flight::request()->url;
$methods = implode(', ', $route->methods);
// Você também poderia usar Flight::render() com um modelo personalizado.
$output = <<<HTML
<h1>My Custom 405 Method Not Allowed</h1>
<h3>The method you have requested for {$url} is not allowed.</h3>
<p>Allowed Methods are: {$methods}</p>
HTML;
$this->response()
->clearBody()
->status(405)
->setHeader('Allow', $methods)
->write($output)
->send();
});
Uso Avançado
Injeção de Dependência em Rotas
Se você quiser usar injeção de dependência por meio de um contêiner (PSR-11, PHP-DI, Dice, etc.), o único tipo de rotas em que isso está disponível é criando diretamente o objeto você mesmo e usando o contêiner para criar seu objeto, ou você pode usar strings para definir a classe e o 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\SimplePdo;
// Greeting.php
class Greeting
{
protected SimplePdo $db;
public function __construct(SimplePdo $db) {
$this->db = $db;
}
public function hello(int $id) {
// faça algo com $this->db
$name = $this->db->fetchField("SELECT name FROM users WHERE id = ?", [ $id ]);
echo "Hello, world! My name is {$name}!";
}
}
// index.php
// Configure o contêiner com os parâmetros que você precisar
// 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(SimplePdo::class, [
'shared' => true,
'constructParams' => [
'mysql:host=localhost;dbname=test',
'root',
'password'
]
]);
// Registre o manipulador do contêiner
Flight::registerContainerHandler(function($class, $params) use ($dice) {
return $dice->create($class, $params);
});
// Rotas como de costume
Flight::route('/hello/@id', [ 'Greeting', 'hello' ]);
// ou
Flight::route('/hello/@id', 'Greeting->hello');
// ou
Flight::route('/hello/@id', 'Greeting::hello');
Flight::start();
Passando a Execução para a Próxima Rota
Obsoleto
Você pode passar a execução para a próxima rota correspondente retornando true da sua função de retorno.
Flight::route('/user/@name', function (string $name) {
// Verifique alguma condição
if ($name !== "Bob") {
// Continue para a próxima rota
return true;
}
});
Flight::route('/user/*', function () {
// Isto será chamado
});
Agora é recomendado usar middleware para lidar com casos de uso complexos como este.
Apelidos de Rota (Aliasing)
Ao atribuir um apelido a uma rota, você pode posteriormente chamar esse apelido em seu aplicativo dinamicamente para ser gerado mais tarde no seu código (ex: um link em um template HTML, ou gerar uma URL de redirecionamento).
Flight::route('/users/@id', function($id) { echo 'user:'.$id; }, false, 'user_view');
// ou
Flight::route('/users/@id', function($id) { echo 'user:'.$id; })->setAlias('user_view');
// mais tarde em algum lugar do código
class UserController {
public function update() {
// código para salvar o usuário...
$id = $user['id']; // 5 por exemplo
$redirectUrl = Flight::getUrl('user_view', [ 'id' => $id ]); // retornará '/users/5'
Flight::redirect($redirectUrl);
}
}
Isso é especialmente útil se a sua URL mudar. No exemplo acima, digamos que os usuários foram movidos para /admin/users/@id em vez disso. Com o apelido em vigor para a rota, você não precisa mais encontrar todas as URLs antigas no seu código e alterá-las, porque o apelido agora retornará /admin/users/5 como no exemplo acima.
O apelido de rota também funciona em grupos:
Flight::group('/users', function() {
Flight::route('/@id', function($id) { echo 'user:'.$id; }, false, 'user_view');
// ou
Flight::route('/@id', function($id) { echo 'user:'.$id; })->setAlias('user_view');
});
Inspecionando Informações da Rota
Se você quiser inspecionar as informações da rota correspondida, há 2 maneiras de fazer isso:
- Você pode usar a propriedade
executedRouteno objetoFlight::router(). - Você pode pedir para que o objeto de rota seja passado para seu retorno de chamada passando
truecomo o terceiro parâmetro no método de rota. O objeto de rota será sempre o último parâmetro passado para sua função de retorno.
executedRoute
Flight::route('/', function() {
$route = Flight::router()->executedRoute;
// Faça algo com $route
// Matriz de métodos HTTP correspondidos
$route->methods;
// Matriz de parâmetros nomeados
$route->params;
// Expressão regular correspondente
$route->regex;
// Contém o conteúdo de qualquer '*' usado no padrão de URL
$route->splat;
// Mostra o caminho da url... se você realmente precisar
$route->pattern;
// Mostra qual middleware está atribuído a isto
$route->middleware;
// Mostra o apelido atribuído a esta rota
$route->alias;
});
Nota: A propriedade
executedRoutesó será definida depois que uma rota for executada. Se você tentar acessá-la antes de uma rota ser executada, ela seráNULL. Você também pode usar executedRoute em middleware também!
Passando true para a definição da rota
Flight::route('/', function(\flight\net\Route $route) {
// Matriz de métodos HTTP correspondidos
$route->methods;
// Matriz de parâmetros nomeados
$route->params;
// Expressão regular correspondente
$route->regex;
// Contém o conteúdo de qualquer '*' usado no padrão de URL
$route->splat;
// Mostra o caminho da url... se você realmente precisar
$route->pattern;
// Mostra qual middleware está atribuído a isto
$route->middleware;
// Mostra o apelido atribuído a esta rota
$route->alias;
}, true);// <-- Este parâmetro true é o que faz isso acontecer
Agrupamento de Rotas e Middleware
Pode haver momentos em que você queira agrupar rotas relacionadas (como /api/v1). Você pode fazer isso usando o método group:
Flight::group('/api/v1', function () {
Flight::route('/users', function () {
// Corresponde a /api/v1/users
});
Flight::route('/posts', function () {
// Corresponde a /api/v1/posts
});
});
Você pode até aninhar grupos de grupos:
Flight::group('/api', function () {
Flight::group('/v1', function () {
// Flight::get() obtém variáveis, não define uma rota! Veja o contexto de objeto abaixo
Flight::route('GET /users', function () {
// Corresponde a GET /api/v1/users
});
Flight::post('/posts', function () {
// Corresponde a POST /api/v1/posts
});
Flight::put('/posts/1', function () {
// Corresponde a PUT /api/v1/posts
});
});
Flight::group('/v2', function () {
// Flight::get() obtém variáveis, não define uma rota! Veja o contexto de objeto abaixo
Flight::route('GET /users', function () {
// Corresponde a GET /api/v2/users
});
});
});
Agrupamento com Contexto de Objeto
Você ainda pode usar o agrupamento de rotas com o objeto Engine da seguinte forma:
$app = Flight::app();
$app->group('/api/v1', function (Router $router) {
// use a variável $router
$router->get('/users', function () {
// Corresponde a GET /api/v1/users
});
$router->post('/posts', function () {
// Corresponde a POST /api/v1/posts
});
});
Nota: Este é o método preferido para definir rotas e grupos com o objeto
$router.
Agrupamento com Middleware
Você também pode atribuir middleware a um grupo de rotas:
Flight::group('/api/v1', function () {
Flight::route('/users', function () {
// Corresponde a /api/v1/users
});
}, [ MyAuthMiddleware::class ]); // ou [ new MyAuthMiddleware() ] se você quiser usar uma instância
Veja mais detalhes na página de middleware de grupo.
Roteamento de Recursos
Você pode criar um conjunto de rotas para um recurso usando o método resource. Isso criará um conjunto de rotas para um recurso que segue as convenções RESTful.
Para criar um recurso, faça o seguinte:
Flight::resource('/users', UsersController::class);
E o que acontecerá em segundo plano é que ele criará as seguintes rotas:
[
'index' => 'GET /users',
'create' => 'GET /users/create',
'store' => 'POST /users',
'show' => 'GET /users/@id',
'edit' => 'GET /users/@id/edit',
'update' => 'PUT /users/@id',
'destroy' => 'DELETE /users/@id'
]
E seu controlador usará os seguintes métodos:
class UsersController
{
public function index(): void
{
}
public function show(string $id): void
{
}
public function create(): void
{
}
public function store(): void
{
}
public function edit(string $id): void
{
}
public function update(string $id): void
{
}
public function destroy(string $id): void
{
}
}
Nota: Você pode visualizar as rotas recém-adicionadas com
runwayexecutandophp runway routes.
Personalizando Rotas de Recurso
Existem algumas opções para configurar as rotas de recurso.
Base do Apelido
Você pode configurar o aliasBase. Por padrão, o apelido é a última parte da URL especificada. Por exemplo, /users/ resultaria em um aliasBase de users. Quando essas rotas são criadas, os apelidos são users.index, users.create, etc. Se você quiser alterar o apelido, defina o aliasBase para o valor desejado.
Flight::resource('/users', UsersController::class, [ 'aliasBase' => 'user' ]);
Only e Except
Você também pode especificar quais rotas deseja criar usando as opções only e except.
// Permita apenas esses métodos na lista branca e bloqueie o restante
Flight::resource('/users', UsersController::class, [ 'only' => [ 'index', 'show' ] ]);
// Bloqueie apenas esses métodos e permita o restante
Flight::resource('/users', UsersController::class, [ 'except' => [ 'create', 'store', 'edit', 'update', 'destroy' ] ]);
Estas são basicamente opções de lista branca e lista negra, para que você possa especificar quais rotas deseja criar.
Middleware
Você também pode especificar middleware para ser executado em cada uma das rotas criadas pelo método resource.
Flight::resource('/users', UsersController::class, [ 'middleware' => [ MyAuthMiddleware::class ] ]);
Respostas em Streaming
Agora você pode transmitir respostas para o cliente usando stream() ou streamWithHeaders(). Isso é útil para enviar arquivos grandes, processos de longa duração ou gerar respostas grandes. O streaming de uma rota é tratado de forma um pouco diferente de uma rota comum.
Nota: Respostas em streaming só estão disponíveis se você tiver
flight.v2.output_bufferingdefinido comofalse.
Streaming com Cabeçalhos Manuais
Você pode transmitir uma resposta para o cliente usando o método stream() em uma rota. Se fizer isso, você deve definir todos os cabeçalhos manualmente antes de enviar qualquer coisa para o cliente. Isso é feito com a função header() do PHP ou com o método Flight::response()->setRealHeader().
Flight::route('/@filename', function($filename) {
$response = Flight::response();
// obviamente você sanitizaria o caminho e tal.
$fileNameSafe = basename($filename);
// Se você tiver cabeçalhos adicionais para definir aqui depois que a rota for executada
// você deve defini-los antes que qualquer coisa seja exibida.
// Todos devem ser uma chamada direta à função header() ou
// uma chamada a Flight::response()->setRealHeader()
header('Content-Disposition: attachment; filename="'.$fileNameSafe.'"');
// ou
$response->setRealHeader('Content-Disposition: attachment; filename="'.$fileNameSafe.'"');
$filePath = '/some/path/to/files/'.$fileNameSafe;
if (!is_readable($filePath)) {
Flight::halt(404, 'File not found');
}
// defina manualmente o comprimento do conteúdo, se quiser
header('Content-Length: '.filesize($filePath));
// ou
$response->setRealHeader('Content-Length: '.filesize($filePath));
// Transmita o arquivo para o cliente enquanto ele é lido
readfile($filePath);
// Esta é a linha mágica aqui
})->stream();
Streaming com Cabeçalhos
Você também pode usar o método streamWithHeaders() para definir os cabeçalhos antes de começar o streaming.
Flight::route('/stream-users', function() {
// você pode adicionar quaisquer cabeçalhos adicionais que quiser aqui
// você só precisa usar header() ou Flight::response()->setRealHeader()
// no entanto você obtém seus dados, apenas como exemplo...
$users_stmt = Flight::db()->query("SELECT id, first_name, last_name FROM users");
echo '{';
$user_count = count($users);
while($user = $users_stmt->fetch(PDO::FETCH_ASSOC)) {
echo json_encode($user);
if(--$user_count > 0) {
echo ',';
}
// Isso é necessário para enviar os dados ao cliente
ob_flush();
}
echo '}';
// É assim que você definirá os cabeçalhos antes de começar o streaming.
})->streamWithHeaders([
'Content-Type' => 'application/json',
'Content-Disposition' => 'attachment; filename="users.json"',
// código de status opcional, padrão 200
'status' => 200
]);
Veja Também
- Middleware - Usando middleware com rotas para autenticação, registro de logs, etc.
- Injeção de Dependência - Simplificando a criação e o gerenciamento de objetos em rotas.
- Por que um Framework? - Entendendo os benefícios de usar um framework como o Flight.
- Estendendo - Como estender o Flight com sua própria funcionalidade, incluindo o método
notFound. - php.net: preg_match - Função PHP para correspondência de expressões regulares.
Solução de Problemas
- Os parâmetros da rota são correspondidos por ordem, não por nome. Garanta que a ordem dos parâmetros do retorno de chamada corresponda à definição da rota.
- Usar
Flight::get()não define uma rota; useFlight::route('GET /...')para roteamento ou o contexto do objeto Router em grupos (ex.:$router->get(...)). - A propriedade executedRoute só é definida depois que uma rota é executada; ela é NULL antes da execução.
- Streaming exige que a funcionalidade legada de buffer de saída do Flight esteja desativada (
flight.v2.output_buffering = false). - Para injeção de dependência, apenas certas definições de rota suportam instanciação baseada em contêiner.
404 Não Encontrado ou Comportamento Inesperado de Rota
Se você está vendo um erro 404 Não Encontrado (mas você jura de pé junto que ele realmente está lá e não é um erro de digitação), isso na verdade pode ser um problema com você retornando um valor no endpoint da rota em vez de apenas exibi-lo. A razão para isso é intencional, mas pode pegar alguns desenvolvedores de surpresa.
Flight::route('/hello', function(){
// Isso pode causar um erro 404 Não Encontrado
return 'Hello World';
});
// O que você provavelmente quer
Flight::route('/hello', function(){
echo 'Hello World';
});
A razão para isso é um mecanismo especial embutido no roteador que trata a saída de retorno como um sinal para "ir para a próxima rota". Você pode ver o comportamento documentado na seção Roteamento.
Registro de Alterações
- v3: Adicionado roteamento de recursos, apelidos de rota e suporte a streaming, grupos de rotas e suporte a middleware.
- v1: A grande maioria dos recursos básicos disponíveis.
Learn/learn
Aprenda Sobre o Flight
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. Ele é construído tendo a simplicidade em mente e é escrito de uma forma fácil de entender e usar — por humanos e por assistentes de codificação por IA.
Nota: Você verá exemplos que usam
Flight::como uma variável estática e alguns que usam o objeto Engine$app->. Ambos funcionam de forma intercambiável.$appe$this->appem um controlador/middleware são a abordagem recomendada pela equipe do Flight (e o que o esqueleto oficial +AGENTS.mdpadronizam para novos projetos).
Componentes Principais
Roteamento
Aprenda como gerenciar rotas para sua aplicação web. Isso também inclui agrupamento de rotas, parâmetros de rota e middleware.
Middleware
Aprenda como usar middleware para filtrar requisições e respostas na sua aplicação.
Carregamento Automático
Aprenda como carregar suas próprias classes automaticamente. A capitalização das pastas deve corresponder aos seus namespaces — o esqueleto usa App\ e pastas PascalCase como app/Controller/.
Requisições
Aprenda como lidar com requisições e respostas na sua aplicação.
Respostas
Aprenda como enviar respostas aos seus usuários.
Templates HTML
Aprenda como renderizar HTML com Twig (padrão do esqueleto), Latte ou outros motores — não apenas as views PHP integradas.
Segurança
Aprenda como proteger sua aplicação contra ameaças de segurança comuns.
Configuração
Aprenda como configurar o framework para sua aplicação.
Gerenciador de Eventos
Aprenda como usar o sistema de eventos para adicionar eventos personalizados à sua aplicação.
Estendendo o Flight
Aprenda como estender o framework adicionando seus próprios métodos e classes.
Ganchos de Método e Filtragem
Aprenda como adicionar ganchos de evento aos seus métodos e aos métodos internos do framework.
Container de Injeção de Dependência (DIC)
Aprenda como usar containers de injeção de dependência (DIC) para gerenciar as dependências da sua aplicação.
Classes Utilitárias
Coleções
As coleções são usadas para armazenar dados e serem acessíveis como um array ou como um objeto para facilitar o uso.
Wrapper JSON
Este tem algumas funções simples para tornar a codificação e decodificação do seu JSON consistente.
SimplePdo
O PDO às vezes pode adicionar mais dor de cabeça do que o necessário. O SimplePdo é uma classe auxiliar moderna de PDO com métodos convenientes como insert(), update(), delete() e transaction() para tornar as operações de banco de dados muito mais fáceis.
PdoWrapper (Obsoleto)
O wrapper PDO original está obsoleto a partir da v3.18.0. Use SimplePdo em seu lugar.
Manipulador de Arquivo Enviado
Uma classe simples para ajudar a gerenciar arquivos enviados e movê-los para um local permanente.
Conceitos Importantes
Por que um Framework?
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.
Além disso, um excelente tutorial foi criado por @lubiana. Embora não entre em grandes detalhes especificamente sobre o Flight, este guia ajudará você a entender alguns dos principais conceitos em torno de um framework e por que eles são benéficos de usar. Você pode encontrar o tutorial aqui.
Flight Comparado a Outros Frameworks
Se você está migrando de outro framework como Laravel, Slim, Fat-Free ou Symfony para o Flight, esta página ajudará você a entender as diferenças entre os dois.
Outros Tópicos
Testes Unitários
Siga este guia para aprender como testar unitariamente seu código Flight para que ele seja extremamente sólido.
IA e Experiência do Desenvolvedor
O Flight é construído para trabalhar com LLMs de codificação: AGENTS.md, comandos Runway ai:* e um layout de esqueleto claro para que os agentes mantenham o padrão.
Migrando da v2 para v3
A compatibilidade com versões anteriores foi, na maior parte, mantida, mas há algumas alterações das quais você deve estar ciente ao migrar da v2 para a v3.
Learn/unit_testing
Testes Unitários
Visão Geral
Os testes unitários no Flight ajudam a garantir que sua aplicação se comporte conforme o esperado, detectar bugs cedo e tornar a base de código mais fácil de manter. O Flight foi projetado para funcionar bem com o PHPUnit, o framework de testes PHP mais popular.
Compreensão
Os testes unitários verificam o comportamento de pequenas partes da sua aplicação (como controladores ou serviços) de forma isolada. No Flight, isso significa testar como suas rotas, controladores e lógica respondem a diferentes entradas—sem depender de estado global ou serviços externos reais.
Princípios-chave:
- Teste o comportamento, não a implementação: Concentre-se no que seu código faz, não em como faz.
- Evite estado global: Use injeção de dependência em vez de
Flight::set()ouFlight::get(). - Simule serviços externos: Substitua coisas como bancos de dados ou serviços de e-mail por dublês de teste.
- Mantenha os testes rápidos e focados: Os testes unitários não devem acessar bancos de dados ou APIs reais.
Uso Básico
Configurando o PHPUnit
- Instale o PHPUnit com o Composer:
composer require --dev phpunit/phpunit - Crie um diretório
testsna raiz do seu projeto. - Adicione um script de teste ao seu
composer.json:"scripts": { "test": "phpunit --configuration phpunit.xml" } - Crie um arquivo
phpunit.xml:<?xml version="1.0" encoding="UTF-8"?> <phpunit bootstrap="vendor/autoload.php"> <testsuites> <testsuite name="Flight Tests"> <directory>tests</directory> </testsuite> </testsuites> </phpunit>
Agora você pode executar seus testes com composer test.
Testando um Manipulador de Rota Simples
Suponha que você tenha uma rota que valide um e-mail:
// index.php
$app->route('POST /register', [ UserController::class, 'register' ]);
// UserController.php
class UserController {
protected $app;
public function __construct(flight\Engine $app) {
$this->app = $app;
}
public function register() {
$email = $this->app->request()->data->email;
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
return $this->app->json(['status' => 'error', 'message' => 'Invalid email']);
}
return $this->app->json(['status' => 'success', 'message' => 'Valid email']);
}
}
Um teste simples para este controlador:
use PHPUnit\Framework\TestCase;
use flight\Engine;
class UserControllerTest extends TestCase {
public function testValidEmailReturnsSuccess() {
$app = new Engine();
$app->request()->data->email = 'test@example.com';
$controller = new UserController($app);
$controller->register();
$response = $app->response()->getBody();
$output = json_decode($response, true);
$this->assertEquals('success', $output['status']);
$this->assertEquals('Valid email', $output['message']);
}
public function testInvalidEmailReturnsError() {
$app = new Engine();
$app->request()->data->email = 'invalid-email';
$controller = new UserController($app);
$controller->register();
$response = $app->response()->getBody();
$output = json_decode($response, true);
$this->assertEquals('error', $output['status']);
$this->assertEquals('Invalid email', $output['message']);
}
}
Dicas:
- Simule dados POST usando
$app->request()->data. - Evite usar estáticos
Flight::em seus testes—use a instância$app.
Usando Injeção de Dependência para Controladores Testáveis
Injete dependências (como o banco de dados ou serviço de e-mail) em seus controladores para facilitar a simulação (mock) nos testes:
use flight\database\SimplePdo;
class UserController {
protected $app;
protected $db;
protected $mailer;
public function __construct($app, $db, $mailer) {
$this->app = $app;
$this->db = $db;
$this->mailer = $mailer;
}
public function register() {
$email = $this->app->request()->data->email;
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
return $this->app->json(['status' => 'error', 'message' => 'Invalid email']);
}
$this->db->runQuery('INSERT INTO users (email) VALUES (?)', [$email]);
$this->mailer->sendWelcome($email);
return $this->app->json(['status' => 'success', 'message' => 'User registered']);
}
}
E um teste com mocks (simulações):
use PHPUnit\Framework\TestCase;
class UserControllerDICTest extends TestCase {
public function testValidEmailSavesAndSendsEmail() {
$mockDb = $this->createMock(flight\database\SimplePdo::class);
$mockDb->method('runQuery')->willReturn(true);
$mockMailer = new class {
public $sentEmail = null;
public function sendWelcome($email) { $this->sentEmail = $email; return true; }
};
$app = new flight\Engine();
$app->request()->data->email = 'test@example.com';
$controller = new UserController($app, $mockDb, $mockMailer);
$controller->register();
$response = $app->response()->getBody();
$result = json_decode($response, true);
$this->assertEquals('success', $result['status']);
$this->assertEquals('User registered', $result['message']);
$this->assertEquals('test@example.com', $mockMailer->sentEmail);
}
}
Uso Avançado
- Simulação (Mocking): Use os mocks integrados do PHPUnit ou classes anônimas para substituir dependências.
- Testando controladores diretamente: Instancie os controladores com um novo
Enginee simule as dependências. - Evite excesso de mocks: Deixe a lógica real executar quando possível; simule apenas serviços externos.
Veja Também
- Guia de Testes Unitários - Um guia completo sobre boas práticas de testes unitários.
- Contêiner de Injeção de Dependência - Como usar DICs para gerenciar dependências e melhorar a testabilidade.
- Estendendo - Como adicionar seus próprios helpers ou sobrescrever classes principais.
- SimplePdo - Simplifica as interações com o banco de dados e é mais fácil de simular em testes.
- Requisições - Lidando com requisições HTTP no Flight.
- Respostas - Enviando respostas aos usuários.
- Testes Unitários e Princípios SOLID - Aprenda como os princípios SOLID podem melhorar seus testes unitários.
Solução de Problemas
- Evite usar estado global (
Flight::set(),$_SESSION, etc.) em seu código e testes. - Se seus testes estão lentos, você pode estar escrevendo testes de integração—simule serviços externos para manter os testes unitários rápidos.
- Se a configuração do teste for complexa, considere refatorar seu código para usar injeção de dependência.
Registro de Alterações
- v3.15.0 - Adicionados exemplos para injeção de dependência e simulação (mocking).
Learn/flight_vs_symfony
Flight vs Symfony
O que é Symfony?
Symfony é um conjunto de componentes PHP reutilizáveis e um framework PHP para projetos web.
A base padrão na qual as melhores aplicações PHP são construídas. Escolha qualquer um dos 50 componentes independentes disponíveis para suas próprias aplicações.
Acelere a criação e manutenção de suas aplicações web PHP. Encerre tarefas repetitivas de codificação e aproveite o poder de controlar seu código.
Prós comparados ao Flight
- Symfony possui um ecossistema enorme de desenvolvedores e módulos que podem ser usados para resolver problemas comuns.
- Symfony possui um ORM completo (Doctrine) que pode ser usado para interagir com seu banco de dados.
- Symfony possui uma grande quantidade de documentação e tutoriais que podem ser usados para aprender o framework.
- Symfony possui podcasts, conferências, reuniões, vídeos e outros recursos que podem ser usados para aprender o framework.
- Symfony é voltado para um desenvolvedor experiente que procura construir um aplicativo web empresarial completo.
Contras comparados ao Flight
- Symfony tem muito mais acontecendo sob o capô do que o Flight. Isso tem um custo drástico em termos de desempenho. Veja os benchmarks TechEmpower para mais informações.
- Flight é voltado para um desenvolvedor que procura construir um aplicativo web leve, rápido e fácil de usar.
- Flight é voltado para simplicidade e facilidade de uso.
- Uma das principais características do Flight é que ele faz o possível para manter a compatibilidade com versões anteriores.
- Flight não possui dependências, enquanto Symfony possui uma série de dependências
- Flight é destinado a desenvolvedores que estão se aventurando no mundo dos frameworks pela primeira vez.
- Flight também pode lidar com aplicações de nível empresarial, mas não possui tantos exemplos e tutoriais quanto Symfony. Também exigirá mais disciplina por parte do desenvolvedor para manter as coisas organizadas e bem estruturadas.
- Flight dá ao desenvolvedor mais controle sobre a aplicação, enquanto o Symfony pode incluir alguma magia nos bastidores.
Learn/flight_vs_another_framework
Comparando Flight com Outro Framework
Se você está migrando de outro framework como Laravel, Slim, Fat-Free, ou Symfony para o Flight, esta página irá ajudá-lo a entender as diferenças entre os dois.
Laravel
Laravel é um framework completo que possui todos os recursos e uma incrível ecossistema focado no desenvolvedor, mas com um custo em desempenho e complexidade.
Veja a comparação entre Laravel e Flight.
Slim
Slim é um micro-framework que é semelhante ao Flight. Ele é projetado para ser leve e fácil de usar, mas pode ser um pouco mais complexo do que o Flight.
Veja a comparação entre Slim e Flight.
Fat-Free
Fat-Free é um framework full-stack em um pacote muito menor. Embora tenha todas as ferramentas necessárias, ele possui uma arquitetura de dados que pode tornar alguns projetos mais complexos do que precisam ser.
Veja a comparação entre Fat-Free e Flight.
Symfony
Symfony é um framework modular de nível empresarial projetado para ser flexível e escalável. Para projetos menores ou desenvolvedores mais novos, Symfony pode ser um pouco avassalador.
Veja a comparação entre Symfony e Flight.
Learn/pdo_wrapper
Classe Auxiliar PDO PdoWrapper
AVISO
Depreciado:
PdoWrapperestá depreciado a partir do Flight v3.18.0. Não será removido em uma versão futura, mas será mantido para compatibilidade com versões anteriores. Por favor, use SimplePdo em vez disso, que oferece a mesma funcionalidade mais métodos auxiliares adicionais para operações comuns de banco de dados.
Visão Geral
A classe PdoWrapper no Flight é um auxiliar amigável para trabalhar com bancos de dados usando PDO. Ela simplifica tarefas comuns de banco de dados, adiciona alguns métodos úteis para buscar resultados e retorna resultados como Collections para acesso fácil. Ela também suporta registro de consultas e monitoramento de desempenho de aplicação (APM) para casos de uso avançados.
Entendendo
Trabalhar com bancos de dados em PHP pode ser um pouco verboso, especialmente ao usar PDO diretamente. PdoWrapper estende PDO e adiciona métodos que tornam a consulta, busca e manipulação de resultados muito mais fáceis. Em vez de lidar com declarações preparadas e modos de busca, você obtém métodos simples para tarefas comuns, e cada linha é retornada como uma Collection, para que você possa usar notação de array ou objeto.
Você pode registrar o PdoWrapper como um serviço compartilhado no Flight, e então usá-lo em qualquer lugar do seu app via Flight::db().
Uso Básico
Registrando o Auxiliar PDO
Primeiro, registre a classe PdoWrapper com o Flight:
Flight::register('db', \flight\database\PdoWrapper::class, [
'mysql:host=localhost;dbname=cool_db_name', 'user', 'pass', [
PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES \'utf8mb4\'',
PDO::ATTR_EMULATE_PREPARES => false,
PDO::ATTR_STRINGIFY_FETCHES => false,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
]
]);
Agora você pode usar Flight::db() em qualquer lugar para obter sua conexão com o banco de dados.
Executando Consultas
runQuery()
function runQuery(string $sql, array $params = []): PDOStatement
Use isso para INSERTs, UPDATEs, ou quando você quiser buscar resultados manualmente:
$db = Flight::db();
$statement = $db->runQuery("SELECT * FROM users WHERE status = ?", ['active']);
while ($row = $statement->fetch()) {
// $row é um array
}
Você também pode usá-lo para gravações:
$db->runQuery("INSERT INTO users (name) VALUES (?)", ['Alice']);
$db->runQuery("UPDATE users SET name = ? WHERE id = ?", ['Bob', 1]);
fetchField()
function fetchField(string $sql, array $params = []): mixed
Obtenha um único valor do banco de dados:
$count = Flight::db()->fetchField("SELECT COUNT(*) FROM users WHERE status = ?", ['active']);
fetchRow()
function fetchRow(string $sql, array $params = []): Collection
Obtenha uma única linha como uma Collection (acesso array/objeto):
$user = Flight::db()->fetchRow("SELECT * FROM users WHERE id = ?", [123]);
echo $user['name'];
// ou
echo $user->name;
fetchAll()
function fetchAll(string $sql, array $params = []): array<Collection>
Obtenha todas as linhas como um array de Collections:
$users = Flight::db()->fetchAll("SELECT * FROM users WHERE status = ?", ['active']);
foreach ($users as $user) {
echo $user['name'];
// ou
echo $user->name;
}
Usando Placeholders IN()
Você pode usar um único ? em uma cláusula IN() e passar um array ou string separada por vírgulas:
$ids = [1, 2, 3];
$users = Flight::db()->fetchAll("SELECT * FROM users WHERE id IN (?)", [$ids]);
// ou
$users = Flight::db()->fetchAll("SELECT * FROM users WHERE id IN (?)", ['1,2,3']);
Uso Avançado
Registro de Consultas & APM
Se você quiser rastrear o desempenho de consultas, ative o rastreamento APM ao registrar:
Flight::register('db', \flight\database\PdoWrapper::class, [
'mysql:host=localhost;dbname=cool_db_name', 'user', 'pass', [/* options */], true // último parâmetro ativa APM
]);
Após executar consultas, você pode registrá-las manualmente, mas o APM as registrará automaticamente se ativado:
Flight::db()->logQueries();
Isso acionará um evento (flight.db.queries) com métricas de conexão e consulta, que você pode escutar usando o sistema de eventos do Flight.
Exemplo Completo
Flight::route('/users', function () {
// Obter todos os usuários
$users = Flight::db()->fetchAll('SELECT * FROM users');
// Transmitir todos os usuários
$statement = Flight::db()->runQuery('SELECT * FROM users');
while ($user = $statement->fetch()) {
echo $user['name'];
}
// Obter um único usuário
$user = Flight::db()->fetchRow('SELECT * FROM users WHERE id = ?', [123]);
// Obter um único valor
$count = Flight::db()->fetchField('SELECT COUNT(*) FROM users');
// Sintaxe especial IN()
$users = Flight::db()->fetchAll('SELECT * FROM users WHERE id IN (?)', [[1,2,3,4,5]]);
$users = Flight::db()->fetchAll('SELECT * FROM users WHERE id IN (?)', ['1,2,3,4,5']);
// Inserir um novo usuário
Flight::db()->runQuery("INSERT INTO users (name, email) VALUES (?, ?)", ['Bob', 'bob@example.com']);
$insert_id = Flight::db()->lastInsertId();
// Atualizar um usuário
Flight::db()->runQuery("UPDATE users SET name = ? WHERE id = ?", ['Bob', 123]);
// Deletar um usuário
Flight::db()->runQuery("DELETE FROM users WHERE id = ?", [123]);
// Obter o número de linhas afetadas
$statement = Flight::db()->runQuery("UPDATE users SET name = ? WHERE name = ?", ['Bob', 'Sally']);
$affected_rows = $statement->rowCount();
});
Veja Também
- Collections - Aprenda como usar a classe Collection para acesso fácil a dados.
Solução de Problemas
- Se você receber um erro sobre conexão com o banco de dados, verifique seu DSN, nome de usuário, senha e opções.
- Todas as linhas são retornadas como Collections—se você precisar de um array simples, use
$collection->getData(). - Para consultas
IN (?), certifique-se de passar um array ou string separada por vírgulas.
Changelog
- v3.2.0 - Lançamento inicial do PdoWrapper com métodos básicos de consulta e busca.
Learn/dependency_injection_container
Container de Injeção de Dependência
Visão Geral
O Container de Injeção de Dependência (DIC) é um recurso poderoso que permite gerenciar as dependências da sua aplicação. É também uma das maiores razões pelas quais o Flight funciona bem com ferramentas de codificação por IA e testes de unidade: os controladores recebem o que precisam no construtor em vez de acessar variáveis globais.
Entendendo
A Injeção de Dependência (DI) é um conceito-chave em frameworks PHP modernos e é usada para gerenciar a instanciação e a configuração de objetos. Alguns exemplos de bibliotecas de DIC são: flightphp/container, Dice, Pimple, PHP-DI e league/container.
Um DIC é uma forma elegante de criar e gerenciar suas classes em um local centralizado. Isso é útil quando você precisa passar o mesmo objeto para várias classes (controladores, middleware, comandos e assim por diante).
O flightphp/skeleton oficial conecta o Dice em app/config/services.php, substitui a instância compartilhada de flight\Engine e resolve alvos de rota como [App\Controller\HomeController::class, 'index']. Prefira esse padrão para novos projetos, para que humanos e agentes editem os mesmos lugares.
Uso Básico
A forma antiga de fazer as coisas pode ser assim:
require 'vendor/autoload.php';
// classe para gerenciar usuários do 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());
}
}
// no seu arquivo routes.php
$db = new PDO('mysql:host=localhost;dbname=test', 'user', 'pass');
$UserController = new UserController($db);
Flight::route('/user/@id', [ $UserController, 'view' ]);
// outras rotas do UserController...
Flight::start();
Você pode ver no código acima que estamos criando um novo objeto PDO e passando-o
para a classe UserController. Isso é bom para uma aplicação pequena, mas conforme sua
aplicação cresce, você descobrirá que está criando ou repassando o mesmo objeto PDO
em vários lugares. É aí que um DIC se torna útil.
Aqui está o mesmo exemplo usando um DIC (com Dice):
require 'vendor/autoload.php';
// mesma classe 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());
}
}
// cria um novo container
$container = new \Dice\Dice;
// adiciona uma regra para dizer ao container como criar um objeto PDO
// 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 container para que o Flight saiba usá-lo.
Flight::registerContainerHandler(function($class, $params) use ($container) {
return $container->create($class, $params);
});
// agora podemos usar o container para criar nosso UserController
Flight::route('/user/@id', [ UserController::class, 'view' ]);
Flight::start();
Aposto que você pode estar pensando que foi adicionado muito código extra ao exemplo.
A mágica acontece quando você tem outro controlador que precisa do objeto PDO.
// Se todos os seus controladores tiverem um construtor que precise de um objeto PDO
// cada uma das rotas abaixo terá ele injetado automaticamente!!!
Flight::route('/company/@id', [ CompanyController::class, 'view' ]);
Flight::route('/organization/@id', [ OrganizationController::class, 'view' ]);
Flight::route('/category/@id', [ CategoryController::class, 'view' ]);
Flight::route('/settings', [ SettingsController::class, 'view' ]);
O benefício adicional de utilizar um DIC é que os testes de unidade se tornam muito mais fáceis. Você pode criar um objeto mock e passá-lo para sua classe. Isso é um enorme benefício quando você está escrevendo testes para sua aplicação — e quando um assistente de IA gera um controlador, a injeção no construtor fornece um padrão claro e consistente a seguir (guia de testes de unidade).
Criando um manipulador de DIC centralizado
Você pode criar um manipulador de DIC centralizado em seu arquivo de serviços estendendo sua aplicação. Aqui está um exemplo:
// services.php
// cria um novo container
$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' ]
]);
// agora podemos criar um método mapeável para criar qualquer objeto.
Flight::map('make', function($class, $params = []) use ($container) {
return $container->create($class, $params);
});
// Isso registra o manipulador do container para que o Flight saiba usá-lo para controladores/middleware
Flight::registerContainerHandler(function($class, $params) {
return Flight::make($class, $params);
});
// digamos que temos a seguinte classe de exemplo que recebe um objeto PDO no construtor
class EmailCron {
protected PDO $pdo;
public function __construct(PDO $pdo) {
$this->pdo = $pdo;
}
public function send() {
// código que envia um e-mail
}
}
// E finalmente você pode criar objetos usando injeção de dependência
$emailCron = Flight::make(EmailCron::class);
$emailCron->send();
flightphp/container
O Flight tem um plugin que fornece um container simples compatível com PSR-11 que você pode usar para lidar com sua injeção de dependência. Aqui está um exemplo rápido de como usá-lo:
// index.php por exemplo
require 'vendor/autoload.php';
use flight\Container;
$container = new Container;
$container->set(PDO::class, fn(): PDO => new PDO('sqlite::memory:'));
Flight::registerContainerHandler([$container, 'get']);
class TestController {
private PDO $pdo;
function __construct(PDO $pdo) {
$this->pdo = $pdo;
}
function index() {
var_dump($this->pdo);
// vai exibir isso corretamente!
}
}
Flight::route('GET /', [TestController::class, 'index']);
Flight::start();
Uso Avançado do flightphp/container
Você também pode resolver dependências de forma recursiva. Aqui está um exemplo:
<?php
require 'vendor/autoload.php';
use flight\Container;
class User {}
interface UserRepository {
function find(int $id): ?User;
}
class PdoUserRepository implements UserRepository {
private PDO $pdo;
function __construct(PDO $pdo) {
$this->pdo = $pdo;
}
function find(int $id): ?User {
// Implementação ...
return null;
}
}
$container = new Container;
$container->set(PDO::class, static fn(): PDO => new PDO('sqlite::memory:'));
$container->set(UserRepository::class, PdoUserRepository::class);
$userRepository = $container->get(UserRepository::class);
var_dump($userRepository);
/*
object(PdoUserRepository)#4 (1) {
["pdo":"PdoUserRepository":private]=>
object(PDO)#3 (0) {
}
}
*/
DICE
Você também pode criar seu próprio manipulador de DIC. Isso é útil se você tiver um container personalizado que queira usar e que não seja PSR-11 (Dice). Consulte a seção uso básico para saber como fazer isso.
Além disso, existem alguns padrões úteis que facilitarão sua vida ao usar o Flight.
Instância do Engine (necessária para injeção de $app)
Se você usar type-hint flight\Engine em controladores ou middleware, o Dice não deve construir um novo Engine. Substitua pela mesma instância do bootstrap. É isso que o skeleton oficial faz, e é o padrão que o AGENTS.md espera para controladores gerados por IA:
// Em algum lugar do seu bootstrap / services.php
use flight\Engine;
use flight\database\SimplePdo;
$app = Flight::app(); // ou $engine = Flight::app();
$container = new \Dice\Dice;
$container = $container->addRule('*', [
'substitutions' => [
// Crítico: reutilize o Engine do bootstrap — não deixe o Dice fazer `new Engine()`
Engine::class => $app,
// Prefira SimplePdo para código novo
// SimplePdo::class => $db,
// Config::class => $config,
// \Twig\Environment::class => $twig,
]
]);
$app->registerContainerHandler(function ($class, $params) use ($container) {
return $container->create($class, $params);
});
// Auxiliar opcional para código fora de rotas
$app->map('make', function ($class, $params = []) use ($container) {
return $container->create($class, $params);
});
// app/Controller/MyController.php (layout do skeleton — a capitalização da pasta corresponde ao namespace)
namespace App\Controller;
use flight\Engine;
class MyController
{
protected Engine $app;
public function __construct(Engine $app)
{
$this->app = $app;
}
public function index(): void
{
// Sem fachada Flight:: na camada da aplicação — mais fácil de testar e mais claro para ferramentas de IA
$this->app->render('welcome', ['message' => 'Hello']);
}
}
// app/config/routes.php
use App\Controller\MyController;
$router->get('/', [MyController::class, 'index']);
Se você pular a substituição do Engine, o Dice pode construir um segundo Engine e seu controlador não compartilhará rotas, configuração ou o render do Twig mapeado a partir do bootstrap.
Adicionando outros serviços compartilhados (SimplePdo, Config, Twig)
use flight\database\SimplePdo;
use flight\Engine;
// Depois que você criar $db, $config, $twig em services.php:
$substitutions = [
Engine::class => $app,
SimplePdo::class => $db,
// App\Utils\Config::class => $config,
// \Twig\Environment::class => $twig,
];
$container = $container->addRule('*', [
'substitutions' => $substitutions,
]);
Então os controladores podem receber SimplePdo $db (ou seu tipo de configuração) no construtor e nunca chamar Flight::db(). Isso está de acordo com as orientações de testes de unidade e com o estilo padrão do skeleton.
Adicionando outras classes
Se você tiver outras classes que queira adicionar ao container, com Dice é fácil, pois elas serão resolvidas automaticamente pelo container. Aqui está um exemplo:
$container = new \Dice\Dice;
// Se você não precisar injetar nenhuma dependência em suas classes
// você não precisa definir nada!
Flight::registerContainerHandler(function($class, $params) use ($container) {
return $container->create($class, $params);
});
class MyCustomClass {
public function parseThing() {
return 'thing';
}
}
class UserController {
protected MyCustomClass $MyCustomClass;
public function __construct(MyCustomClass $MyCustomClass) {
$this->MyCustomClass = $MyCustomClass;
}
public function index() {
echo $this->MyCustomClass->parseThing();
}
}
Flight::route('/user', 'UserController->index');
PSR-11
O Flight também pode usar qualquer container compatível com PSR-11. Isso significa que você pode usar qualquer container que implemente a interface PSR-11. Aqui está um exemplo usando o container PSR-11 da League:
require 'vendor/autoload.php';
use flight\database\SimplePdo;
// mesma ideia do UserController acima, usando type-hint de SimplePdo em vez de PDO puro
$container = new \League\Container\Container();
$container->add(UserController::class)->addArgument(SimplePdo::class);
$container->add(SimplePdo::class)
->addArgument('mysql:host=localhost;dbname=test')
->addArgument('user')
->addArgument('pass');
Flight::registerContainerHandler($container);
Flight::route('/user', [ 'UserController', 'view' ]);
Flight::start();
Isso pode ser um pouco mais verboso do que o exemplo anterior com Dice, mas ainda assim cumpre o objetivo com os mesmos benefícios!
Veja Também
- Instalação - Layout do skeleton e onde
services.phpestá localizado. - Autoloading - Namespaces
App\e capitalização da pasta. - Estendendo o Flight - Aprenda como adicionar injeção de dependência às suas próprias classes estendendo o framework.
- Configuração - Aprenda como configurar o Flight para sua aplicação.
- Roteamento - Aprenda como definir rotas para sua aplicação e como a injeção de dependência funciona com controladores.
- Middleware - Aprenda como criar middleware para sua aplicação e como a injeção de dependência funciona com middleware.
- Testes de Unidade - Por que a injeção no construtor é melhor do que globais
Flight::. - IA e Experiência do Desenvolvedor - Um padrão de DI para humanos e agentes.
- SimplePdo - Auxiliar de banco de dados preferido para injeção.
Solução de Problemas
- Se você estiver tendo problemas com seu container, certifique-se de que está passando os nomes de classe corretos para ele.
- Controladores que usam type-hint
Enginemas recebem uma aplicação "em branco": adicione a substituição do Engine (veja acima). O Dice não deve fazernewem um segundo Engine. - Classe não encontrada para
App\Controller\…: verifique a capitalização da pasta emapp/Controller/— veja Autoloading. - O manipulador deve retornar o objeto criado a partir de
registerContainerHandler(não chameFlight::make()semreturn).
Histórico de Alterações
- Documentação – Documentar skeleton Dice + substituições de Engine, SimplePdo e layout
App\Controllerpara projetos amigáveis a IA. - v3.7.0 - Adicionada a capacidade de registrar um manipulador de DIC no Flight.
Learn/middleware
Middleware
Visão Geral
Flight suporta middleware de rota e middleware de grupo de rotas. O middleware é uma parte da sua aplicação onde o código é executado antes (ou depois) do callback da rota. Essa é uma ótima maneira de adicionar verificações de autenticação de API no seu código, ou para validar que o usuário tem permissão para acessar a rota.
Entendendo
O middleware pode simplificar greatly sua app. Em vez de herança complexa de classes abstratas ou sobrescritas de métodos, o middleware permite que você controle suas rotas atribuindo sua lógica de app personalizada a elas. Você pode pensar no middleware como um sanduíche. Você tem pão por fora, e então camadas de ingredientes como alface, tomates, carnes e queijo. Então imagine que cada requisição é como dar uma mordida no sanduíche onde você come as camadas externas primeiro e trabalha seu caminho até o núcleo.
Aqui está uma visualização de como o middleware funciona. Em seguida, mostraremos um exemplo prático de como isso funciona.
Requisição do usuário na URL /api ---->
Middleware->before() executado ----->
Callable/método anexado a /api executado e resposta gerada ------>
Middleware->after() executado ----->
Usuário recebe resposta do servidor
E aqui está um exemplo prático:
Usuário navega para a URL /dashboard
LoggedInMiddleware->before() executa
before() verifica sessão logada válida
se sim, não faz nada e continua a execução
se não, redireciona o usuário para /login
Callable/método anexado a /api executado e resposta gerada
LoggedInMiddleware->after() não tem nada definido, então deixa a execução continuar
Usuário recebe HTML do dashboard do servidor
Ordem de Execução
As funções de middleware são executadas na ordem em que são adicionadas à rota. A execução é semelhante a como Slim Framework lida com isso.
Os métodos before() são executados na ordem adicionada, e os métodos after() são executados em ordem reversa.
Ex: Middleware1->before(), Middleware2->before(), Middleware2->after(), Middleware1->after().
Uso Básico
Você pode usar middleware como qualquer método callable, incluindo uma função anônima ou uma classe (recomendado)
Função Anônima
Aqui está um exemplo simples:
Flight::route('/path', function() { echo ' Here I am!'; })->addMiddleware(function() {
echo 'Middleware first!';
});
Flight::start();
// Isso exibirá "Middleware first! Here I am!"
Nota: Ao usar uma função anônima, o único método interpretado é um método
before(). Você não pode definir comportamentoafter()com uma classe anônima.
Usando Classes
O middleware pode (e deve) ser registrado como uma classe. Se você precisar da funcionalidade "after", você deve usar uma classe.
class MyMiddleware {
public function before($params) {
echo 'Middleware first!';
}
public function after($params) {
echo 'Middleware last!';
}
}
$MyMiddleware = new MyMiddleware();
Flight::route('/path', function() { echo ' Here I am! '; })->addMiddleware($MyMiddleware);
// também ->addMiddleware([ $MyMiddleware, $MyMiddleware2 ]);
Flight::start();
// Isso exibirá "Middleware first! Here I am! Middleware last!"
Você também pode apenas definir o nome da classe de middleware e ela instanciará a classe.
Flight::route('/path', function() { echo ' Here I am! '; })->addMiddleware(MyMiddleware::class);
Nota: Se você passar apenas o nome do middleware, ele será automaticamente executado pelo container de injeção de dependência e o middleware será executado com os parâmetros que precisa. Se você não tiver um container de injeção de dependência registrado, ele passará por padrão a instância de
flight\Engineno__construct(Engine $app).
Usando Rotas com Parâmetros
Se você precisar de parâmetros da sua rota, eles serão passados em um único array para a função de middleware. (function($params) { ... } ou public function before($params) { ... }). A razão para isso é que você pode estruturar seus parâmetros em grupos e em alguns desses grupos, seus parâmetros podem aparecer em uma ordem diferente, o que quebraria a função de middleware ao se referir ao parâmetro errado. Dessa forma, você pode acessá-los por nome em vez de posição.
use flight\Engine;
class RouteSecurityMiddleware {
protected Engine $app;
public function __construct(Engine $app) {
$this->app = $app;
}
public function before(array $params) {
$clientId = $params['clientId'];
// jobId pode ou não ser passado
$jobId = $params['jobId'] ?? 0;
// talvez se não houver ID de job, você não precise buscar nada.
if($jobId === 0) {
return;
}
// execute uma busca de algum tipo no seu banco de dados
$isValid = !!$this->app->db()->fetchField("SELECT 1 FROM client_jobs WHERE client_id = ? AND job_id = ?", [ $clientId, $jobId ]);
if($isValid !== true) {
$this->app->halt(400, 'You are blocked, muahahaha!');
}
}
}
// routes.php
$router->group('/client/@clientId/job/@jobId', function(Router $router) {
// Este grupo abaixo ainda recebe o middleware do pai
// Mas os parâmetros são passados em um único array
// no middleware.
$router->group('/job/@jobId', function(Router $router) {
$router->get('', [ JobController::class, 'view' ]);
$router->put('', [ JobController::class, 'update' ]);
$router->delete('', [ JobController::class, 'delete' ]);
// mais rotas...
});
}, [ RouteSecurityMiddleware::class ]);
Agrupando Rotas com Middleware
Você pode adicionar um grupo de rota, e então toda rota nesse grupo terá o mesmo middleware também. Isso é útil se você precisar agrupar um monte de rotas por, digamos, um middleware de Auth para verificar a chave de API no header.
// adicionado no final do método de grupo
Flight::group('/api', function() {
// Esta rota "vazia" na verdade corresponderá a /api
Flight::route('', function() { echo 'api'; }, false, 'api');
// Isso corresponderá a /api/users
Flight::route('/users', function() { echo 'users'; }, false, 'users');
// Isso corresponderá a /api/users/1234
Flight::route('/users/@id', function($id) { echo 'user:'.$id; }, false, 'user_view');
}, [ new ApiAuthMiddleware() ]);
Se você quiser aplicar um middleware global a todas as suas rotas, você pode adicionar um grupo "vazio":
// adicionado no final do método de grupo
Flight::group('', function() {
// Isso ainda é /users
Flight::route('/users', function() { echo 'users'; }, false, 'users');
// E isso ainda é /users/1234
Flight::route('/users/@id', function($id) { echo 'user:'.$id; }, false, 'user_view');
}, [ ApiAuthMiddleware::class ]); // ou [ new ApiAuthMiddleware() ], mesma coisa
Casos de Uso Comuns
Validação de Chave de API
Se você quiser proteger suas rotas /api verificando se a chave de API está correta, você pode lidar com isso facilmente com middleware.
use flight\Engine;
class ApiMiddleware {
protected Engine $app;
public function __construct(Engine $app) {
$this->app = $app;
}
public function before(array $params) {
$authorizationHeader = $this->app->request()->getHeader('Authorization');
$apiKey = str_replace('Bearer ', '', $authorizationHeader);
// faça uma busca no seu banco de dados pela chave de api
$apiKeyHash = hash('sha256', $apiKey);
$hasValidApiKey = !!$this->db()->fetchField("SELECT 1 FROM api_keys WHERE hash = ? AND valid_date >= NOW()", [ $apiKeyHash ]);
if($hasValidApiKey !== true) {
$this->app->jsonHalt(['error' => 'Invalid API Key']);
}
}
}
// routes.php
$router->group('/api', function(Router $router) {
$router->get('/users', [ ApiController::class, 'getUsers' ]);
$router->get('/companies', [ ApiController::class, 'getCompanies' ]);
// mais rotas...
}, [ ApiMiddleware::class ]);
Agora todas as suas rotas de API estão protegidas por este middleware de validação de chave de API que você configurou! Se você adicionar mais rotas ao grupo do roteador, elas terão instantaneamente a mesma proteção!
Validação de Login
Você quer proteger algumas rotas para que estejam disponíveis apenas para usuários logados? Isso pode ser facilmente alcançado com middleware!
use flight\Engine;
class LoggedInMiddleware {
protected Engine $app;
public function __construct(Engine $app) {
$this->app = $app;
}
public function before(array $params) {
$session = $this->app->session();
if($session->get('logged_in') !== true) {
$this->app->redirect('/login');
exit;
}
}
}
// routes.php
$router->group('/admin', function(Router $router) {
$router->get('/dashboard', [ DashboardController::class, 'index' ]);
$router->get('/clients', [ ClientController::class, 'index' ]);
// mais rotas...
}, [ LoggedInMiddleware::class ]);
Validação de Parâmetro de Rota
Você quer proteger seus usuários de alterar valores na URL para acessar dados que não deveriam? Isso pode ser resolvido com middleware!
use flight\Engine;
class RouteSecurityMiddleware {
protected Engine $app;
public function __construct(Engine $app) {
$this->app = $app;
}
public function before(array $params) {
$clientId = $params['clientId'];
$jobId = $params['jobId'];
// execute uma busca de algum tipo no seu banco de dados
$isValid = !!$this->app->db()->fetchField("SELECT 1 FROM client_jobs WHERE client_id = ? AND job_id = ?", [ $clientId, $jobId ]);
if($isValid !== true) {
$this->app->halt(400, 'You are blocked, muahahaha!');
}
}
}
// routes.php
$router->group('/client/@clientId/job/@jobId', function(Router $router) {
$router->get('', [ JobController::class, 'view' ]);
$router->put('', [ JobController::class, 'update' ]);
$router->delete('', [ JobController::class, 'delete' ]);
// mais rotas...
}, [ RouteSecurityMiddleware::class ]);
Lidando com a Execução de Middleware
Digamos que você tenha um middleware de autenticação e queira redirecionar o usuário para uma página de login se ele não estiver autenticado. Você tem algumas opções à sua disposição:
- Você pode retornar false da função de middleware e Flight retornará automaticamente um erro 403 Forbidden, mas sem customização.
- Você pode redirecionar o usuário para uma página de login usando
Flight::redirect(). - Você pode criar um erro customizado dentro do middleware e interromper a execução da rota.
Simples e Direto
Aqui está um exemplo simples de return false; :
class MyMiddleware {
public function before($params) {
$hasUserKey = Flight::session()->exists('user');
if ($hasUserKey === false) {
return false;
}
// como é true, tudo continua
}
}
Exemplo de Redirecionamento
Aqui está um exemplo de redirecionar o usuário para uma página de login:
class MyMiddleware {
public function before($params) {
$hasUserKey = Flight::session()->exists('user');
if ($hasUserKey === false) {
Flight::redirect('/login');
exit;
}
}
}
Exemplo de Erro Customizado
Digamos que você precise lançar um erro JSON porque está construindo uma API. Você pode fazer isso assim:
class MyMiddleware {
public function before($params) {
$authorization = Flight::request()->getHeader('Authorization');
if(empty($authorization)) {
Flight::jsonHalt(['error' => 'You must be logged in to access this page.'], 403);
// ou
Flight::json(['error' => 'You must be logged in to access this page.'], 403);
exit;
// ou
Flight::halt(403, json_encode(['error' => 'You must be logged in to access this page.']);
}
}
}
Veja Também
- Routing - Como mapear rotas para controladores e renderizar views.
- Requests - Entendendo como lidar com requisições de entrada.
- Responses - Como customizar respostas HTTP.
- Dependency Injection - Simplificando a criação e gerenciamento de objetos em rotas.
- Why a Framework? - Entendendo os benefícios de usar um framework como Flight.
- Middleware Execution Strategy Example
Solução de Problemas
- Se você tiver um redirecionamento no seu middleware, mas sua app não parecer estar redirecionando, certifique-se de adicionar uma declaração
exit;no seu middleware.
Changelog
- v3.1: Adicionado suporte para middleware.
Learn/filtering
Filtragem
Visão Geral
O Flight permite que você filtre métodos mapeados antes e depois de serem chamados.
Entendendo
Não há ganchos predefinidos que você precise memorizar. Você pode filtrar qualquer um dos métodos padrão do framework, bem como qualquer método personalizado que você tenha mapeado.
Uma função de filtro se parece com esta:
/**
* @param array $params Os parâmetros passados para o método sendo filtrado.
* @param string $output (apenas buffer de saída v2) A saída do método sendo filtrado.
* @return bool Retorne true/void ou não retorne para continuar a cadeia, false para quebrar a cadeia.
*/
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 fazer um filtro executar antes de um método fazendo:
Flight::before('start', function (array &$params, string &$output): bool {
// Faça algo
});
Você pode fazer um filtro executar depois de 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 depois
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ê tiver definido múltiplos filtros, você pode quebrar a cadeia retornando false em qualquer uma de suas funções de filtro:
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 encerrará a cadeia
return false;
});
// Isso não será chamado
Flight::before('start', function (array &$params, string &$output): bool {
echo 'três';
return true;
});
Nota: Métodos principais como
maperegisternão podem ser filtrados porque são chamados diretamente e não invocados dinamicamente. Veja Estendendo o Flight para mais informações.
Veja Também
Solução de Problemas
- Certifique-se de retornar
falsede suas funções de filtro se quiser que a cadeia pare. Se você não retornar nada, a cadeia continuará.
Registro de Alterações
- v2.0 - Lançamento Inicial.
Learn/requests
Requests
Visão Geral
Flight encapsula a requisição HTTP em um único objeto, que pode ser acessado fazendo:
$request = Flight::request();
Compreendendo
As requisições HTTP são um dos aspectos principais a entender sobre o ciclo de vida HTTP. Um usuário realiza uma ação em um navegador web ou em um cliente HTTP, e eles enviam uma série de cabeçalhos, corpo, URL, etc. para o seu projeto. Você pode capturar esses cabeçalhos (a linguagem do navegador, que tipo de compressão eles podem lidar, o agente do usuário, etc.) e capturar o corpo e a URL que são enviados para a sua aplicação Flight. Essas requisições são essenciais para que o seu app entenda o que fazer em seguida.
Uso Básico
PHP possui vários super globais, incluindo $_GET, $_POST, $_REQUEST, $_SERVER, $_FILES e $_COOKIE. Flight abstrai esses em coleções práticas Collections. Você pode acessar as propriedades query, data, cookies e files como arrays ou objetos.
Nota: É ALTAMENTE desaconselhável usar esses super globais no seu projeto e eles devem ser referenciados através do objeto
request().
Nota: Não há abstração disponível para
$_ENV.
$_GET
Você pode acessar o array $_GET via a propriedade query:
// GET /search?keyword=something
Flight::route('/search', function(){
$keyword = Flight::request()->query['keyword'];
// ou
$keyword = Flight::request()->query->keyword;
echo "Você está procurando por: $keyword";
// consultar um banco de dados ou algo mais com o $keyword
});
$_POST
Você pode acessar o array $_POST via a propriedade data:
Flight::route('POST /submit', function(){
$name = Flight::request()->data['name'];
$email = Flight::request()->data['email'];
// ou
$name = Flight::request()->data->name;
$email = Flight::request()->data->email;
echo "Você enviou: $name, $email";
// salvar em um banco de dados ou algo mais com o $name e $email
});
$_COOKIE
Você pode acessar o array $_COOKIE via a propriedade cookies:
Flight::route('GET /login', function(){
$savedLogin = Flight::request()->cookies['myLoginCookie'];
// ou
$savedLogin = Flight::request()->cookies->myLoginCookie;
// verificar se foi realmente salvo ou não e se foi, fazer login automático
if($savedLogin) {
Flight::redirect('/dashboard');
return;
}
});
Para ajuda em definir novos valores de cookie, veja overclokk/cookie
$_SERVER
Há um atalho disponível para acessar o array $_SERVER via o método getVar():
$host = Flight::request()->getVar('HTTP_HOST');
$_FILES
Você pode acessar arquivos enviados via a propriedade files:
// acesso raw à propriedade $_FILES. Veja abaixo para a abordagem recomendada
$uploadedFile = Flight::request()->files['myFile'];
// ou
$uploadedFile = Flight::request()->files->myFile;
Veja Uploaded File Handler para mais informações.
Processando Uploads de Arquivos
v3.12.0
Você pode processar uploads de arquivos usando o framework com alguns métodos auxiliares. Basicamente, isso se resume a extrair os dados do arquivo da requisição e movê-lo para um novo local.
Flight::route('POST /upload', function(){
// Se você tivesse um campo de entrada como <input type="file" name="myFile">
$uploadedFileData = Flight::request()->getUploadedFiles();
$uploadedFile = $uploadedFileData['myFile'];
$uploadedFile->moveTo('/path/to/uploads/' . $uploadedFile->getClientFilename());
});
Se você tiver múltiplos arquivos enviados, você pode iterar por eles:
Flight::route('POST /upload', function(){
// Se você tivesse um campo de entrada como <input type="file" name="myFiles[]">
$uploadedFiles = Flight::request()->getUploadedFiles()['myFiles'];
foreach ($uploadedFiles as $uploadedFile) {
$uploadedFile->moveTo('/path/to/uploads/' . $uploadedFile->getClientFilename());
}
});
Nota de Segurança: Sempre valide e sanitize a entrada do usuário, especialmente ao lidar com uploads de arquivos. Sempre valide o tipo de extensões que você permitirá ser enviado, mas você também deve validar os "magic bytes" do arquivo para garantir que é realmente o tipo de arquivo que o usuário afirma que é. Há artigos e bibliotecas disponíveis para ajudar com isso.
Corpo da Requisição
Para obter o corpo raw da requisição HTTP, por exemplo ao lidar com requisições POST/PUT, você pode fazer:
Flight::route('POST /users/xml', function(){
$xmlBody = Flight::request()->getBody();
// faça algo com o XML que foi enviado.
});
Corpo JSON
Se você receber uma requisição com o tipo de conteúdo application/json e os dados de exemplo {"id": 123}
ele estará disponível na propriedade data:
$id = Flight::request()->data->id;
Cabeçalhos da Requisição
Você pode acessar os cabeçalhos da requisição usando o método getHeader() ou getHeaders():
// Talvez você precise do cabeçalho Authorization
$host = Flight::request()->getHeader('Authorization');
// ou
$host = Flight::request()->header('Authorization');
// Se você precisar pegar todos os cabeçalhos
$headers = Flight::request()->getHeaders();
// ou
$headers = Flight::request()->headers();
Método da Requisição
Você pode acessar o método da requisição usando a propriedade method ou o método getMethod():
$method = Flight::request()->method; // na verdade populado pelo getMethod()
$method = Flight::request()->getMethod();
Nota: O método getMethod() primeiro puxa o método de $_SERVER['REQUEST_METHOD'], então ele pode ser sobrescrito
por $_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'] se existir ou $_REQUEST['_method'] se existir.
Propriedades do Objeto Request
O objeto request fornece as seguintes propriedades:
- body - O corpo raw da requisição HTTP
- url - A URL sendo requisitada
- base - O subdiretório pai da URL
- method - O método da requisição (GET, POST, PUT, DELETE)
- referrer - A URL de referência
- ip - Endereço IP do cliente
- ajax - Se a requisição é uma requisição AJAX
- scheme - O protocolo do servidor (http, https)
- user_agent - Informações do navegador
- type - O tipo de conteúdo
- length - O comprimento do conteúdo
- query - Parâmetros da string de query
- data - Dados POST ou dados JSON
- cookies - Dados de cookie
- files - Arquivos enviados
- secure - Se a conexão é segura
- accept - Parâmetros HTTP accept
- proxy_ip - Endereço IP proxy do cliente. Escaneia o array
$_SERVERporHTTP_CLIENT_IP,HTTP_X_FORWARDED_FOR,HTTP_X_FORWARDED,HTTP_X_CLUSTER_CLIENT_IP,HTTP_FORWARDED_FOR,HTTP_FORWARDEDnessa ordem. - host - O nome do host da requisição
- servername - O SERVER_NAME de
$_SERVER
Métodos Auxiliares
Há alguns métodos auxiliares para juntar partes de uma URL, ou lidar com certos cabeçalhos.
URL Completa
Você pode acessar a URL completa da requisição usando o método getFullUrl():
$url = Flight::request()->getFullUrl();
// https://example.com/some/path?foo=bar
URL Base
Você pode acessar a URL base usando o método getBaseUrl():
// http://example.com/path/to/something/cool?query=yes+thanks
$url = Flight::request()->getBaseUrl();
// https://example.com
// Note, sem barra no final.
Análise de Query
Você pode passar uma URL para o método parseQuery() para analisar a string de query em um array associativo:
$query = Flight::request()->parseQuery('https://example.com/some/path?foo=bar');
// ['foo' => 'bar']
Negociar Tipos de Conteúdo Aceitos
v3.17.2
Você pode usar o método negotiateContentType() para determinar o melhor tipo de conteúdo para responder com base no cabeçalho Accept enviado pelo cliente.
// Exemplo de cabeçalho Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
// O abaixo define o que você suporta.
$availableTypes = ['application/json', 'application/xml'];
$typeToServe = Flight::request()->negotiateContentType($availableTypes);
if ($typeToServe === 'application/json') {
// Sirva resposta JSON
} elseif ($typeToServe === 'application/xml') {
// Sirva resposta XML
} else {
// Padrão para algo mais ou lance um erro
}
Nota: Se nenhum dos tipos disponíveis for encontrado no cabeçalho
Accept, o método retornaránull. Se não houver cabeçalhoAcceptdefinido, o método retornará o primeiro tipo no array$availableTypes.
Veja Também
- Routing - Veja como mapear rotas para controladores e renderizar views.
- Responses - Como personalizar respostas HTTP.
- Why a Framework? - Como as requisições se encaixam no quadro geral.
- Collections - Trabalhando com coleções de dados.
- Uploaded File Handler - Lidando com uploads de arquivos.
Solução de Problemas
request()->iperequest()->proxy_ippodem ser diferentes se o seu servidor web estiver atrás de um proxy, balanceador de carga, etc.
Changelog
- v3.17.2 - Adicionado negotiateContentType()
- v3.12.0 - Adicionada capacidade de lidar com uploads de arquivos através do objeto request.
- v1.0 - Lançamento inicial.
Learn/why_frameworks
Por que um Framework?
Alguns programadores são veementemente contra o uso de frameworks. Eles argumentam que os frameworks são inflados, lentos e difíceis de aprender. Dizem que os frameworks são desnecessários e que você pode escrever um código melhor sem eles. Certamente, há alguns pontos válidos a serem considerados sobre as desvantagens do uso de frameworks. No entanto, também existem muitas vantagens em usar frameworks.
Motivos para Usar um Framework
Aqui estão algumas razões pelas quais você pode querer considerar o uso de um framework:
- Desenvolvimento Rápido: Os frameworks fornecem muita funcionalidade pronta para uso. Isso significa que você pode construir aplicações web mais rapidamente. Você não precisa escrever tanto código, pois o framework fornece grande parte da funcionalidade necessária.
- Consistência: Os frameworks fornecem uma forma consistente de fazer as coisas. Isso facilita a compreensão de como o código funciona e torna mais fácil para outros desenvolvedores entenderem o seu código. Se você o tem script por script, pode perder a consistência entre scripts, especialmente se estiver trabalhando com uma equipe de desenvolvedores.
- Segurança: Os frameworks oferecem recursos de segurança que ajudam a proteger suas aplicações web contra ameaças de segurança comuns. Isso significa que você não precisa se preocupar tanto com a segurança, pois o framework cuida de grande parte disso para você.
- Comunidade: Os frameworks têm grandes comunidades de desenvolvedores que contribuem para o framework. Isso significa que você pode obter ajuda de outros desenvolvedores quando tiver perguntas ou problemas. Também significa que há muitos recursos disponíveis para ajudá-lo a aprender como usar o framework.
- Melhores Práticas: Os frameworks são construídos seguindo as melhores práticas. Isso significa que você pode aprender com o framework e usar as mesmas melhores práticas em seu próprio código. Isso pode ajudá-lo a se tornar um melhor programador. Às vezes, você não sabe o que não sabe e isso pode prejudicá-lo no final.
- Extensibilidade: Os frameworks são projetados para serem estendidos. Isso significa que você pode adicionar sua própria funcionalidade ao framework. Isso permite que você construa aplicações web adaptadas às suas necessidades específicas.
Flight é um micro-framework. Isso significa que ele é pequeno e leve. Ele não fornece tanta funcionalidade quanto frameworks maiores como Laravel ou Symfony. No entanto, ele fornece muita da 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 rapidamente e facilmente. Se você é novo em frameworks, o Flight é um ótimo framework para iniciantes a começar. Vai ajudá-lo a entender as vantagens de usar frameworks sem sobrecarregá-lo com muita complexidade. Depois de adquirir 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 um aplicativo robusto e bem-sucedido.
O Que é Roteamento?
O roteamento é o núcleo do framework Flight, mas o que é exatamente? Roteamento é o processo de pegar uma URL e correspondê-la a 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 ele visita /user/1234, mas mostrar uma lista de todos os usuários quando eles visitam /users. Tudo isso é feito por meio do roteamento.
Pode funcionar algo assim:
- Um usuário vai para o seu navegador e digita
http://exemplo.com/user/1234. - O servidor recebe a solicitação, olha para a URL e a passa para o seu código de aplicativo Flight.
- Digamos que em seu código Flight você tenha algo como
Flight::route('/user/@id', [ 'ControladorDeUsuario', 'verPerfilDoUsuario' ]);. Seu código de aplicativo Flight olha a URL e percebe que corresponde a uma rota que você definiu, e então executa o código que você definiu para essa rota. - O roteador do Flight vai então chamar o método
verPerfilDoUsuario($id)na classeControladorDeUsuario, passando o1234como argumento$idno método. - O código em seu método
verPerfilDoUsuario()então vai rodar e fazer o que você mandou fazer. Você pode acabar ecoando um pouco de HTML para a página do perfil do usuário, ou se isso for uma API RESTful, você pode ecoar uma resposta JSON com a informação do usuário. - O Flight embrulha isso em um laço bonito, gera os cabeçalhos de resposta e envia de volta para o navegador do usuário.
- O usuário fica cheio de alegria e dá a si mesmo um caloroso abraço!
E Por que é Importante?
Ter um roteador centralizado apropriado pode realmente tornar sua vida dramaticamente mais fácil! Pode ser difícil ver isso à primeira vista. Aqui estão algumas razões:
- Roteamento Centralizado: Você pode manter todas as suas rotas em um só lugar. Isso torna mais fácil ver quais rotas você tem e o que elas fazem. Também facilita alterá-las, se necessário.
- Parâmetros de Rota: Você pode usar parâmetros de rota para passar dados para seus métodos de rota. Esta é uma ótima maneira de manter seu código limpo e organizado.
- Grupos de Rotas: Você pode agrupar rotas juntas. Isso é ótimo para manter seu código organizado e para aplicar middleware a um grupo de rotas.
- Alias de Rota: Você pode atribuir um alias a uma rota, para que a URL possa ser gerada dinamicamente mais tarde em seu código (como um modelo, por exemplo). Ex: em vez de codificar
/user/1234no seu código, você poderia referenciar o aliasuser_viewe passar oidcomo parâmetro. Isso é maravilhoso no caso de decidir alterá-lo para/admin/user/1234posteriormente. Você não precisará mudar todas as suas URLs codificadas, apenas a URL associada à rota. - Middleware de Rota: Você pode adicionar middleware às suas rotas. O middleware é incrivelmente poderoso para adicionar comportamentos específicos à sua aplicação, como autenticar que um determinado usuário pode acessar uma rota ou grupo de rotas.
Tenho certeza de que você está familiarizado com a maneira script por script de criar um site. Você pode ter um arquivo chamado index.php que possui um monte de declarações if para verificar a URL e em seguida executar uma função específica com base na URL. Isso é uma forma de roteamento, mas não é muito organizado e pode sair do controle rapidamente. O sistema de roteamento do Flight é uma maneira muito mais organizada e poderosa de lidar com o roteamento.
Isto?
// /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', [ 'ControladorDeUsuario', 'verPerfilDoUsuario' ]);
Flight::route('/user/@id/edit', [ 'ControladorDeUsuario', 'editarPerfilDoUsuario' ]);
// Em talvez o seu app/controllers/ControladorDeUsuario.php
class ControladorDeUsuario {
public function verPerfilDoUsuario($id) {
// faça algo
}
public function editarPerfilDoUsuario($id) {
// faça algo
}
}
Espero que você comece a ver os benefícios de usar um sistema de roteamento centralizado. É muito mais fácil de gerenciar e entender a longo prazo!
Solicitações e Respostas
O Flight oferece 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 de um navegador de um usuário, a processa e envia de volta uma resposta. Com isso, você pode construir aplicações web que realizem tarefas como mostrar o perfil de um usuário, permitir que um usuário faça login, ou permitir que um usuário poste uma nova postagem em um blog.
Solicitações
Uma solicitação é o que o navegador de um usuário envia para o seu servidor quando eles visitam o seu site. Esta solicitação contém informações sobre o que o usuário quer fazer. Por exemplo, ela pode conter informações sobre qual URL o usuário deseja visitar, quais 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 lê-la.
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 esse 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.
Respostas
Uma resposta é o que o seu servidor envia de volta para o navegador de um usuário quando eles visitam o seu site. Esta resposta contém informações sobre o que o seu servidor quer fazer. Por exemplo, ela pode conter informações sobre que tipo de dados o seu servidor quer enviar para o usuário, que tipo de dados seu servidor quer receber do usuário, ou que tipo de dados seu servidor quer 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 enviar de volta para o usuário.
Learn/responses
Respostas
Visão Geral
Flight ajuda a gerar parte dos cabeçalhos de resposta para você, mas você mantém a maior parte do controle sobre o que envia de volta ao usuário. Na maioria das vezes, você acessará o objeto response() diretamente, mas Flight tem alguns métodos auxiliares para definir alguns dos cabeçalhos de resposta para você.
Entendendo
Após o usuário enviar sua request para sua aplicação, você precisa gerar uma resposta apropriada para eles. Eles enviaram informações como a linguagem que preferem, se podem lidar com certos tipos de compressão, seu agente de usuário, etc., e após processar tudo, é hora de enviar de volta uma resposta apropriada. Isso pode ser definir cabeçalhos, produzir um corpo de HTML ou JSON para eles, ou redirecioná-los para uma página.
Uso Básico
Enviando um Corpo de Resposta
Flight usa ob_start() para armazenar em buffer a saída. Isso significa que você pode usar echo ou print para enviar uma resposta ao usuário e Flight a capturará e a enviará de volta ao usuário com os cabeçalhos apropriados.
// Isso enviará "Hello, World!" para o navegador do usuário
Flight::route('/', function() {
echo "Hello, World!";
});
// HTTP/1.1 200 OK
// Content-Type: text/html
//
// Hello, World!
Como alternativa, você pode chamar o método write() para adicionar ao corpo também.
// Isso enviará "Hello, World!" para o navegador do usuário
Flight::route('/', function() {
// verboso, mas faz o trabalho às vezes quando você precisa
Flight::response()->write("Hello, World!");
// se você quiser recuperar o corpo que definiu neste ponto
// você pode fazer assim
$body = Flight::response()->getBody();
});
JSON
Flight fornece suporte para enviar respostas JSON e JSONP. Para enviar uma resposta JSON, você passa alguns dados para serem codificados em JSON:
Flight::route('/@companyId/users', function(int $companyId) {
// de alguma forma, extraia seus usuários de um banco de dados, por exemplo
$users = Flight::db()->fetchAll("SELECT id, first_name, last_name FROM users WHERE company_id = ?", [ $companyId ]);
Flight::json($users);
});
// [{"id":1,"first_name":"Bob","last_name":"Jones"}, /* more users */ ]
Nota: Por padrão, Flight enviará um cabeçalho
Content-Type: application/jsoncom a resposta. Ele também usará as flagsJSON_THROW_ON_ERROReJSON_UNESCAPED_SLASHESao codificar o JSON.
JSON com Código de Status
Você também pode passar um código de status como o segundo argumento:
Flight::json(['id' => 123], 201);
JSON com Impressão Bonita
Você também pode passar um argumento na última posição para ativar a impressão bonita:
Flight::json(['id' => 123], 200, true, 'utf-8', JSON_PRETTY_PRINT);
Alterando a Ordem dos Argumentos JSON
Flight::json() é um método muito antigo, mas o objetivo do Flight é manter a compatibilidade retroativa
para projetos. Na verdade, é muito simples se você quiser refazer a ordem dos argumentos para usar uma sintaxe mais simples,
você pode apenas remapear o método JSON como qualquer outro método do Flight:
Flight::map('json', function($data, $code = 200, $options = 0) {
// agora você não precisa de `true, 'utf-8'` ao usar o método json()!
Flight::_json($data, $code, true, 'utf-8', $options);
}
// E agora pode ser usado assim
Flight::json(['id' => 123], 200, JSON_PRETTY_PRINT);
JSON e Parando a Execução
v3.10.0
Se você quiser enviar uma resposta JSON e parar 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 uma resposta JSON imediatamente, limpar o conteúdo do corpo existente
e parar a execução.
Flight::route('/users', function() {
$authorized = someAuthorizationCheck();
// Verifica se o usuário está autorizado
if($authorized === false) {
Flight::jsonHalt(['error' => 'Unauthorized'], 401);
// sem exit; necessário aqui.
}
// Continua com o resto da rota
});
Antes da v3.10.0, você teria que fazer algo assim:
Flight::route('/users', function() {
$authorized = someAuthorizationCheck();
// Verifica se o usuário está autorizado
if($authorized === false) {
Flight::halt(401, json_encode(['error' => 'Unauthorized']));
}
// Continua com o resto da rota
});
Limpando um Corpo de Resposta
Se você quiser limpar o corpo da resposta, você pode usar o método clearBody:
Flight::route('/', function() {
if($someCondition) {
Flight::response()->write("Hello, World!");
} else {
Flight::response()->clearBody();
}
});
O caso de uso acima provavelmente não é comum, no entanto, poderia ser mais comum se isso fosse usado em um middleware.
Executando um Callback no Corpo da Resposta
Você pode executar um callback no corpo da resposta usando o método addResponseBodyCallback:
Flight::route('/users', function() {
$db = Flight::db();
$users = $db->fetchAll("SELECT * FROM users");
Flight::render('users_table', ['users' => $users]);
});
// Isso fará gzip em todas as respostas para qualquer rota
Flight::response()->addResponseBodyCallback(function($body) {
return gzencode($body, 9);
});
Você pode adicionar múltiplos callbacks e eles serão executados na ordem em que foram adicionados. Como isso pode aceitar qualquer callable, pode aceitar um array de classe [ $class, 'method' ], uma closure $strReplace = function($body) { str_replace('hi', 'there', $body); };, ou um nome de função 'minify' se você tivesse uma função para minificar seu código HTML, por exemplo.
Nota: Callbacks de rota não funcionarão se você estiver usando a opção de configuração flight.v2.output_buffering.
Callback de Rota Específica
Se você quisesse que isso se aplicasse apenas a uma rota específica, poderia adicionar o callback na própria rota:
Flight::route('/users', function() {
$db = Flight::db();
$users = $db->fetchAll("SELECT * FROM users");
Flight::render('users_table', ['users' => $users]);
// Isso fará gzip apenas na resposta desta rota
Flight::response()->addResponseBodyCallback(function($body) {
return gzencode($body, 9);
});
});
Opção de Middleware
Você também pode usar middleware para aplicar o callback a todas as rotas via middleware:
// MinifyMiddleware.php
class MinifyMiddleware {
public function before() {
// Aplique o callback aqui no objeto response().
Flight::response()->addResponseBodyCallback(function($body) {
return $this->minify($body);
});
}
protected function minify(string $body): string {
// minifique o corpo de alguma forma
return $body;
}
}
// index.php
Flight::group('/users', function() {
Flight::route('', function() { /* ... */ });
Flight::route('/@id', function($id) { /* ... */ });
}, [ new MinifyMiddleware() ]);
Códigos de Status
Você pode definir o código de status da resposta usando o método status:
Flight::route('/@id', function($id) {
if($id == 123) {
Flight::response()->status(200);
echo "Hello, World!";
} else {
Flight::response()->status(403);
echo "Forbidden";
}
});
Se você quiser obter o código de status atual, pode usar o método status sem argumentos:
Flight::response()->status(); // 200
Definindo um Cabeçalho de Resposta
Você pode definir um cabeçalho, como o tipo de conteúdo da resposta, usando o método header:
// Isso enviará "Hello, World!" para o navegador do usuário em texto plano
Flight::route('/', function() {
Flight::response()->header('Content-Type', 'text/plain');
// ou
Flight::response()->setHeader('Content-Type', 'text/plain');
echo "Hello, World!";
});
Redirecionamento
Você pode redirecionar a solicitação atual usando o método redirect() e passando
uma nova URL:
Flight::route('/login', function() {
$username = Flight::request()->data->username;
$password = Flight::request()->data->password;
$passwordConfirm = Flight::request()->data->password_confirm;
if($password !== $passwordConfirm) {
Flight::redirect('/new/location');
return; // isso é necessário para que a funcionalidade abaixo não execute
}
// adicione o novo usuário...
Flight::db()->runQuery("INSERT INTO users ....");
Flight::redirect('/admin/dashboard');
});
Nota: Por padrão, Flight envia um código de status HTTP 303 ("See Other"). Você pode opcionalmente definir um código personalizado:
Flight::redirect('/new/location', 301); // permanente
Parando a Execução da Rota
Você pode parar o framework e sair imediatamente em qualquer ponto chamando o método halt:
Flight::halt();
Você também pode especificar um código de status HTTP opcional e mensagem:
Flight::halt(200, 'Be right back...');
Chamar halt descartará qualquer conteúdo de resposta até aquele ponto e parará toda a execução.
Se você quiser parar o framework e produzir a resposta atual, use o método stop:
Flight::stop($httpStatusCode = null);
Nota:
Flight::stop()tem algum comportamento estranho, como produzir a resposta, mas continuar executando seu script, o que pode não ser o que você deseja. Você pode usarexitoureturnapós chamarFlight::stop()para prevenir execução adicional, mas geralmente é recomendado usarFlight::halt().
Isso salvará a chave e o valor do cabeçalho no objeto de resposta. No final do ciclo de vida da solicitação, ele construirá os cabeçalhos e enviará uma resposta.
Uso Avançado
Enviando um Cabeçalho Imediatamente
Pode haver momentos em que você precisa fazer algo personalizado com o cabeçalho e precisa enviar o cabeçalho
na própria linha de código com a qual está trabalhando. Se você estiver definindo uma rota transmitida,
isso é o que você precisaria. Isso é alcançável através de response()->setRealHeader().
Flight::route('/', function() {
Flight::response()->setRealHeader('Content-Type: text/plain');
echo 'Streaming response...';
sleep(5);
echo 'Done!';
})->stream();
JSONP
Para solicitações JSONP, você pode opcionalmente passar o nome do parâmetro de consulta que está usando para definir sua função de callback:
Flight::jsonp(['id' => 123], 'q');
Então, ao fazer uma solicitação GET usando ?q=my_func, você deve receber a saída:
my_func({"id":123});
Se você não passar um nome de parâmetro de consulta, ele usará jsonp por padrão.
Nota: Se você ainda estiver usando solicitações JSONP em 2025 e além, entre no chat e nos diga por quê! Adoramos ouvir algumas boas histórias de batalha/horror!
Limpando Dados de Resposta
Você pode limpar o corpo da resposta e cabeçalhos usando o método clear(). Isso limpará
quaisquer cabeçalhos atribuídos à resposta, limpará o corpo da resposta e definirá o código de status para 200.
Flight::response()->clear();
Limpando Apenas o Corpo da Resposta
Se você quiser limpar apenas o corpo da resposta, pode usar o método clearBody():
// Isso ainda manterá quaisquer cabeçalhos definidos no objeto response().
// Isso ainda manterá quaisquer cabeçalhos definidos no objeto response().
Flight::response()->clearBody();
Cache HTTP
Flight fornece suporte integrado para cache no nível HTTP. Se a condição de cache
for atendida, Flight retornará uma resposta HTTP 304 Not Modified. Na próxima vez que o
cliente solicitar o mesmo recurso, eles serão solicitados a usar sua versão em cache local.
Cache no Nível de Rota
Se você quiser armazenar em cache toda a sua resposta, pode usar o método cache() e passar o tempo para cache.
// Isso armazenará em cache a resposta por 5 minutos
Flight::route('/news', function () {
Flight::response()->cache(time() + 300);
echo 'This content will be cached.';
});
// Alternativamente, você pode usar uma string que passaria
// para o método strtotime()
Flight::route('/news', function () {
Flight::response()->cache('+5 minutes');
echo 'This content will be cached.';
});
Last-Modified
Você pode usar o método lastModified e passar um timestamp UNIX para definir a data
e hora em que uma página foi modificada pela última vez. O cliente continuará usando seu cache até
que o valor da última modificação seja alterado.
Flight::route('/news', function () {
Flight::lastModified(1234567890);
echo 'This content will be cached.';
});
ETag
O cache ETag é semelhante ao Last-Modified, exceto que você pode especificar qualquer ID que
desejar para o recurso:
Flight::route('/news', function () {
Flight::etag('my-unique-id');
echo 'This content will be cached.';
});
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, Flight enviará imediatamente
uma resposta HTTP 304 e parará o processamento.
Baixar um Arquivo
v3.12.0
Há um método auxiliar para transmitir um arquivo ao usuário final. Você pode usar o método download e passar o caminho.
Flight::route('/download', function () {
Flight::download('/path/to/file.txt');
// A partir da v3.17.1, você pode especificar um nome de arquivo personalizado para o download
Flight::download('/path/to/file.txt', 'custom_name.txt');
});
Veja Também
- Routing - Como mapear rotas para controladores e renderizar visualizações.
- Requests - Entendendo como lidar com solicitações de entrada.
- Middleware - Usando middleware com rotas para autenticação, logging, etc.
- Por que um Framework? - Entendendo os benefícios de usar um framework como Flight.
- Extending - Como estender Flight com sua própria funcionalidade.
Solução de Problemas
- Se você estiver tendo problemas com redirecionamentos não funcionando, certifique-se de adicionar um
return;ao método. stop()ehalt()não são a mesma coisa.halt()parará a execução imediatamente, enquantostop()permitirá que a execução continue.
Changelog
- v3.17.1 - Adicionado
$fileNameao métododownloadFile(). - v3.12.0 - Adicionado método auxiliar downloadFile.
- v3.10.0 - Adicionado
jsonHalt. - v1.0 - Lançamento inicial.
Learn/events
Gerenciador de Eventos
a partir da v3.15.0
Visão Geral
Os eventos permitem que você registre e dispare comportamentos personalizados em sua aplicação. Com a adição de Flight::onEvent() e Flight::triggerEvent(), você pode agora se conectar a momentos chave do ciclo de vida da sua aplicação ou definir seus próprios eventos (como notificações e e-mails) para tornar seu código mais modular e extensível. Esses métodos fazem parte dos métodos mapeáveis da Flight, o que significa que você pode sobrescrever seu comportamento para atender às suas necessidades.
Entendendo
Os eventos permitem que você separe diferentes partes da sua aplicação para que elas não dependam excessivamente umas das outras. Essa separação — frequentemente chamada de desacoplamento — torna seu código mais fácil de atualizar, estender ou depurar. Em vez de escrever tudo em um grande bloco, você pode dividir sua lógica em peças menores e independentes que respondem a ações específicas (eventos).
Imagine que você está construindo uma aplicação de blog:
- Quando um usuário posta um comentário, você pode querer:
- Salvar o comentário no banco de dados.
- Enviar um e-mail para o proprietário do blog.
- Registrar a ação para segurança.
Sem eventos, você enfiaria tudo isso em uma única função. Com eventos, você pode dividi-lo: uma parte salva o comentário, outra dispara um evento como 'comment.posted', e ouvintes separados lidam com o e-mail e o registro. Isso mantém seu código mais limpo e permite que você adicione ou remova recursos (como notificações) sem tocar na lógica principal.
Casos de Uso Comuns
Na maior parte do tempo, os eventos são bons para coisas que são opcionais, mas não uma parte absolutamente central do seu sistema. Por exemplo, os seguintes são bons de ter, mas se eles falharem por algum motivo, sua aplicação ainda deve funcionar:
- Registro: Registrar ações como logins ou erros sem bagunçar seu código principal.
- Notificações: Enviar e-mails ou alertas quando algo acontece.
- Atualizações de Cache: Atualizar caches ou notificar outros sistemas sobre mudanças.
No entanto, digamos que você tenha um recurso de esquecimento de senha. Isso deve fazer parte da funcionalidade principal e não ser um evento, porque se esse e-mail não for enviado, o usuário não pode redefinir a senha e usar sua aplicação.
Uso Básico
O sistema de eventos da Flight é construído em torno de dois métodos principais: Flight::onEvent() para registrar ouvintes de eventos e Flight::triggerEvent() para disparar eventos. Aqui está como você pode usá-los:
Registrando Ouvintes de Eventos
Para escutar um evento, use Flight::onEvent(). Esse método permite que você defina o que deve acontecer quando um evento ocorre.
Flight::onEvent(string $event, callable $callback): void
$event: Um nome para o seu evento (ex.:'user.login').$callback: A função a ser executada quando o evento é disparado.
Você "se inscreve" em um evento ao dizer à Flight o que fazer quando ele acontece. O callback pode aceitar argumentos passados do disparo do evento.
O sistema de eventos da Flight é síncrono, o que significa que cada ouvinte de evento é executado em sequência, um após o outro. Quando você dispara um evento, todos os ouvintes registrados para esse evento serão executados até o fim antes que seu código continue. Isso é importante de entender, pois difere de sistemas de eventos assíncronos onde os ouvintes podem rodar em paralelo ou em um momento posterior.
Exemplo Simples
Flight::onEvent('user.login', function ($username) {
echo "Bem-vindo de volta, $username!";
// você pode enviar um e-mail se o login for de um novo local
});
Aqui, quando o evento 'user.login' é disparado, ele cumprimentará o usuário pelo nome e também pode incluir lógica para enviar um e-mail se necessário.
Nota: O callback pode ser uma função, uma função anônima ou um método de uma classe.
Disparando Eventos
Para fazer um evento acontecer, use Flight::triggerEvent(). Isso diz à Flight para executar todos os ouvintes registrados para esse evento, passando qualquer dado que você fornecer.
Flight::triggerEvent(string $event, ...$args): void
$event: O nome do evento que você está disparando (deve corresponder a um evento registrado)....$args: Argumentos opcionais para enviar aos ouvintes (pode ser qualquer número de argumentos).
Exemplo Simples
$username = 'alice';
Flight::triggerEvent('user.login', $username);
Isso dispara o evento 'user.login' e envia 'alice' para o ouvinte que definimos anteriormente, o que exibirá: Welcome back, alice!.
- Se nenhum ouvinte estiver registrado, nada acontece — sua aplicação não quebrará.
- Use o operador de espalhamento (
...) para passar múltiplos argumentos de forma flexível.
Parando Eventos
Se um ouvinte retornar false, nenhum ouvinte adicional para esse evento será executado. Isso permite que você pare a cadeia de eventos com base em condições específicas. Lembre-se, a ordem dos ouvintes importa, pois o primeiro a retornar false impedirá o resto de rodar.
Exemplo:
Flight::onEvent('user.login', function ($username) {
if (isBanned($username)) {
logoutUser($username);
return false; // Para ouvintes subsequentes
}
});
Flight::onEvent('user.login', function ($username) {
sendWelcomeEmail($username); // isso nunca é enviado
});
Sobrescrevendo Métodos de Eventos
Flight::onEvent() e Flight::triggerEvent() estão disponíveis para serem estendidos, o que significa que você pode redefinir como eles funcionam. Isso é ótimo para usuários avançados que querem personalizar o sistema de eventos, como adicionar registro ou alterar como os eventos são despachados.
Exemplo: Personalizando onEvent
Flight::map('onEvent', function (string $event, callable $callback) {
// Registra toda inscrição de evento
error_log("Novo ouvinte de evento adicionado para: $event");
// Chama o comportamento padrão (assumindo um sistema de eventos interno)
Flight::_onEvent($event, $callback);
});
Agora, toda vez que você registrar um evento, ele será registrado antes de prosseguir.
Por Que Sobrescrever?
- Adicionar depuração ou monitoramento.
- Restringir eventos em certos ambientes (ex.: desabilitar em testes).
- Integrar com uma biblioteca de eventos diferente.
Onde Colocar Seus Eventos
Se você é novo nos conceitos de eventos no seu projeto, pode se perguntar: onde eu registro todos esses eventos na minha aplicação? A simplicidade da Flight significa que não há uma regra estrita — você pode colocá-los onde fizer sentido para o seu projeto. No entanto, mantê-los organizados ajuda a manter seu código à medida que sua aplicação cresce. Aqui estão algumas opções práticas e melhores práticas, adaptadas à natureza leve da Flight:
Opção 1: No Seu index.php Principal
Para aplicações pequenas ou protótipos rápidos, você pode registrar eventos diretamente no seu arquivo index.php junto com suas rotas. Isso mantém tudo em um só lugar, o que é bom quando a simplicidade é a prioridade.
require 'vendor/autoload.php';
// Registra eventos
Flight::onEvent('user.login', function ($username) {
error_log("$username logged in at " . date('Y-m-d H:i:s'));
});
// Define rotas
Flight::route('/login', function () {
$username = 'bob';
Flight::triggerEvent('user.login', $username);
echo "Logged in!";
});
Flight::start();
- Prós: Simples, sem arquivos extras, ótimo para projetos pequenos.
- Contras: Pode ficar bagunçado à medida que sua aplicação cresce com mais eventos e rotas.
Opção 2: Um Arquivo events.php Separado
Para uma aplicação um pouco maior, considere mover os registros de eventos para um arquivo dedicado como app/config/events.php. Inclua este arquivo no seu index.php antes das suas rotas. Isso imita como as rotas são frequentemente organizadas em app/config/routes.php em projetos Flight.
// app/config/events.php
Flight::onEvent('user.login', function ($username) {
error_log("$username logged in at " . date('Y-m-d H:i:s'));
});
Flight::onEvent('user.registered', function ($email, $name) {
echo "Email sent to $email: Welcome, $name!";
});
// index.php
require 'vendor/autoload.php';
require 'app/config/events.php';
Flight::route('/login', function () {
$username = 'bob';
Flight::triggerEvent('user.login', $username);
echo "Logged in!";
});
Flight::start();
- Prós: Mantém o
index.phpfocado em roteamento, organiza eventos logicamente, fácil de encontrar e editar. - Contras: Adiciona um pouquinho de estrutura, o que pode parecer exagero para aplicações muito pequenas.
Opção 3: Perto de Onde Eles São Disparados
Outra abordagem é registrar eventos perto de onde eles são disparados, como dentro de um controlador ou definição de rota. Isso funciona bem se um evento for específico para uma parte da sua aplicação.
Flight::route('/signup', function () {
// Registra evento aqui
Flight::onEvent('user.registered', function ($email) {
echo "Welcome email sent to $email!";
});
$email = 'jane@example.com';
Flight::triggerEvent('user.registered', $email);
echo "Signed up!";
});
- Prós: Mantém o código relacionado junto, bom para recursos isolados.
- Contras: Espalha os registros de eventos, tornando mais difícil ver todos os eventos de uma vez; risco de registros duplicados se não for cuidadoso.
Melhor Prática para Flight
- Comece Simples: Para aplicações minúsculas, coloque eventos no
index.php. É rápido e alinha com o minimalismo da Flight. - Cresça de Forma Inteligente: À medida que sua aplicação expande (ex.: mais de 5-10 eventos), use um arquivo
app/config/events.php. É um passo natural, como organizar rotas, e mantém seu código organizado sem adicionar frameworks complexos. - Evite Superengenharia: Não crie uma classe ou diretório “gerenciador de eventos” completo a menos que sua aplicação fique enorme — a Flight prospera na simplicidade, então mantenha leve.
Dica: Agrupe por Propósito
Em events.php, agrupe eventos relacionados (ex.: todos os eventos relacionados a usuários juntos) com comentários para clareza:
// app/config/events.php
// Eventos de Usuário
Flight::onEvent('user.login', function ($username) {
error_log("$username logged in");
});
Flight::onEvent('user.registered', function ($email) {
echo "Welcome to $email!";
});
// Eventos de Página
Flight::onEvent('page.updated', function ($pageId) {
Flight::cache()->delete("page_$pageId");
});
Essa estrutura escala bem e permanece amigável para iniciantes.
Exemplos do Mundo Real
Vamos percorrer alguns cenários do mundo real para mostrar como os eventos funcionam e por que eles são úteis.
Exemplo 1: Registrando um Login de Usuário
// Passo 1: Registra um ouvinte
Flight::onEvent('user.login', function ($username) {
$time = date('Y-m-d H:i:s');
error_log("$username logged in at $time");
});
// Passo 2: Dispare-o na sua aplicação
Flight::route('/login', function () {
$username = 'bob'; // Finja que isso vem de um formulário
Flight::triggerEvent('user.login', $username);
echo "Hi, $username!";
});
Por Que É Útil: O código de login não precisa saber sobre o registro — ele apenas dispara o evento. Você pode adicionar mais ouvintes depois (ex.: enviar um e-mail de boas-vindas) sem alterar a rota.
Exemplo 2: Notificando Sobre Novos Usuários
// Ouvinte para novos registros
Flight::onEvent('user.registered', function ($email, $name) {
// Simula o envio de um e-mail
echo "Email sent to $email: Welcome, $name!";
});
// Dispare quando alguém se inscreve
Flight::route('/signup', function () {
$email = 'jane@example.com';
$name = 'Jane';
Flight::triggerEvent('user.registered', $email, $name);
echo "Thanks for signing up!";
});
Por Que É Útil: A lógica de inscrição foca em criar o usuário, enquanto o evento lida com notificações. Você pode adicionar mais ouvintes (ex.: registrar a inscrição) depois.
Exemplo 3: Limpando um Cache
// Ouvinte para limpar um cache
Flight::onEvent('page.updated', function ($pageId) {
// se usando o plugin flightphp/cache
Flight::cache()->delete("page_$pageId");
echo "Cache cleared for page $pageId.";
});
// Dispare quando uma página é editada
Flight::route('/edit-page/(@id)', function ($pageId) {
// Finja que atualizamos a página
Flight::triggerEvent('page.updated', $pageId);
echo "Page $pageId updated.";
});
Por Que É Útil: O código de edição não se importa com cache — ele apenas sinaliza a atualização. Outras partes da aplicação podem reagir conforme necessário.
Melhores Práticas
- Nomeie Eventos Claramente: Use nomes específicos como
'user.login'ou'page.updated'para que seja óbvio o que eles fazem. - Mantenha Ouvintes Simples: Não coloque tarefas lentas ou complexas em ouvintes — mantenha sua aplicação rápida.
- Teste Seus Eventos: Dispare-os manualmente para garantir que os ouvintes funcionem como esperado.
- Use Eventos com Sabedoria: Eles são ótimos para desacoplamento, mas muitos podem tornar seu código difícil de seguir — use-os quando fizer sentido.
O sistema de eventos na Flight PHP, com Flight::onEvent() e Flight::triggerEvent(), dá a você uma forma simples, mas poderosa, de construir aplicações flexíveis. Ao permitir que diferentes partes da sua aplicação se comuniquem através de eventos, você pode manter seu código organizado, reutilizável e fácil de expandir. Seja registrando ações, enviando notificações ou gerenciando atualizações, os eventos ajudam você a fazer isso sem emaranhar sua lógica. Além disso, com a capacidade de sobrescrever esses métodos, você tem a liberdade de adaptar o sistema às suas necessidades. Comece pequeno com um único evento e veja como ele transforma a estrutura da sua aplicação!
Eventos Integrados
A Flight PHP vem com alguns eventos integrados que você pode usar para se conectar ao ciclo de vida do framework. Esses eventos são disparados em pontos específicos do ciclo de solicitação/resposta, permitindo que você execute lógica personalizada quando certas ações ocorrem.
Lista de Eventos Integrados
- flight.request.received:
function(Request $request)Disparado quando uma solicitação é recebida, analisada e processada. - flight.error:
function(Throwable $exception)Disparado quando um erro ocorre durante o ciclo de vida da solicitação. - flight.redirect:
function(string $url, int $status_code)Disparado quando um redirecionamento é iniciado. - flight.cache.checked:
function(string $cache_key, bool $hit, float $executionTime)Disparado quando o cache é verificado para uma chave específica e se houve acerto ou falha no cache. - flight.middleware.before:
function(Route $route)Disparado após a execução do middleware before. - flight.middleware.after:
function(Route $route)Disparado após a execução do middleware after. - flight.middleware.executed:
function(Route $route, $middleware, string $method, float $executionTime)Disparado após a execução de qualquer middleware - flight.route.matched:
function(Route $route)Disparado quando uma rota é correspondida, mas ainda não executada. - flight.route.executed:
function(Route $route, float $executionTime)Disparado após uma rota ser executada e processada.$executionTimeé o tempo que levou para executar a rota (chamar o controlador, etc). - flight.view.rendered:
function(string $template_file_path, float $executionTime)Disparado após uma view ser renderizada.$executionTimeé o tempo que levou para renderizar o template. Nota: Se você sobrescrever o métodorender, precisará disparar novamente este evento. - flight.response.sent:
function(Response $response, float $executionTime)Disparado após uma resposta ser enviada ao cliente.$executionTimeé o tempo que levou para construir a resposta.
Veja Também
- Estendendo a Flight - Como estender e personalizar a funcionalidade principal da Flight.
- Cache - Exemplo de uso de eventos para limpar o cache quando uma página é atualizada.
Solução de Problemas
- Se você não estiver vendo seus ouvintes de eventos sendo chamados, certifique-se de registrá-los antes de disparar os eventos. A ordem de registro importa.
Registro de Alterações
- v3.15.0 - Adicionados eventos à Flight.
Learn/templates
Visualizações HTML e Templates
Visão Geral
O Flight fornece algumas funcionalidades básicas de templating HTML por padrão. Templating é uma forma muito eficaz de desconectar sua lógica de aplicação da camada de apresentação. Um motor dedicado (Twig, Latte, etc.) também dá às ferramentas de codificação de IA uma sintaxe familiar e restrita, para que tenham menos probabilidade de despejar lógica de negócios em seu HTML.
Entendimento
Quando você está construindo uma aplicação, provavelmente terá HTML que deseja entregar ao usuário final. O PHP por si só é uma linguagem de templating, mas é muito fácil envolver lógica de negócio, como chamadas de banco de dados, chamadas de API, etc., em seu arquivo HTML e tornar o teste e o desacoplamento um processo muito difícil. Ao empurrar dados para um template e deixar o template se renderizar, fica muito mais fácil desacoplar e testar unitariamente seu código. Você nos agradecerá se usar templates!
Uso Básico
O Flight permite que você troque o motor de visualização padrão simplesmente mapeando render (ou registrando uma classe de visualização). Role para baixo para Twig, Latte, Smarty, Blade e mais.
Padrão do skeleton: O flightphp/skeleton oficial usa apenas Twig em
app/views/(*.twig). Os controladores chamam$this->app->render('welcome', $data)(extensão opcional). Isso é uma escolha de aplicação para novos projetos — não um requisito do núcleo do Flight. Latte e outros motores permanecem totalmente suportados.
Twig
padrão do skeleton
Twig é um motor de template flexível, rápido e seguro usado pelo Symfony e muitos outros projetos PHP. Ferramentas de codificação de IA tendem a conhecer Twig especialmente bem, e ele escapa a saída automaticamente por padrão, o que ajuda a proteger contra XSS.
Instalação
composer require twig/twig
(Já incluído quando você executa composer create-project flightphp/skeleton.)
Configuração Básica
Sobrescreva o método render para usar Twig em vez do renderizador PHP padrão:
// sobrescreve o método render para usar Twig em vez do renderizador PHP padrão
Flight::map('render', function(string $template, array $data): void {
$loader = new \Twig\Loader\FilesystemLoader(Flight::get('flight.views.path'));
$twig = new \Twig\Environment($loader, [
// Onde o Twig armazena seus templates compilados
'cache' => __DIR__ . '/../cache/twig',
'auto_reload' => true,
]);
// Permite "welcome" ou "welcome.twig"
if (substr($template, -5) !== '.twig') {
$template .= '.twig';
}
echo $twig->render($template, $data);
});
No skeleton, essa ligação fica em app/config/services.php (ambiente Twig compartilhado, caminho de cache, globais como base_url / nonce CSP). Prefira injetar Engine e chamar $app->render() a partir dos controladores para que o código permaneça amigável a IA e a testes.
Usando Twig no Flight
Agora que você pode renderizar com Twig, você pode fazer algo assim:
{# app/views/home.twig #}
<html>
<head>
<title>{% if title %}{{ title }} - {% endif %}My App</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<h1>Hello, {{ name }}!</h1>
</body>
</html>
// routes.php
Flight::route('/@name', function ($name) {
Flight::render('home.twig', [
'title' => 'Home Page',
'name' => $name
]);
});
Quando você visitar /Bob no seu navegador, a saída seria:
<html>
<head>
<title>Home Page - My App</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<h1>Hello, Bob!</h1>
</body>
</html>
Leitura Adicional
Um exemplo mais completo de uso do Twig com layouts é mostrado na seção plugins incríveis desta documentação. Para métricas de tempo de renderização na barra do Tracy, consulte o painel Twig nas Extensões Tracy.
Você pode aprender mais sobre todos os recursos do Twig lendo a documentação oficial.
Latte
ótima alternativa
Latte é um motor completo com sintaxe semelhante ao PHP. Ainda é uma excelente escolha para aplicativos Flight; o skeleton simplesmente padroniza o Twig como um padrão compartilhado (especialmente útil quando ferramentas de IA geram templates).
Instalação
composer require latte/latte
Configuração Básica
A ideia principal é que você sobrescreva o método render para usar Latte em vez do renderizador PHP padrão.
// sobrescreve o método render para usar latte em vez do renderizador PHP padrão
Flight::map('render', function(string $template, array $data, ?string $block): void {
$latte = new Latte\Engine;
// Onde o latte armazena especificamente seu cache
$latte->setTempDirectory(__DIR__ . '/../cache/');
$finalPath = Flight::get('flight.views.path') . $template;
$latte->render($finalPath, $data, $block);
});
Usando Latte no Flight
Agora que você pode renderizar com Latte, você pode fazer algo assim:
<!-- app/views/home.latte -->
<html>
<head>
<title>{$title ? $title . ' - '}My App</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<h1>Hello, {$name}!</h1>
</body>
</html>
// routes.php
Flight::route('/@name', function ($name) {
Flight::render('home.latte', [
'title' => 'Home Page',
'name' => $name
]);
});
Quando você visitar /Bob no seu navegador, a saída seria:
<html>
<head>
<title>Home Page - My App</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<h1>Hello, Bob!</h1>
</body>
</html>
Leitura Adicional
Um exemplo mais complexo de uso do Latte com layouts é mostrado na seção plugins incríveis desta documentação.
Você pode aprender mais sobre todos os recursos do Latte, incluindo tradução e recursos de idioma, lendo a documentação oficial.
Motor de Visualização Integrado
descontinuado
Nota: Embora ainda seja a funcionalidade padrão e ainda funcione tecnicamente.
Para exibir um template de visualização, chame o método render com o nome do arquivo de template e dados opcionais do template:
Flight::render('hello.php', ['name' => 'Bob']);
Os dados do template que você passa são automaticamente injetados no template e podem ser referenciados como uma variável local. Arquivos de template são simplesmente arquivos PHP. Se o conteúdo do arquivo de template hello.php for:
Hello, <?= $name ?>!
A saída seria:
Hello, 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:
Flight::render('hello');
Observe que, ao especificar o nome do template no método render, você pode omitir a extensão .php.
Por padrão, o Flight procura um diretório views para arquivos de template. Você pode definir um caminho alternativo para seus templates definindo a seguinte configuração:
Flight::set('flight.views.path', '/path/to/views');
Layouts
É comum que sites tenham um único arquivo de template de layout com conteúdo intercambiável. Para renderizar conteúdo a ser usado em um layout, você pode passar um parâmetro opcional ao método render.
Flight::render('header', ['heading' => 'Hello'], 'headerContent');
Flight::render('body', ['body' => 'World'], 'bodyContent');
Sua visualização terá então variáveis salvas chamadas headerContent e bodyContent. Você pode então renderizar seu layout fazendo:
Flight::render('layout', ['title' => 'Home Page']);
Se os arquivos de template se parecerem com isto:
header.php:
<h1><?= $heading ?></h1>
body.php:
<div><?= $body ?></div>
layout.php:
<html>
<head>
<title><?= $title ?></title>
</head>
<body>
<?= $headerContent ?>
<?= $bodyContent ?>
</body>
</html>
A saída seria:
<html>
<head>
<title>Home Page</title>
</head>
<body>
<h1>Hello</h1>
<div>World</div>
</body>
</html>
Smarty
Veja como você usaria o motor de template Smarty para suas visualizações:
// Carrega a biblioteca Smarty
require './Smarty/libs/Smarty.class.php';
// Registra o Smarty como a classe de visualização
// Também passa uma função de retorno 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/');
});
// Atribui dados do template
Flight::view()->assign('name', 'Bob');
// Exibe o template
Flight::view()->display('hello.tpl');
Para completude, você também deve sobrescrever 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);
});
Blade
Veja como você usaria o motor de template Blade para suas visualizações:
Primeiro, você precisa instalar a biblioteca BladeOne via Composer:
composer require eftec/bladeone
Então, você pode configurar o BladeOne como a classe de visualização no Flight:
<?php
// Carrega a biblioteca BladeOne
use eftec\bladeone\BladeOne;
// Registra o BladeOne como a classe de visualização
// Também passa uma função de retorno para configurar o BladeOne ao carregar
Flight::register('view', BladeOne::class, [], function (BladeOne $blade) {
$views = __DIR__ . '/../views';
$cache = __DIR__ . '/../cache';
$blade->setPath($views);
$blade->setCompiledPath($cache);
});
// Atribui dados do template
Flight::view()->share('name', 'Bob');
// Exibe o template
echo Flight::view()->run('hello', []);
Para completude, você também deve sobrescrever o método de renderização padrão do Flight:
<?php
Flight::map('render', function(string $template, array $data): void {
echo Flight::view()->run($template, $data);
});
Neste exemplo, o arquivo de template hello.blade.php pode se parecer com isto:
<?php
Hello, {{ $name }}!
A saída seria:
Hello, Bob!
Veja Também
- Instalação - Estrutura do skeleton (
app/views/*.twig) para novos projetos. - Estendendo - Como sobrescrever o método
renderpara usar um motor de template diferente. - Roteamento - Como mapear rotas para controladores e renderizar visualizações.
- Respostas - Como personalizar respostas HTTP.
- Segurança - Auto escaping e XSS.
- IA e Experiência do Desenvolvedor - Por que um padrão de motor de visualização ajuda agentes de codificação.
- Por que um Framework? - Como os templates se encaixam no panorama geral.
Solução de Problemas
- Se você tiver um redirecionamento em seu middleware, mas seu aplicativo não parecer estar redirecionando, certifique-se de adicionar uma instrução
exit;em seu middleware. - Se o Twig não conseguir encontrar um template, verifique
flight.views.pathe se o arquivo existe nesse caminho com a extensão esperada (skeleton:app/views/).
Changelog
- Docs – Twig documentado como o padrão oficial do skeleton; Latte permanece como uma alternativa de primeira classe.
- v2.0 - Lançamento inicial.
Learn/simple_pdo
Classe Auxiliar SimplePdo PDO
Visão Geral
A classe SimplePdo no Flight é um auxiliar moderno e rico em recursos para trabalhar com bancos de dados usando PDO. Ela estende PdoWrapper e adiciona métodos auxiliares convenientes para operações comuns de banco de dados, como insert(), update(), delete() e transações. Ela simplifica tarefas de banco de dados, retorna resultados como Collections para acesso fácil e suporta registro de consultas e monitoramento de desempenho de aplicação (APM) para casos de uso avançados.
Entendendo
A classe SimplePdo é projetada para tornar o trabalho com bancos de dados em PHP muito mais fácil. Em vez de lidar com declarações preparadas, modos de busca e operações SQL verbosas, você obtém métodos limpos e simples para tarefas comuns. Cada linha é retornada como uma Collection, para que você possa usar tanto notação de array ($row['name']) quanto notação de objeto ($row->name).
Esta classe é um superconjunto de PdoWrapper, o que significa que ela inclui toda a funcionalidade de PdoWrapper mais métodos auxiliares adicionais que tornam seu código mais limpo e mais fácil de manter. Se você está usando PdoWrapper atualmente, atualizar para SimplePdo é direto, pois ela estende PdoWrapper.
Você pode registrar SimplePdo como um serviço compartilhado no Flight e, em seguida, usá-lo em qualquer lugar em seu app via Flight::db().
Uso Básico
Registrando SimplePdo
Primeiro, registre a classe SimplePdo com o Flight:
Flight::register('db', \flight\database\SimplePdo::class, [
'mysql:host=localhost;dbname=cool_db_name', 'user', 'pass', [
PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES \'utf8mb4\'',
PDO::ATTR_EMULATE_PREPARES => false,
PDO::ATTR_STRINGIFY_FETCHES => false,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
]
]);
NOTA
Se você não especificar
PDO::ATTR_DEFAULT_FETCH_MODE,SimplePdoo definirá automaticamente comoPDO::FETCH_ASSOCpara você.
Agora você pode usar Flight::db() em qualquer lugar para obter sua conexão com o banco de dados.
Executando Consultas
runQuery()
function runQuery(string $sql, array $params = []): PDOStatement
Use isso para INSERTs, UPDATEs ou quando você quiser buscar resultados manualmente:
$db = Flight::db();
$statement = $db->runQuery("SELECT * FROM users WHERE status = ?", ['active']);
while ($row = $statement->fetch()) {
// $row é um array
}
Você também pode usá-lo para gravações:
$db->runQuery("INSERT INTO users (name) VALUES (?)", ['Alice']);
$db->runQuery("UPDATE users SET name = ? WHERE id = ?", ['Bob', 1]);
fetchField()
function fetchField(string $sql, array $params = []): mixed
Obtenha um único valor do banco de dados:
$count = Flight::db()->fetchField("SELECT COUNT(*) FROM users WHERE status = ?", ['active']);
fetchRow()
function fetchRow(string $sql, array $params = []): ?Collection
Obtenha uma única linha como uma Collection (acesso array/objeto):
$user = Flight::db()->fetchRow("SELECT * FROM users WHERE id = ?", [123]);
echo $user['name'];
// ou
echo $user->name;
DICA
SimplePdoadiciona automaticamenteLIMIT 1às consultasfetchRow()se não estiver presente, tornando suas consultas mais eficientes.
fetchAll()
function fetchAll(string $sql, array $params = []): array<Collection>
Obtenha todas as linhas como um array de Collections:
$users = Flight::db()->fetchAll("SELECT * FROM users WHERE status = ?", ['active']);
foreach ($users as $user) {
echo $user['name'];
// ou
echo $user->name;
}
fetchColumn()
function fetchColumn(string $sql, array $params = []): array
Busque uma única coluna como um array:
$ids = Flight::db()->fetchColumn("SELECT id FROM users WHERE active = ?", [1]);
// Retorna: [1, 2, 3, 4, 5]
fetchPairs()
function fetchPairs(string $sql, array $params = []): array
Busque resultados como pares chave-valor (primeira coluna como chave, segunda como valor):
$userNames = Flight::db()->fetchPairs("SELECT id, name FROM users");
// Retorna: [1 => 'John', 2 => 'Jane', 3 => 'Bob']
Usando Placeholders IN()
Você pode usar um único ? em uma cláusula IN() e passar um array:
$ids = [1, 2, 3];
$users = Flight::db()->fetchAll("SELECT * FROM users WHERE id IN (?)", [$ids]);
Métodos Auxiliares
Uma das principais vantagens do SimplePdo sobre PdoWrapper é a adição de métodos auxiliares convenientes para operações comuns de banco de dados.
insert()
function insert(string $table, array $data): string
Insira uma ou mais linhas e retorne o último ID de inserção.
Inserção única:
$id = Flight::db()->insert('users', [
'name' => 'John',
'email' => 'john@example.com'
]);
Inserção em massa:
$id = Flight::db()->insert('users', [
['name' => 'John', 'email' => 'john@example.com'],
['name' => 'Jane', 'email' => 'jane@example.com'],
]);
update()
function update(string $table, array $data, string $where, array $whereParams = []): int
Atualize linhas e retorne o número de linhas afetadas:
$affected = Flight::db()->update(
'users',
['name' => 'Jane', 'email' => 'jane@example.com'],
'id = ?',
[1]
);
NOTA
O
rowCount()do SQLite retorna o número de linhas onde os dados realmente mudaram. Se você atualizar uma linha com os mesmos valores que ela já tem,rowCount()retornará 0. Isso difere do comportamento do MySQL ao usarPDO::MYSQL_ATTR_FOUND_ROWS.
delete()
function delete(string $table, string $where, array $whereParams = []): int
Exclua linhas e retorne o número de linhas excluídas:
$deleted = Flight::db()->delete('users', 'id = ?', [1]);
transaction()
function transaction(callable $callback): mixed
Execute um callback dentro de uma transação. A transação é confirmada automaticamente em caso de sucesso ou revertida em caso de erro:
$result = Flight::db()->transaction(function($db) {
$db->insert('users', ['name' => 'John']);
$db->insert('logs', ['action' => 'user_created']);
return $db->lastInsertId();
});
Se qualquer exceção for lançada dentro do callback, a transação é revertida automaticamente e a exceção é relançada.
Uso Avançado
Registro de Consultas & APM
Se você quiser rastrear o desempenho de consultas, habilite o rastreamento de APM ao registrar:
Flight::register('db', \flight\database\SimplePdo::class, [
'mysql:host=localhost;dbname=cool_db_name',
'user',
'pass',
[/* opções PDO */],
[
'trackApmQueries' => true,
'maxQueryMetrics' => 1000
]
]);
Após executar consultas, você pode registrá-las manualmente, mas o APM as registrará automaticamente se habilitado:
Flight::db()->logQueries();
Isso acionará um evento (flight.db.queries) com métricas de conexão e consulta, que você pode escutar usando o sistema de eventos do Flight.
Exemplo Completo
Flight::route('/users', function () {
// Obter todos os usuários
$users = Flight::db()->fetchAll('SELECT * FROM users');
// Transmitir todos os usuários
$statement = Flight::db()->runQuery('SELECT * FROM users');
while ($user = $statement->fetch()) {
echo $user['name'];
}
// Obter um único usuário
$user = Flight::db()->fetchRow('SELECT * FROM users WHERE id = ?', [123]);
// Obter um único valor
$count = Flight::db()->fetchField('SELECT COUNT(*) FROM users');
// Obter uma única coluna
$ids = Flight::db()->fetchColumn('SELECT id FROM users');
// Obter pares chave-valor
$userNames = Flight::db()->fetchPairs('SELECT id, name FROM users');
// Sintaxe especial IN()
$users = Flight::db()->fetchAll('SELECT * FROM users WHERE id IN (?)', [[1,2,3,4,5]]);
// Inserir um novo usuário
$id = Flight::db()->insert('users', [
'name' => 'Bob',
'email' => 'bob@example.com'
]);
// Inserção em massa de usuários
Flight::db()->insert('users', [
['name' => 'Bob', 'email' => 'bob@example.com'],
['name' => 'Jane', 'email' => 'jane@example.com']
]);
// Atualizar um usuário
$affected = Flight::db()->update('users', ['name' => 'Bob'], 'id = ?', [123]);
// Excluir um usuário
$deleted = Flight::db()->delete('users', 'id = ?', [123]);
// Usar uma transação
$result = Flight::db()->transaction(function($db) {
$db->insert('users', ['name' => 'John', 'email' => 'john@example.com']);
$db->insert('audit_log', ['action' => 'user_created']);
return $db->lastInsertId();
});
});
Migrando de PdoWrapper
Se você está usando PdoWrapper atualmente, migrar para SimplePdo é direto:
-
Atualize seu registro:
// Antigo Flight::register('db', \flight\database\PdoWrapper::class, [ /* ... */ ]); // Novo Flight::register('db', \flight\database\SimplePdo::class, [ /* ... */ ]); -
Todos os métodos existentes de
PdoWrapperfuncionam noSimplePdo- Não há mudanças que quebrem o código. Seu código existente continuará funcionando. -
Opcionalmente, use os novos métodos auxiliares - Comece a usar
insert(),update(),delete()etransaction()para simplificar seu código.
Veja Também
- Collections - Aprenda como usar a classe Collection para acesso fácil aos dados.
- PdoWrapper - A classe auxiliar PDO legada (depreciada).
Solução de Problemas
- Se você receber um erro sobre conexão com o banco de dados, verifique seu DSN, nome de usuário, senha e opções.
- Todas as linhas são retornadas como Collections—se você precisar de um array simples, use
$collection->getData(). - Para consultas
IN (?), certifique-se de passar um array. - Se você estiver enfrentando problemas de memória com registro de consultas em processos de longa duração, ajuste a opção
maxQueryMetrics.
Registro de Alterações
- v3.18.0 - Lançamento inicial do SimplePdo com métodos auxiliares para insert, update, delete e transações.
Learn/collections
Coleções
Visão Geral
A classe Collection no Flight é um utilitário útil para gerenciar conjuntos de dados. Ela permite acessar e manipular dados usando tanto a notação de array quanto a de objeto, tornando seu código mais limpo e flexível.
Entendendo
Uma Collection é basicamente um invólucro em torno de um array, mas com alguns poderes extras. Você pode usá-la como um array, percorrê-la, contar seus itens e até acessar itens como se fossem propriedades de objeto. Isso é especialmente útil quando você deseja passar dados estruturados em seu aplicativo ou quando quer tornar seu código um pouco mais legível.
As coleções implementam várias interfaces do PHP:
ArrayAccess(então você pode usar sintaxe de array)Iterator(então você pode percorrer comforeach)Countable(então você pode usarcount())JsonSerializable(então você pode facilmente converter para JSON)
Uso Básico
Criando uma Coleção
Você pode criar uma coleção simplesmente passando um array para seu construtor:
use flight\util\Collection;
$data = [
'name' => 'Flight',
'version' => 3,
'features' => ['routing', 'views', 'extending']
];
$collection = new Collection($data);
Acessando Itens
Você pode acessar itens usando a notação de array ou de objeto:
// Notação de array
echo $collection['name']; // Saída: FlightPHP
// Notação de objeto
echo $collection->version; // Saída: 3
Se você tentar acessar uma chave que não existe, obterá null em vez de um erro.
Definindo Itens
Você também pode definir itens usando qualquer uma das notações:
// Notação de array
$collection['author'] = 'Mike Cao';
// Notação de objeto
$collection->license = 'MIT';
Verificando e Removendo Itens
Verifique se um item existe:
if (isset($collection['name'])) {
// Faça algo
}
if (isset($collection->version)) {
// Faça algo
}
Remova um item:
unset($collection['author']);
unset($collection->license);
Percorrendo uma Coleção
As coleções são iteráveis, então você pode usá-las em um loop foreach:
foreach ($collection as $key => $value) {
echo "$key: $value\n";
}
Contando Itens
Você pode contar o número de itens em uma coleção:
echo count($collection); // Saída: 4
Obtendo Todas as Chaves ou Dados
Obtenha todas as chaves:
$keys = $collection->keys(); // ['name', 'version', 'features', 'license']
Obtenha todos os dados como um array:
$data = $collection->getData();
Limpando a Coleção
Remova todos os itens:
$collection->clear();
Serialização JSON
As coleções podem ser facilmente convertidas para JSON:
echo json_encode($collection);
// Saída: {"name":"FlightPHP","version":3,"features":["routing","views","extending"],"license":"MIT"}
Uso Avançado
Você pode substituir o array de dados interno completamente, se necessário:
$collection->setData(['foo' => 'bar']);
As coleções são especialmente úteis quando você deseja passar dados estruturados entre componentes, ou quando deseja fornecer uma interface mais orientada a objetos para dados de array.
Veja Também
- Requisições - Aprenda como lidar com requisições HTTP e como as coleções podem ser usadas para gerenciar dados de requisição.
- SimplePdo - Auxiliar de banco de dados que retorna linhas de consulta como Coleções.
Solução de Problemas
- Se você tentar acessar uma chave que não existe, obterá
nullem vez de um erro. - Lembre-se de que as coleções não são recursivas: arrays aninhados não são convertidos automaticamente em coleções.
- Se você precisar redefinir a coleção, use
$collection->clear()ou$collection->setData([]).
Registro de Alterações
- v3.0 - Melhorias nas dicas de tipo e suporte ao PHP 8+.
- v1.0 - Lançamento inicial da classe Collection.
Learn/flight_vs_fat_free
Flight vs Fat-Free
O que é o Fat-Free?
Fat-Free (carinhosamente conhecido como F3) é um micro-framework PHP poderoso e fácil de usar, projetado para ajudá-lo a construir aplicações web dinâmicas e robustas - rápido!
O Flight se compara ao Fat-Free de várias maneiras e provavelmente é o primo mais próximo em termos de recursos e simplicidade. O Fat-Free tem muitos recursos que o Flight não tem, mas também tem muitos recursos que o Flight tem. O Fat-Free está começando a mostrar sua idade e não é mais tão popular quanto já foi.
As atualizações estão se tornando menos frequentes e a comunidade não está tão ativa quanto antes. O código é simples o suficiente, mas às vezes a falta de disciplina na sintaxe pode dificultar a leitura e o entendimento. Ele funciona com PHP 8.3, mas o código em si ainda parece que vive em PHP 5.3.
Prós em comparação ao Flight
- O Fat-Free tem algumas estrelas a mais no GitHub do que o Flight.
- O Fat-Free tem uma documentação razoável, mas deixa a desejar em algumas áreas com clareza.
- O Fat-Free tem alguns recursos escassos, como tutoriais no YouTube e artigos online que podem ser usados para aprender o framework.
- O Fat-Free tem alguns plugins úteis integrados que às vezes são úteis.
- O Fat-Free tem um ORM integrado chamado Mapper que pode ser usado para interagir com seu banco de dados. O Flight tem active-record.
- O Fat-Free tem Sessões, Cache e localização integrados. O Flight exige que você use bibliotecas de terceiros, mas isso é coberto na documentação.
- O Fat-Free tem um pequeno grupo de plugins criados pela comunidade que podem ser usados para estender o framework. O Flight tem alguns cobertos na página de documentação e de exemplos.
- O Fat-Free, assim como o Flight, não tem dependências.
- O Fat-Free, assim como o Flight, é voltado para dar ao desenvolvedor controle sobre sua aplicação e uma experiência de desenvolvimento simples.
- O Fat-Free mantém compatibilidade reversa como o Flight faz (parcialmente porque as atualizações estão se tornando menos frequentes).
- O Fat-Free, assim como o Flight, é destinado a desenvolvedores que estão se aventurando no mundo dos frameworks pela primeira vez.
- O Fat-Free tem um mecanismo de template integrado que é mais robusto que o mecanismo de template do Flight. O Flight recomenda Latte para conseguir isso.
- O Fat-Free tem um comando CLI exclusivo do tipo "rota" onde você pode construir aplicativos CLI dentro do próprio Fat-Free e tratá-lo muito como uma requisição
GET. O Flight consegue isso com runway.
Contras em comparação ao Flight
- O Fat-Free tem alguns testes de implementação e até possui sua própria classe de teste que é muito básica. No entanto, não é 100% testado por unidade como o Flight é.
- Você tem que usar um mecanismo de busca como o Google para realmente pesquisar no site da documentação.
- O Flight tem modo escuro no site da documentação. (deixa o microfone cair)
- O Fat-Free tem alguns módulos que estão lamentavelmente sem manutenção.
- O Flight tem SimplePdo para acesso a banco de dados, que é um pouco mais simples que a classe
DB\SQLintegrada do Fat-Free (e preferido em relação ao PdoWrapper descontinuado). - O Flight tem um plugin de permissões que pode ser usado para proteger sua aplicação. O Fat Free exige que você use uma biblioteca de terceiros.
- O Flight tem um ORM chamado active-record que parece mais um ORM do que o Mapper do Fat-Free.
O benefício adicional do
active-recordé que você pode definir relacionamentos entre registros para junções automáticas, enquanto o Mapper do Fat-Free exige que você crie views SQL. - Surpreendentemente, o Fat-Free não tem um namespace raiz. O Flight tem namespace de ponta a ponta para não colidir com seu próprio código.
a classe
Cacheé a maior ofensora aqui. - O Fat-Free não tem middleware. Em vez disso, existem hooks
beforerouteeafterrouteque podem ser usados para filtrar requisições e respostas nos controladores. - O Fat-Free não pode agrupar rotas.
- O Fat-Free tem um manipulador de contêiner de injeção de dependência, mas a documentação é incrivelmente escassa sobre como usá-lo.
- A depuração pode ficar um pouco complicada, pois basicamente tudo é armazenado no que é chamado de
HIVE
Learn/extending
Estendendo
Visão Geral
Flight é 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é substitua classes e métodos existentes.
Compreendendo
Existem 2 maneiras de você estender a funcionalidade do Flight:
- Mapeamento de Métodos - Isso é usado para criar métodos personalizados simples que você pode chamar de qualquer lugar em sua aplicação. Esses são tipicamente usados para funções utilitárias que você deseja poder chamar de qualquer lugar em seu código.
- Registrando Classes - Isso é usado para registrar suas próprias classes com o Flight. Isso é tipicamente usado para classes que têm dependências ou requerem configuração.
Você também pode substituir métodos existentes do framework para alterar seu comportamento padrão para melhor atender às necessidades do seu projeto.
Se você está procurando um DIC (Container de Injeção de Dependência), pule para a página Container de Injeção de Dependência.
Uso Básico
Substituindo Métodos do Framework
Flight permite que você substitua sua funcionalidade padrão para atender às suas próprias necessidades, sem precisar modificar nenhum código. Você pode ver todos os métodos que pode substituir abaixo.
Por exemplo, quando o Flight não consegue corresponder 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() {
// Exibe página 404 personalizada
include 'errors/404.html';
});
Flight também permite que você substitua componentes principais do framework. Por exemplo, você pode substituir a classe Router padrão pela sua própria classe personalizada:
// crie sua classe Router personalizada
class MyRouter extends \flight\net\Router {
// substitua métodos aqui
// por exemplo, um atalho para requisições GET para remover
// o recurso de passagem de rota
public function get($pattern, $callback, $alias = '') {
return parent::get($pattern, $callback, false, $alias);
}
}
// Registre sua classe personalizada
Flight::register('router', MyRouter::class);
// Quando o Flight carrega a instância do Router, ele carregará sua classe
$myRouter = Flight::router();
$myRouter->get('/hello', function() {
echo "Hello World!";
}, 'hello_alias');
Métodos do framework como map e register, no entanto, não podem ser substituídos. Você receberá
um erro se tentar fazer isso (novamente, veja abaixo para uma lista de métodos).
Métodos Mapeáveis do Framework
A seguir está 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 principais para o framework e não podem ser substituídos.
Flight::map(string $name, callable $callback, bool $pass_route = false) // Cria um método personalizado do framework.
Flight::register(string $name, string $class, array $params = [], ?callable $callback = null) // Registra uma classe a um método do framework.
Flight::unregister(string $name) // Desregistra uma classe de 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 após um método do framework.
Flight::path(string $path) // Adiciona um caminho para carregamento automático de classes.
Flight::get(string $key) // Obtém uma variável definida por Flight::set().
Flight::set(string $key, mixed $value) // Define uma variável dentro do engine do Flight.
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 requisiçã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
Métodos Extensíveis
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, string $alias = '') // Mapeia um padrão de URL para um callback.
Flight::post(string $pattern, callable $callback, bool $pass_route = false, string $alias = '') // Mapeia um padrão de URL de requisição POST para um callback.
Flight::put(string $pattern, callable $callback, bool $pass_route = false, string $alias = '') // Mapeia um padrão de URL de requisição PUT para um callback.
Flight::patch(string $pattern, callable $callback, bool $pass_route = false, string $alias = '') // Mapeia um padrão de URL de requisição PATCH para um callback.
Flight::delete(string $pattern, callable $callback, bool $pass_route = false, string $alias = '') // Mapeia um padrão de URL de requisição DELETE para um callback.
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 baseada em um alias de rota.
Flight::redirect(string $url, int $code) // Redireciona para outra URL.
Flight::download(string $filePath) // Baixa um arquivo.
Flight::render(string $file, array $data, ?string $key = null) // Renderiza um arquivo de template.
Flight::error(Throwable $error) // Envia uma resposta HTTP 500.
Flight::notFound() // Envia uma resposta HTTP 404.
Flight::etag(string $id, string $type = 'string') // Realiza cache HTTP ETag.
Flight::lastModified(int $time) // Realiza cache 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.
Flight::jsonHalt(mixed $data, int $code = 200, bool $encode = true, string $charset = 'utf8', int $option) // Envia uma resposta JSON e para o framework.
Flight::onEvent(string $event, callable $callback) // Registra um ouvinte de evento.
Flight::triggerEvent(string $event, ...$args) // Dispara um evento.
Quaisquer métodos personalizados adicionados com map e register também podem ser filtrados. Para exemplos de como filtrar esses métodos, veja o guia Filtrando Métodos.
Classes Extensíveis do Framework
Existem várias classes que você pode substituir a funcionalidade estendendo-as e registrando sua própria classe. Essas classes são:
Flight::app() // Classe de Aplicação - estenda a classe flight\Engine
Flight::request() // Classe de Requisição - estenda a classe flight\net\Request
Flight::response() // Classe de Resposta - estenda a classe flight\net\Response
Flight::router() // Classe de Roteador - estenda a classe flight\net\Router
Flight::view() // Classe de Visualização - estenda a classe flight\template\View
Flight::eventDispatcher() // Classe de Dispatcher de Eventos - estenda a classe flight\core\Dispatcher
Mapeando Métodos Personalizados
Para mapear seu próprio método personalizado simples, você usa a função map:
// Mapeie seu método
Flight::map('hello', function (string $name) {
echo "hello $name!";
});
// Chame seu método personalizado
Flight::hello('Bob');
Embora seja possível criar métodos personalizados simples, é recomendado apenas criar funções padrão em PHP. Isso tem autocompletar em IDEs e é mais fácil de ler. O equivalente do código acima seria:
function hello(string $name) {
echo "hello $name!";
}
hello('Bob');
Isso é usado mais quando você precisa passar variáveis para o seu método para obter um
valor esperado. Usar o método register() como abaixo é mais para passar configuração
e depois chamar sua classe pré-configurada.
Registrando Classes Personalizadas
Para registrar sua própria classe e configurá-la, você usa a função register. A vantagem que isso tem sobre map() é que você pode reutilizar a mesma classe quando chamar essa função (seria útil com Flight::db() para compartilhar a mesma instância).
// Registre sua classe
Flight::register('user', User::class);
// Obtenha uma instância da sua classe
$user = Flight::user();
O método register também permite que você passe parâmetros para o construtor da sua classe. Então, quando você carrega sua classe personalizada, ela virá 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:
// Registre a classe com parâmetros do construtor
Flight::register('db', PDO::class, ['mysql:host=localhost;dbname=test', 'user', 'pass']);
// Obtenha uma instância da sua classe
// Isso criará um objeto com os parâmetros definidos
//
// new PDO('mysql:host=localhost;dbname=test','user','pass');
//
$db = Flight::db();
// e se você precisar dela mais tarde no seu código, você apenas chama o mesmo método novamente
class SomeController {
public function __construct() {
$this->db = Flight::db();
}
}
Se você passar um parâmetro de callback adicional, ele será executado imediatamente após a construção da classe. Isso permite que você execute qualquer procedimento de configuração para o seu novo objeto. A função de callback 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', 'user', 'pass'],
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, simplesmente passe false como parâmetro:
// Instância compartilhada da classe
$shared = Flight::db();
// Nova instância da classe
$new = Flight::db(false);
Nota: Lembre-se de que os métodos mapeados têm precedência sobre as classes registradas. Se você declarar ambos usando o mesmo nome, apenas o método mapeado será invocado.
Exemplos
Aqui estão alguns exemplos de como você pode estender o Flight com funcionalidades que não estão integradas no núcleo.
Logging
Flight não tem um sistema de logging integrado, no entanto, é realmente fácil usar uma biblioteca de logging com o Flight. Aqui está um exemplo usando a biblioteca Monolog:
// services.php
// Registre o logger com o Flight
Flight::register('log', Monolog\Logger::class, [ 'name' ], function(Monolog\Logger $log) {
$log->pushHandler(new Monolog\Handler\StreamHandler('path/to/your.log', Monolog\Logger::WARNING));
});
Agora que está registrado, você pode usá-lo em sua aplicação:
// Em seu controlador ou rota
Flight::log()->warning('This is a warning message');
Isso registrará uma mensagem no arquivo de log que você especificou. E se você quiser registrar algo quando um
erro ocorrer? Você pode usar o método error:
// Em seu controlador ou rota
Flight::map('error', function(Throwable $ex) {
Flight::log()->error($ex->getMessage());
// Exiba sua página de erro personalizada
include 'errors/500.html';
});
Você também poderia criar um sistema básico de APM (Monitoramento de Desempenho de Aplicação)
usando os métodos before e after:
// Em seu arquivo services.php
Flight::before('start', function() {
Flight::set('start_time', microtime(true));
});
Flight::after('start', function() {
$end = microtime(true);
$start = Flight::get('start_time');
Flight::log()->info('Request '.Flight::request()->url.' took ' . round($end - $start, 4) . ' seconds');
// Você também poderia adicionar os cabeçalhos de requisição ou resposta
// para registrá-los também (tenha cuidado, pois isso seria um
// monte de dados se você tiver muitas requisições)
Flight::log()->info('Request Headers: ' . json_encode(Flight::request()->headers));
Flight::log()->info('Response Headers: ' . json_encode(Flight::response()->headers));
});
Cache
Flight não tem um sistema de cache integrado, no entanto, é realmente fácil usar uma biblioteca de cache com o Flight. Aqui está um exemplo usando a PHP File Cache biblioteca:
// services.php
// Registre o cache com o Flight
Flight::register('cache', \flight\Cache::class, [ __DIR__ . '/../cache/' ], function(\flight\Cache $cache) {
$cache->setDevMode(ENVIRONMENT === 'development');
});
Agora que está registrado, você pode usá-lo em sua aplicação:
// Em seu controlador ou rota
$data = Flight::cache()->get('my_cache_key');
if (empty($data)) {
// Faça algum processamento para obter os dados
$data = [ 'some' => 'data' ];
Flight::cache()->set('my_cache_key', $data, 3600); // cache por 1 hora
}
Instanciação Fácil de Objetos DIC
Se você está usando um DIC (Container de Injeção de Dependência) em sua aplicação, você pode usar o Flight para ajudá-lo a instanciar seus objetos. Aqui está um exemplo usando a biblioteca Dice:
// services.php
// crie um novo container
$container = new \Dice\Dice;
// não esqueça de reatribuí-lo a si mesmo como abaixo!
$container = $container->addRule('PDO', [
// shared significa que o mesmo objeto será retornado a cada vez
'shared' => true,
'constructParams' => ['mysql:host=localhost;dbname=test', 'user', 'pass' ]
]);
// agora podemos criar um método mapeável para criar qualquer objeto.
Flight::map('make', function($class, $params = []) use ($container) {
return $container->create($class, $params);
});
// Isso registra o manipulador de container para que o Flight saiba usá-lo para controladores/middleware
Flight::registerContainerHandler(function($class, $params) {
Flight::make($class, $params);
});
// digamos que tenhamos a seguinte classe de exemplo que recebe um objeto PDO no construtor
class EmailCron {
protected PDO $pdo;
public function __construct(PDO $pdo) {
$this->pdo = $pdo;
}
public function send() {
// código que envia um email
}
}
// E finalmente você pode criar objetos usando injeção de dependência
$emailCron = Flight::make(EmailCron::class);
$emailCron->send();
Legal, né?
Veja Também
- Container de Injeção de Dependência - Como usar um DIC com o Flight.
- File Cache - Exemplo de uso de uma biblioteca de cache com o Flight.
Solução de Problemas
- Lembre-se de que os métodos mapeados têm precedência sobre as classes registradas. Se você declarar ambos usando o mesmo nome, apenas o método mapeado será invocado.
Changelog
- v2.0 - Lançamento Inicial.
Learn/json
JSON Wrapper
Visão Geral
A classe Json no Flight fornece uma maneira simples e consistente de codificar e decodificar dados JSON em sua aplicação. Ela envolve as funções JSON nativas do PHP com melhor tratamento de erros e alguns padrões úteis, tornando mais fácil e seguro trabalhar com JSON.
Entendendo
Trabalhar com JSON é super comum em aplicações PHP modernas, especialmente ao construir APIs ou lidar com requisições AJAX. A classe Json centraliza toda a codificação e decodificação JSON, para que você não precise se preocupar com casos de borda estranhos ou erros crípticos das funções integradas do PHP.
Principais recursos:
- Tratamento de erros consistente (lança exceções em caso de falha)
- Opções padrão para codificação/decodificação (como barras invertidas não escapadas)
- Métodos utilitários para impressão formatada e validação
Uso Básico
Codificando Dados para JSON
Para converter dados PHP em uma string JSON, use Json::encode():
use flight\util\Json;
$data = [
'framework' => 'Flight',
'version' => 3,
'features' => ['routing', 'views', 'extending']
];
$json = Json::encode($data);
echo $json;
// Saída: {"framework":"Flight","version":3,"features":["routing","views","extending"]}
Se a codificação falhar, você receberá uma exceção com uma mensagem de erro útil.
Impressão Formatada
Quer que seu JSON seja legível por humanos? Use prettyPrint():
echo Json::prettyPrint($data);
/*
{
"framework": "Flight",
"version": 3,
"features": [
"routing",
"views",
"extending"
]
}
*/
Decodificando Strings JSON
Para converter uma string JSON de volta para dados PHP, use Json::decode():
$json = '{"framework":"Flight","version":3}';
$data = Json::decode($json);
echo $data->framework; // Saída: Flight
Se você quiser um array associativo em vez de um objeto, passe true como o segundo argumento:
$data = Json::decode($json, true);
echo $data['framework']; // Saída: Flight
Se a decodificação falhar, você receberá uma exceção com uma mensagem de erro clara.
Validando JSON
Verifique se uma string é um JSON válido:
if (Json::isValid($json)) {
// É válido!
} else {
// Não é JSON válido
}
Obtendo o Último Erro
Se você quiser verificar a última mensagem de erro JSON (das funções nativas do PHP):
$error = Json::getLastError();
if ($error !== '') {
echo "Último erro JSON: $error";
}
Uso Avançado
Você pode personalizar as opções de codificação e decodificação se precisar de mais controle (veja opções do json_encode do PHP):
// Codificar com a opção JSON_HEX_TAG
$json = Json::encode($data, JSON_HEX_TAG);
// Decodificar com profundidade personalizada
$data = Json::decode($json, false, 1024);
Veja Também
- Collections - Para trabalhar com dados estruturados que podem ser facilmente convertidos para JSON.
- Configuration - Como configurar sua aplicação Flight.
- Extending - Como adicionar suas próprias utilidades ou substituir classes principais.
Solução de Problemas
- Se a codificação ou decodificação falhar, uma exceção é lançada — envolva suas chamadas em try/catch se quiser lidar com erros de forma graciosa.
- Se você obtiver resultados inesperados, verifique seus dados em busca de referências circulares ou caracteres não-UTF8.
- Use
Json::isValid()para verificar se uma string é um JSON válido antes de decodificar.
Changelog
- v3.16.0 - Adicionada classe utilitária de wrapper JSON.
Learn/flight_vs_slim
Flight vs Slim
O que é o Slim?
Slim é um microframework PHP que ajuda você a escrever rapidamente aplicações web e APIs simples, porém poderosas.
Boa parte da inspiração para alguns dos recursos da v3 do Flight veio na verdade do Slim. Agrupar rotas e executar middleware em uma ordem específica são dois recursos que foram inspirados pelo Slim. O Slim v3 foi lançado com foco na simplicidade, mas houve opiniões mistas em relação à v4.
Prós em comparação ao Flight
- Slim tem uma comunidade maior de desenvolvedores, que por sua vez criam módulos úteis para ajudar você a não reinventar a roda.
- Slim segue muitas interfaces e padrões comuns na comunidade PHP, o que aumenta a interoperabilidade.
- Slim tem documentação e tutoriais decentes que podem ser usados para aprender o framework (nada comparado a Laravel ou Symfony, no entanto).
- Slim tem diversos recursos, como tutoriais no YouTube e artigos online, que podem ser usados para aprender o framework.
- Slim permite que você use quaisquer componentes que desejar para lidar com os recursos principais de roteamento, pois é compatível com PSR-7.
Contras em comparação ao Flight
- Surpreendentemente, o Slim não é tão rápido quanto você imagina que seria para um microframework. Veja os benchmarks do TechEmpower para mais informações.
- Flight é voltado para um desenvolvedor que procura criar uma aplicação web leve, rápida e fácil de usar.
- Flight não tem dependências, enquanto o Slim tem algumas dependências que você precisa instalar.
- Flight é voltado para simplicidade e facilidade de uso.
- Um dos recursos principais do Flight é que ele faz o possível para manter a compatibilidade com versões anteriores. A mudança do Slim v3 para v4 foi uma quebra de compatibilidade.
- Flight é destinado a desenvolvedores que estão se aventurando no mundo dos frameworks pela primeira vez.
- Flight também pode fazer aplicações de nível empresarial, mas não tem tantos exemplos e tutoriais quanto o Slim. Também exigirá mais disciplina por parte do desenvolvedor para manter as coisas organizadas e bem estruturadas.
- Flight dá ao desenvolvedor mais controle sobre a aplicação, enquanto o Slim pode introduzir alguma mágica nos bastidores.
- Flight tem SimplePdo para acesso a banco de dados (preferido em relação ao PdoWrapper descontinuado). O Slim exige que você use uma biblioteca de terceiros.
- Flight tem um plugin de permissões que pode ser usado para proteger sua aplicação. O Slim exige que você use uma biblioteca de terceiros.
- Flight tem um ORM chamado active-record que pode ser usado para interagir com seu banco de dados. O Slim exige que você use uma biblioteca de terceiros.
- Flight tem uma aplicação CLI chamada runway que pode ser usada para executar sua aplicação a partir da linha de comando. O Slim não tem.
Learn/autoloading
Carregamento automático (Autoloading)
Visão geral
O carregamento automático (autoloading) é 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 Composer.
Acertar o autoloading também é importante para o desenvolvimento assistido por IA: os agentes colocam arquivos onde o namespace aponta. Se as maiúsculas/minúsculas das pastas e as do namespace divergirem, erros de classe não encontrada aparecem no Linux, mesmo quando as coisas "funcionavam" em um disco Mac sem distinção de maiúsculas/minúsculas.
Entendendo
Por padrão, qualquer classe Flight é carregada automaticamente para você graças ao Composer. Para as classes de sua aplicação, você tem duas abordagens comuns:
- Composer PSR-4 (o que o skeleton oficial usa): mapeie um prefixo de namespace para um diretório em
composer.jsone depois executecomposer dump-autoload. Flight::path(): aponte o carregador do Flight para diretórios (útil para aplicações simples ou quando você não está usando Composer para o código da aplicação).
Usar um autoloader simplifica muito seu código. Em vez de uma parede de include / require no topo de cada arquivo, as classes são carregadas quando você as usa pela primeira vez.
Sensibilidade a maiúsculas/minúsculas (leia isto duas vezes)
Os namespaces devem corresponder à estrutura de diretórios e às maiúsculas/minúsculas desses diretórios.
| Funciona | Quebra no Linux |
|---|---|
App\Controller\HomeController → app/Controller/HomeController.php |
App\Controller\… com a pasta app/controllers/ |
app\controllers\MyController → app/controllers/MyController.php |
Misturar App\ com controllers minúsculo |
Namespaces em PHP não diferenciam maiúsculas de minúsculas em alguns contextos, mas o Composer e o sistema de arquivos diferenciam. O skeleton oficial é padronizado em:
- Composer:
"App\\": "app/" - Pastas:
Controller,Middleware,Model,Utils(PascalCase), nãocontrollers/middlewares
Documentações antigas e exemplos da comunidade às vezes usavam app\controllers minúsculo. Isso ainda funciona se suas pastas forem minúsculas—mas os novos projetos skeleton usam App\ + pastas PascalCase. Escolha uma convenção por projeto e siga-a para que humanos e ferramentas de IA não inventem uma segunda estrutura.
Skeleton (recomendado para novos projetos)
Após composer create-project flightphp/skeleton, o código da aplicação é carregado automaticamente via Composer—nenhum Flight::path() é necessário para classes App\:
{
"autoload": {
"psr-4": {
"App\\": "app/"
}
}
}
// app/Controller/HomeController.php
namespace App\Controller;
use flight\Engine;
class HomeController
{
protected Engine $app;
public function __construct(Engine $app)
{
$this->app = $app;
}
public function index(): void
{
$this->app->render('welcome', ['message' => 'Hello!']);
}
}
// app/config/routes.php — Dice resolve App\Controller\… via o container
$router->get('/', [HomeController::class, 'index']);
Veja Instalação para a árvore completa e IA & experiência de desenvolvimento para saber como o AGENTS.md documenta essa estrutura para assistentes de codificação.
Uso básico (Flight::path())
Vamos supor que temos uma árvore de diretórios como a seguinte:
# Exemplo de caminho
/home/user/project/my-flight-project/
├── app
│ ├── cache
│ ├── config
│ ├── controllers - contém os controllers deste projeto
│ ├── translations
│ ├── UTILS - contém classes apenas para esta aplicação (todo em maiúsculas de propósito para um exemplo mais adiante)
│ └── views
└── public
└── css
└── js
└── index.php
Você deve ter notado que isso é semelhante a uma árvore típica de aplicação (o próprio site da documentação usa uma estrutura organizada). O controllers minúsculo aqui é uma escolha válida—apenas não é o padrão atual do skeleton.
Você pode especificar cada diretório para carregar desta forma:
/**
* public/index.php
*/
// Adiciona um caminho ao autoloader
Flight::path(__DIR__.'/../app/controllers/');
Flight::path(__DIR__.'/../app/utils/');
/**
* app/controllers/MyController.php
*/
// nenhum namespace é necessário
// Todas as classes autoloaded são recomendadas em Pascal Case (cada palavra capitalizada, sem espaços)
class MyController {
public function index() {
// faz alguma coisa
}
}
Namespaces com Flight::path()
Se você tiver namespaces, na verdade fica muito fácil implementar isso. Você deve usar o método Flight::path() para especificar o diretório raiz (não o document root nem a pasta public/) da sua aplicação.
/**
* public/index.php
*/
// Adiciona um caminho ao autoloader
Flight::path(__DIR__.'/../');
Agora é assim que seu controller pode ficar. Veja o exemplo abaixo, mas preste atenção nos comentários para informações importantes.
/**
* app/controllers/MyController.php
*/
// namespaces são obrigatórios
// namespaces são iguais à estrutura de diretórios
// namespaces devem seguir as mesmas maiúsculas/minúsculas da estrutura de diretórios
// namespaces e diretórios não podem conter underscores (a menos que Loader::setV2ClassLoading(false) esteja definido)
namespace app\controllers;
// Todas as classes autoloaded são recomendadas em Pascal Case (cada palavra capitalizada, sem espaços)
// A partir da 3.7.2, você pode usar Pascal_Snake_Case para os nomes das suas classes executando Loader::setV2ClassLoading(false);
class MyController {
public function index() {
// faz alguma coisa
}
}
E se você quisesse autocarregar uma classe no diretório utils, faria basicamente o mesmo:
/**
* app/UTILS/ArrayHelperUtil.php
*/
// o namespace deve corresponder à estrutura de diretórios e às maiúsculas/minúsculas (observe que o diretório UTILS está todo em maiúsculas
// como na árvore de arquivos acima)
namespace app\UTILS;
class ArrayHelperUtil {
public function changeArrayCase(array $array) {
// faz alguma coisa
}
}
Namespace no estilo skeleton (mesmas regras, maiúsculas/minúsculas diferentes)
/**
* app/Controller/MyController.php
*/
namespace App\Controller;
class MyController {
// ...
}
A regra não mudou—apenas a escolha de maiúsculas/minúsculas de pasta/namespace do skeleton. Qualquer que seja a capitalização das suas pastas, sua linha namespace deve corresponder.
Underscores em nomes de classes
A partir da 3.7.2, você pode usar Pascal_Snake_Case para os nomes das suas classes executando Loader::setV2ClassLoading(false);.
Isso permitirá que você use underscores nos nomes das suas classes.
Não é recomendado, mas está disponível para quem precisar.
use flight\core\Loader;
/**
* public/index.php
*/
// Adiciona um caminho ao autoloader
Flight::path(__DIR__.'/../app/controllers/');
Flight::path(__DIR__.'/../app/utils/');
Loader::setV2ClassLoading(false);
/**
* app/controllers/My_Controller.php
*/
// nenhum namespace é necessário
class My_Controller {
public function index() {
// faz alguma coisa
}
}
Veja também
- Instalação - Árvore do skeleton e padrões
App\para novos projetos. - Roteamento - Como mapear rotas para controllers e renderizar views.
- Injeção de dependência - Como os controllers obtêm
Enginee serviços. - IA & experiência de desenvolvimento - Mantenha os agentes alinhados com sua estrutura via
AGENTS.md. - Por que um framework? - Entendendo os benefícios de usar um framework como o Flight.
Solução de problemas
- Se você não conseguir descobrir por que suas classes com namespace não estão sendo encontradas, lembre-se: com
Flight::path(), aponte para a raiz do projeto (ou a base correta para seu namespace), não apenas uma pasta aninhada que você esqueceu de espelhar no namespace. - Com o Composer PSR-4, execute
composer dump-autoloadapós alterar os mapeamentos emcomposer.json. - Em CI Linux ou produção, uma capitalização errada de pasta é uma falha muito comum de "funciona na minha máquina".
Classe não encontrada (autoloading não funcionando)
Pode haver um ou dois motivos para isso não acontecer. Abaixo estão alguns exemplos.
Nome de arquivo incorreto
O mais comum é que o nome da classe não corresponda ao nome do arquivo.
Se você tem uma classe chamada MyClass, então o arquivo deve ser nomeado MyClass.php. Se você tem uma classe chamada MyClass e o arquivo é nomeado myclass.php,
o autoloader não conseguirá encontrá-la.
Namespace ou capitalização de pasta incorretos
Se você estiver usando namespaces, o namespace deve corresponder à estrutura de diretórios incluindo maiúsculas/minúsculas.
// ...código...
// se o seu MyController está em app/Controller (skeleton) e com namespace App\Controller
// isto não funcionará:
Flight::route('/hello', 'MyController->hello');
// Estilo skeleton:
use App\Controller\MyController;
Flight::route('/hello', [ MyController::class, 'hello' ]);
// Layout antigo em minúsculas (somente se suas pastas forem realmente app/controllers):
use app\controllers\MyController;
Flight::route('/hello', [ MyController::class, 'hello' ]);
// ou totalmente qualificado:
Flight::route('/hello', [ 'App\Controller\MyController', 'hello' ]);
path() não definido (código de aplicação sem Composer)
Se você confiar em Flight::path() em vez do Composer para as classes da aplicação, defina o caminho antes das rotas que referenciam essas classes (geralmente no início do bootstrap / public/index.php):
// Adiciona um caminho ao autoloader (raiz do projeto para aplicações com namespace)
Flight::path(__DIR__.'/../');
O skeleton oficial usa principalmente Composer PSR-4 para App\, então você normalmente não precisará de Flight::path() para controllers e models lá.
Changelog
- Documentação – Documentar o skeleton
App\+ pastas PascalCase e as armadilhas de sensibilidade a maiúsculas/minúsculas para humanos e ferramentas de IA. - v3.7.2 - Você pode usar Pascal_Snake_Case para os nomes das suas classes executando
Loader::setV2ClassLoading(false); - v2.0 - Funcionalidade de autoload adicionada.
Learn/uploaded_file
Manipulador de Arquivo Enviado
Visão Geral
A classe UploadedFile no Flight facilita e torna seguro o manuseio de uploads de arquivos em sua aplicação. Ela encapsula os detalhes do processo de upload de arquivos do PHP, fornecendo uma maneira simples e orientada a objetos para acessar informações do arquivo e mover arquivos enviados.
Compreendendo
Quando um usuário envia um arquivo via formulário, o PHP armazena informações sobre o arquivo na superglobal $_FILES. No Flight, você raramente interage diretamente com $_FILES. Em vez disso, o objeto Request do Flight (acessível via Flight::request()) fornece um método getUploadedFiles() que retorna um array de objetos UploadedFile, tornando o manuseio de arquivos muito mais conveniente e robusto.
A classe UploadedFile fornece métodos para:
- Obter o nome original do arquivo, tipo MIME, tamanho e localização temporária
- Verificar erros de upload
- Mover o arquivo enviado para uma localização permanente
Essa classe ajuda você a evitar armadilhas comuns com uploads de arquivos, como lidar com erros ou mover arquivos de forma segura.
Uso Básico
Acessando Arquivos Enviados de uma Requisição
A maneira recomendada de acessar arquivos enviados é através do objeto de requisição:
Flight::route('POST /upload', function() {
// Para um campo de formulário nomeado <input type="file" name="myFile">
$uploadedFiles = Flight::request()->getUploadedFiles();
$file = $uploadedFiles['myFile'];
// Agora você pode usar os métodos do UploadedFile
if ($file->getError() === UPLOAD_ERR_OK) {
$file->moveTo('/path/to/uploads/' . $file->getClientFilename());
echo "Arquivo enviado com sucesso!";
} else {
echo "Falha no envio: " . $file->getError();
}
});
Manipulando Múltiplos Uploads de Arquivos
Se o seu formulário usa name="myFiles[]" para múltiplos uploads, você obterá um array de objetos UploadedFile:
Flight::route('POST /upload', function() {
// Para um campo de formulário nomeado <input type="file" name="myFiles[]">
$uploadedFiles = Flight::request()->getUploadedFiles();
foreach ($uploadedFiles['myFiles'] as $file) {
if ($file->getError() === UPLOAD_ERR_OK) {
$file->moveTo('/path/to/uploads/' . $file->getClientFilename());
echo "Enviado: " . $file->getClientFilename() . "<br>";
} else {
echo "Falha no envio: " . $file->getClientFilename() . "<br>";
}
}
});
Criando uma Instância de UploadedFile Manualmente
Normalmente, você não criará um UploadedFile manualmente, mas pode fazer isso se necessário:
use flight\net\UploadedFile;
$file = new UploadedFile(
$_FILES['myfile']['name'],
$_FILES['myfile']['type'],
$_FILES['myfile']['size'],
$_FILES['myfile']['tmp_name'],
$_FILES['myfile']['error']
);
Acessando Informações do Arquivo
Você pode facilmente obter detalhes sobre o arquivo enviado:
echo $file->getClientFilename(); // Nome original do arquivo do computador do usuário
echo $file->getClientMediaType(); // Tipo MIME (ex.: image/png)
echo $file->getSize(); // Tamanho do arquivo em bytes
echo $file->getTempName(); // Caminho temporário do arquivo no servidor
echo $file->getError(); // Código de erro de upload (0 significa sem erro)
Movendo o Arquivo Enviado
Após validar o arquivo, mova-o para uma localização permanente:
try {
$file->moveTo('/path/to/uploads/' . $file->getClientFilename());
echo "Arquivo enviado com sucesso!";
} catch (Exception $e) {
echo "Falha no envio: " . $e->getMessage();
}
O método moveTo() lançará uma exceção se algo der errado (como um erro de upload ou problema de permissão).
Manipulando Erros de Upload
Se houver um problema durante o upload, você pode obter uma mensagem de erro legível por humanos:
if ($file->getError() !== UPLOAD_ERR_OK) {
// Você pode usar o código de erro ou capturar a exceção de moveTo()
echo "Houve um erro ao enviar o arquivo.";
}
Veja Também
- Requests - Aprenda como acessar arquivos enviados de requisições HTTP e veja mais exemplos de upload de arquivos.
- Configuration - Como configurar limites de upload e diretórios no PHP.
- Extending - Como personalizar ou estender as classes principais do Flight.
Solução de Problemas
- Sempre verifique
$file->getError()antes de mover o arquivo. - Certifique-se de que o diretório de upload é gravável pelo servidor web.
- Se
moveTo()falhar, verifique a mensagem de exceção para detalhes. - As configurações
upload_max_filesizeepost_max_sizedo PHP podem limitar uploads de arquivos. - Para múltiplos uploads de arquivos, sempre itere pelo array de objetos
UploadedFile.
Changelog
- v3.12.0 - Adicionada a classe
UploadedFileao objeto de requisição para um manuseio de arquivos mais fácil.
Guides/unit_testing
Testes Unitários no Flight PHP com PHPUnit
Este guia introduz testes unitários no Flight PHP usando o PHPUnit, voltado para iniciantes que desejam entender por que os testes unitários são importantes e como aplicá-los na prática. Vamos nos concentrar em testar o comportamento — garantindo que sua aplicação faça o que você espera, como enviar um e-mail ou salvar um registro — em vez de cálculos triviais. Começaremos com um manipulador de rota simples e avançaremos para um controller mais complexo, incorporando injeção de dependência (DI) e simulação (mock) de serviços de terceiros.
Por que Testes Unitários?
Testes unitários garantem que seu código se comporte como esperado, detectando bugs antes que cheguem à produção. Eles são especialmente valiosos no Flight, onde o roteamento leve e a flexibilidade podem levar a interações complexas. Para desenvolvedores solo ou equipes, os testes unitários atuam como uma rede de segurança, documentando o comportamento esperado e prevenindo regressões quando você revisita o código mais tarde. Eles também melhoram o design: código difícil de testar frequentemente sinaliza classes excessivamente complexas ou fortemente acopladas.
Diferente de exemplos simplistas (por exemplo, testar x * y = z), vamos nos concentrar em comportamentos do mundo real, como validar entrada, salvar dados ou acionar ações como e-mails. Nosso objetivo é tornar os testes acessíveis e significativos.
Princípios Gerais Orientadores
- Teste o Comportamento, Não a Implementação: Concentre-se nos resultados (por exemplo, "e-mail enviado" ou "registro salvo") em vez de detalhes internos. Isso torna os testes robustos contra refatorações.
- Pare de usar
Flight::: Os métodos estáticos do Flight são terrivelmente convenientes, mas dificultam os testes. Você deve se acostumar a usar a variável$appde$app = Flight::app();.$apptem todos os mesmos métodos queFlight::tem. Você ainda poderá usar$app->route()ou$this->app->json()no seu controller, etc. Você também deve usar o roteador real do Flight com$router = $app->router()e então pode usar$router->get(),$router->post(),$router->group()etc. Veja Roteamento. - Mantenha os Testes Rápidos: Testes rápidos incentivam a execução frequente. Evite operações lentas, como chamadas de banco de dados, em testes unitários. Se você tem um teste lento, é um sinal de que está escrevendo um teste de integração, não um teste unitário. Testes de integração são quando você realmente envolve bancos de dados reais, chamadas HTTP reais, envio de e-mail real, etc. Eles têm seu lugar, mas são lentos e podem ser instáveis, ou seja, às vezes falham por um motivo desconhecido.
- Use Nomes Descritivos: Os nomes dos testes devem descrever claramente o comportamento que está sendo testado. Isso melhora a legibilidade e a manutenibilidade.
- Evite Globais como uma Praga: Minimize o uso de
$app->set()e$app->get(), pois eles agem como estado global, exigindo mocks em todos os testes. Prefira DI ou um contêiner de DI (veja Contêiner de Injeção de Dependência). Até mesmo usar o método$app->map()é tecnicamente um "global" e deve ser evitado em favor de DI. Use uma biblioteca de sessão como flightphp/session para que você possa simular o objeto de sessão em seus testes. Não chame$_SESSIONdiretamente no seu código, pois isso está injetando uma variável global no seu código, dificultando o teste. - Use Injeção de Dependência: Injete dependências (por exemplo,
PDO, mailers) nos controllers para isolar a lógica e simplificar os mocks. Se você tem uma classe com muitas dependências, considere refatorá-la em classes menores que cada uma tenha uma única responsabilidade seguindo os princípios SOLID. - Simule Serviços de Terceiros: Simule bancos de dados, clientes HTTP (cURL) ou serviços de e-mail para evitar chamadas externas. Teste uma ou duas camadas de profundidade, mas deixe sua lógica principal ser executada. Por exemplo, se seu aplicativo envia uma mensagem de texto, você NÃO quer realmente enviar uma mensagem de texto toda vez que executa seus testes, porque esses custos vão aumentar (e será mais lento). Em vez disso, simule o serviço de mensagem de texto e apenas verifique se seu código chamou o serviço de mensagem de texto com os parâmetros corretos.
- Busque Alta Cobertura, Não Perfeição: 100% de cobertura de linha é bom, mas isso não significa que tudo no seu código está testado da maneira que deveria (pesquise sobre cobertura de branch/path no PHPUnit). Priorize comportamentos críticos (por exemplo, registro de usuário, respostas de API e captura de respostas com falha).
- Use Controllers para Rotas: Em suas definições de rota, use controllers em vez de closures. O
flight\Engine $appé injetado em cada controller via construtor por padrão. Em testes, use$app = new Flight\Engine()para instanciar o Flight dentro de um teste, injete-o no seu controller e chame os métodos diretamente (por exemplo,$controller->register()). Veja Estendendo o Flight e Roteamento. - Escolha um estilo de mock e mantenha-o: O PHPUnit suporta vários estilos de mock (por exemplo, prophecy, mocks embutidos), ou você pode usar classes anônimas que têm seus próprios benefícios, como autocompletar código, quebrar se você alterar a definição do método, etc. Apenas seja consistente em seus testes. Veja Objetos Mock do PHPUnit.
- Use visibilidade
protectedpara métodos/propriedades que você deseja testar em subclasses: Isso permite que você os substitua em subclasses de teste sem torná-los públicos, isso é especialmente útil para mocks de classes anônimas.
Configurando o PHPUnit
Primeiro, configure o PHPUnit no seu projeto Flight PHP usando o Composer para facilitar os testes. Veja o Guia de Introdução ao PHPUnit para mais detalhes.
-
No diretório do seu projeto, execute:
composer require --dev phpunit/phpunitIsso instala o PHPUnit mais recente como uma dependência de desenvolvimento.
-
Crie um diretório
testsna raiz do seu projeto para os arquivos de teste. -
Adicione um script de teste ao
composer.jsonpara conveniência:// outro conteúdo do composer.json "scripts": { "test": "phpunit --configuration phpunit.xml" } -
Crie um arquivo
phpunit.xmlna raiz:<?xml version="1.0" encoding="UTF-8"?> <phpunit bootstrap="vendor/autoload.php"> <testsuites> <testsuite name="Flight Tests"> <directory>tests</directory> </testsuite> </testsuites> </phpunit>
Agora, quando seus testes estiverem prontos, você pode executar composer test para executar os testes.
Testando um Manipulador de Rota Simples
Vamos começar com uma rota básica que valida a entrada de e-mail de um usuário. Vamos testar seu comportamento: retornar uma mensagem de sucesso para e-mails válidos e um erro para e-mails inválidos. Para validação de e-mail, usamos filter_var.
// index.php
$app->route('POST /register', [ UserController::class, 'register' ]);
// UserController.php
class UserController {
protected $app;
public function __construct(flight\Engine $app) {
$this->app = $app;
}
public function register() {
$email = $this->app->request()->data->email;
$responseArray = [];
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
$responseArray = ['status' => 'error', 'message' => 'E-mail inválido'];
} else {
$responseArray = ['status' => 'success', 'message' => 'E-mail válido'];
}
$this->app->json($responseArray);
}
}
Para testar isso, crie um arquivo de teste. Veja Testes Unitários e Princípios SOLID para mais sobre como estruturar testes:
// tests/UserControllerTest.php
use PHPUnit\Framework\TestCase;
use Flight;
use flight\Engine;
class UserControllerTest extends TestCase {
public function testValidEmailReturnsSuccess() {
$app = new Engine();
$request = $app->request();
$request->data->email = 'test@example.com'; // Simula dados POST
$UserController = new UserController($app);
$UserController->register($request->data->email);
$response = $app->response()->getBody();
$output = json_decode($response, true);
$this->assertEquals('success', $output['status']);
$this->assertEquals('E-mail válido', $output['message']);
}
public function testInvalidEmailReturnsError() {
$app = new Engine();
$request = $app->request();
$request->data->email = 'invalid-email'; // Simula dados POST
$UserController = new UserController($app);
$UserController->register($request->data->email);
$response = $app->response()->getBody();
$output = json_decode($response, true);
$this->assertEquals('error', $output['status']);
$this->assertEquals('E-mail inválido', $output['message']);
}
}
Pontos-chave:
- Simulamos dados POST usando a classe de requisição. Não use globais como
$_POST,$_GET, etc., pois isso torna o teste mais complicado (você sempre precisa redefinir esses valores ou outros testes podem falhar). - Todos os controllers, por padrão, terão a instância de
flight\Engineinjetada neles, mesmo sem um contêiner DIC configurado. Isso torna muito mais fácil testar controllers diretamente. - Não há nenhum uso de
Flight::, tornando o código mais fácil de testar. - Os testes verificam o comportamento: status e mensagem corretos para e-mails válidos/inválidos.
Execute composer test para verificar se a rota se comporta como esperado. Para mais sobre requisições e respostas no Flight, consulte a documentação relevante.
Usando Injeção de Dependência para Controllers Testáveis
Para cenários mais complexos, use injeção de dependência (DI) para tornar os controllers testáveis. Evite os globais do Flight (por exemplo, Flight::set(), Flight::map(), Flight::register()) pois eles agem como estado global, exigindo mocks para todos os testes. Em vez disso, use o contêiner DI do Flight, DICE, PHP-DI ou DI manual.
Vamos usar flight\database\SimplePdo em vez de PDO cru. Este helper é muito mais fácil de simular e testar unitariamente (e é preferido em relação ao PdoWrapper obsoleto).
Aqui está um controller que salva um usuário no banco de dados e envia um e-mail de boas-vindas:
use flight\database\SimplePdo;
class UserController {
protected $app;
protected $db;
protected $mailer;
public function __construct(Engine $app, SimplePdo $db, MailerInterface $mailer) {
$this->app = $app;
$this->db = $db;
$this->mailer = $mailer;
}
public function register() {
$email = $this->app->request()->data->email;
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
// adicionar o return aqui ajuda no teste unitário a interromper a execução
return $this->app->jsonHalt(['status' => 'error', 'message' => 'E-mail inválido']);
}
$this->db->runQuery('INSERT INTO users (email) VALUES (?)', [$email]);
$this->mailer->sendWelcome($email);
return $this->app->json(['status' => 'success', 'message' => 'Usuário registrado']);
}
}
Pontos-chave:
- O controller depende de uma instância de
SimplePdoe de umMailerInterface(um serviço de e-mail de terceiros fictício). - As dependências são injetadas via construtor, evitando globais.
Testando o Controller com Mocks
Agora, vamos testar o comportamento do UserController: validar e-mails, salvar no banco de dados e enviar e-mails. Vamos simular o banco de dados e o mailer para isolar o controller.
// tests/UserControllerDICTest.php
use flight\database\SimplePdo;
use PHPUnit\Framework\TestCase;
class UserControllerDICTest extends TestCase {
public function testValidEmailSavesAndSendsEmail() {
// Às vezes, misturar estilos de mock é necessário
// Aqui usamos o mock embutido do PHPUnit para PDOStatement
$statementMock = $this->createMock(PDOStatement::class);
$statementMock->method('execute')->willReturn(true);
// Usando uma classe anônima para simular SimplePdo
$mockDb = new class($statementMock) extends SimplePdo {
protected $statementMock;
public function __construct($statementMock) {
$this->statementMock = $statementMock;
}
// Quando simulamos dessa forma, não estamos realmente fazendo uma chamada de banco de dados.
// Podemos configurar ainda mais isso para alterar o mock do PDOStatement para simular falhas, etc.
public function runQuery(string $sql, array $params = []): PDOStatement {
return $this->statementMock;
}
};
$mockMailer = new class implements MailerInterface {
public $sentEmail = null;
public function sendWelcome($email): bool {
$this->sentEmail = $email;
return true;
}
};
$app = new Engine();
$app->request()->data->email = 'test@example.com';
$controller = new UserControllerDIC($app, $mockDb, $mockMailer);
$controller->register();
$response = $app->response()->getBody();
$result = json_decode($response, true);
$this->assertEquals('success', $result['status']);
$this->assertEquals('Usuário registrado', $result['message']);
$this->assertEquals('test@example.com', $mockMailer->sentEmail);
}
public function testInvalidEmailSkipsSaveAndEmail() {
$mockDb = new class() extends SimplePdo {
// Um construtor vazio evita o construtor pai
public function __construct() {}
public function runQuery(string $sql, array $params = []): PDOStatement {
throw new Exception('Não deveria ser chamado');
}
};
$mockMailer = new class implements MailerInterface {
public $sentEmail = null;
public function sendWelcome($email): bool {
throw new Exception('Não deveria ser chamado');
}
};
$app = new Engine();
$app->request()->data->email = 'invalid-email';
// Precisamos mapear jsonHalt para evitar sair
$app->map('jsonHalt', function($data) use ($app) {
$app->json($data, 400);
});
$controller = new UserControllerDIC($app, $mockDb, $mockMailer);
$controller->register();
$response = $app->response()->getBody();
$result = json_decode($response, true);
$this->assertEquals('error', $result['status']);
$this->assertEquals('E-mail inválido', $result['message']);
}
}
Pontos-chave:
- Simulamos
SimplePdoeMailerInterfacepara evitar chamadas reais de banco de dados ou e-mail. - Os testes verificam o comportamento: e-mails válidos acionam inserções no banco de dados e envios de e-mail; e-mails inválidos pulam ambos.
- Simule dependências de terceiros (por exemplo,
SimplePdo,MailerInterface), permitindo que a lógica do controller seja executada.
Simulando demais
Tome cuidado para não simular demais o seu código. Deixe-me dar um exemplo abaixo sobre por que isso pode ser ruim usando nosso UserController. Vamos transformar essa verificação em um método chamado isEmailValid (usando filter_var) e as outras novas adições em um método separado chamado registerUser.
use flight\database\SimplePdo;
use flight\Engine;
// UserControllerDICV2.php
class UserControllerDICV2 {
protected $app;
protected $db;
protected $mailer;
public function __construct(Engine $app, SimplePdo $db, MailerInterface $mailer) {
$this->app = $app;
$this->db = $db;
$this->mailer = $mailer;
}
public function register() {
$email = $this->app->request()->data->email;
if (!$this->isEmailValid($email)) {
// adicionar o return aqui ajuda no teste unitário a interromper a execução
return $this->app->jsonHalt(['status' => 'error', 'message' => 'E-mail inválido']);
}
$this->registerUser($email);
$this->app->json(['status' => 'success', 'message' => 'Usuário registrado']);
}
protected function isEmailValid($email) {
return filter_var($email, FILTER_VALIDATE_EMAIL) !== false;
}
protected function registerUser($email) {
$this->db->runQuery('INSERT INTO users (email) VALUES (?)', [$email]);
$this->mailer->sendWelcome($email);
}
}
E agora o teste unitário excessivamente simulado que na verdade não testa nada:
use PHPUnit\Framework\TestCase;
class UserControllerTest extends TestCase {
public function testValidEmailSavesAndSendsEmail() {
$app = new Engine();
$app->request()->data->email = 'test@example.com';
// estamos pulando a injeção de dependência extra aqui porque é "fácil"
$controller = new class($app) extends UserControllerDICV2 {
protected $app;
// Evita as dependências no construtor
public function __construct($app) {
$this->app = $app;
}
// Vamos apenas forçar isso para ser válido.
protected function isEmailValid($email) {
return true; // Sempre retorna true, evitando a validação real
}
// Evita as chamadas reais de banco de dados e mailer
protected function registerUser($email) {
return false;
}
};
$controller->register();
$response = $app->response()->getBody();
$result = json_decode($response, true);
$this->assertEquals('success', $result['status']);
$this->assertEquals('Usuário registrado', $result['message']);
}
}
Viva, temos testes unitários e eles estão passando! Mas espere, e se eu realmente alterar o funcionamento interno de isEmailValid ou registerUser? Meus testes ainda passarão porque eu simulei toda a funcionalidade. Deixe-me mostrar o que quero dizer.
// UserControllerDICV2.php
class UserControllerDICV2 {
// ... outros métodos ...
protected function isEmailValid($email) {
// Lógica alterada
$validEmail = filter_var($email, FILTER_VALIDATE_EMAIL) !== false;
// Agora deve ter apenas um domínio específico
$validDomain = strpos($email, '@example.com') !== false;
return $validEmail && $validDomain;
}
}
Se eu executar meus testes unitários acima, eles ainda passarão! Mas porque eu não estava testando o comportamento (realmente deixando parte do código ser executado), eu potencialmente codifiquei um bug prestes a acontecer em produção. O teste deve ser modificado para levar em conta o novo comportamento, e também o oposto de quando o comportamento não é o que esperamos.
Exemplo Completo
Você pode encontrar um exemplo completo de um projeto Flight PHP com testes unitários no GitHub: n0nag0n/flight-unit-tests-guide. Para um entendimento mais profundo, veja Testes Unitários e Princípios SOLID.
Armadilhas Comuns
- Excesso de Mock: Não simule todas as dependências; deixe alguma lógica (por exemplo, validação do controller) ser executada para testar o comportamento real. Veja Testes Unitários e Princípios SOLID.
- Estado Global: Usar variáveis globais do PHP (por exemplo,
$_SESSION,$_COOKIE) intensamente torna os testes frágeis. O mesmo vale paraFlight::. Refatore para passar dependências explicitamente. - Configuração Complexa: Se a configuração do teste for trabalhosa, sua classe pode ter dependências ou responsabilidades demais, violando os princípios SOLID.
Escalando com Testes Unitários
Testes unitários brilham em projetos maiores ou ao revisitar código após meses. Eles documentam o comportamento e detectam regressões, economizando você de ter que reaprender sua aplicação. Para desenvolvedores solo, teste caminhos críticos (por exemplo, registro de usuário, processamento de pagamentos). Para equipes, os testes garantem um comportamento consistente entre as contribuições. Veja Por que Frameworks? para mais sobre os benefícios de usar frameworks e testes.
Contribua com suas próprias dicas de teste para o repositório de documentação do Flight PHP!
Escrito por n0nag0n 2025
Guides/blog
Construindo um Blog Simples com Flight PHP
Este guia mostra como criar um blog básico usando o framework Flight PHP. Você configurará um projeto, definirá rotas, gerenciará posts com JSON e os renderizará com o mecanismo de templates Latte — tudo demonstrando a simplicidade e flexibilidade do Flight. Ao final, você terá um blog funcional com uma página inicial, páginas individuais de posts e um formulário de criação.
Pré-requisitos
- PHP 7.4+: Instalado no seu sistema.
- Composer: Para gerenciamento de dependências.
- Editor de Texto: Qualquer editor como VS Code ou PHPStorm.
- Conhecimento básico de PHP e desenvolvimento web.
Passo 1: Configure Seu Projeto
Comece criando um novo diretório de projeto e instalando o Flight via Composer.
-
Crie um Diretório:
mkdir flight-blog cd flight-blog -
Instale o Flight:
composer require flightphp/core -
Crie um Diretório Público: O Flight usa um único ponto de entrada (
index.php). Crie uma pastapublic/para ele:mkdir public -
index.phpBásico: Criepublic/index.phpcom uma rota simples de “hello world”:<?php require '../vendor/autoload.php'; Flight::route('/', function () { echo 'Hello, Flight!'; }); Flight::start(); -
Execute o Servidor Embutido: Teste sua configuração com o servidor de desenvolvimento do PHP:
php -S localhost:8000 -t public/Visite
http://localhost:8000para ver “Hello, Flight!”.
Passo 2: Organize a Estrutura do Seu Projeto
Para uma configuração organizada, estruture seu projeto assim:
flight-blog/
├── app/
│ ├── config/
│ └── views/
├── data/
├── public/
│ └── index.php
├── vendor/
└── composer.json
app/config/: Arquivos de configuração (ex.: eventos, rotas).app/views/: Templates para renderização de páginas.data/: Arquivo JSON para armazenar posts do blog.public/: Raiz web comindex.php.
Passo 3: Instale e Configure o Latte
Latte é um mecanismo de templates leve que se integra bem ao Flight.
-
Instale o Latte:
composer require latte/latte -
Configure o Latte no Flight: Atualize
public/index.phppara registrar o Latte como mecanismo de visualização:<?php require '../vendor/autoload.php'; use Latte\Engine; Flight::register('view', Engine::class, [], function ($latte) { $latte->setTempDirectory(__DIR__ . '/../cache/'); $latte->setLoader(new \Latte\Loaders\FileLoader(__DIR__ . '/../app/views/')); }); Flight::route('/', function () { Flight::view()->render('home.latte', ['title' => 'My Blog']); }); Flight::start(); -
Crie um Template de Layout: Em
app/views/layout.latte:<!DOCTYPE html> <html> <head> <title>{$title}</title> </head> <body> <header> <h1>My Blog</h1> <nav> <a href="/">Home</a> | <a href="/create">Create a Post</a> </nav> </header> <main> {block content}{/block} </main> <footer> <p>© {date('Y')} Flight Blog</p> </footer> </body> </html> -
Crie um Template Inicial: Em
app/views/home.latte:{extends 'layout.latte'} {block content} <h2>{$title}</h2> <ul> {foreach $posts as $post} <li><a href="/post/{$post['slug']}">{$post['title']}</a></li> {/foreach} </ul> {/block}Reinicie o servidor se você o tiver encerrado e visite
http://localhost:8000para ver a página renderizada. -
Crie um Arquivo de Dados:
Use um arquivo JSON para simular um banco de dados por simplicidade.
Em
data/posts.json:[ { "slug": "first-post", "title": "My First Post", "content": "This is my very first blog post with Flight PHP!" } ]
Passo 4: Defina Rotas
Separe suas rotas em um arquivo de configuração para melhor organização.
-
Crie
routes.php: Emapp/config/routes.php:<?php Flight::route('/', function () { Flight::view()->render('home.latte', ['title' => 'My Blog']); }); Flight::route('/post/@slug', function ($slug) { Flight::view()->render('post.latte', ['title' => 'Post: ' . $slug, 'slug' => $slug]); }); Flight::route('GET /create', function () { Flight::view()->render('create.latte', ['title' => 'Create a Post']); }); -
Atualize
index.php: Inclua o arquivo de rotas:<?php require '../vendor/autoload.php'; use Latte\Engine; Flight::register('view', Engine::class, [], function ($latte) { $latte->setTempDirectory(__DIR__ . '/../cache/'); $latte->setLoader(new \Latte\Loaders\FileLoader(__DIR__ . '/../app/views/')); }); require '../app/config/routes.php'; Flight::start();
Passo 5: Armazene e Recupere Posts do Blog
Adicione os métodos para carregar e salvar posts.
-
Adicione um Método de Posts: Em
index.php, adicione um método para carregar posts:Flight::map('posts', function () { $file = __DIR__ . '/../data/posts.json'; return json_decode(file_get_contents($file), true); }); -
Atualize Rotas: Modifique
app/config/routes.phppara usar os posts:<?php Flight::route('/', function () { $posts = Flight::posts(); Flight::view()->render('home.latte', [ 'title' => 'My Blog', 'posts' => $posts ]); }); Flight::route('/post/@slug', function ($slug) { $posts = Flight::posts(); $post = array_filter($posts, fn($p) => $p['slug'] === $slug); $post = reset($post) ?: null; if (!$post) { Flight::notFound(); return; } Flight::view()->render('post.latte', [ 'title' => $post['title'], 'post' => $post ]); }); Flight::route('GET /create', function () { Flight::view()->render('create.latte', ['title' => 'Create a Post']); });
Passo 6: Crie Templates
Atualize seus templates para exibir posts.
-
Página do Post (
app/views/post.latte):{extends 'layout.latte'} {block content} <h2>{$post['title']}</h2> <div class="post-content"> <p>{$post['content']}</p> </div> {/block}
Passo 7: Adicione Criação de Posts
Lide com o envio do formulário para adicionar novos posts.
-
Crie o Formulário (
app/views/create.latte):{extends 'layout.latte'} {block content} <h2>{$title}</h2> <form method="POST" action="/create"> <div class="form-group"> <label for="title">Title:</label> <input type="text" name="title" id="title" required> </div> <div class="form-group"> <label for="content">Content:</label> <textarea name="content" id="content" required></textarea> </div> <button type="submit">Save Post</button> </form> {/block} -
Adicione a Rota POST: Em
app/config/routes.php:Flight::route('POST /create', function () { $request = Flight::request(); $title = $request->data['title']; $content = $request->data['content']; $slug = strtolower(str_replace(' ', '-', $title)); $posts = Flight::posts(); $posts[] = ['slug' => $slug, 'title' => $title, 'content' => $content]; file_put_contents(__DIR__ . '/../../data/posts.json', json_encode($posts, JSON_PRETTY_PRINT)); Flight::redirect('/'); }); -
Teste:
- Visite
http://localhost:8000/create. - Envie um novo post (ex.: “Segundo Post” com algum conteúdo).
- Verifique a página inicial para vê-lo listado.
- Visite
Passo 8: Melhore com Tratamento de Erros
Sobrescreva o método notFound para uma melhor experiência de 404.
Em index.php:
Flight::map('notFound', function () {
Flight::view()->render('404.latte', ['title' => 'Page Not Found']);
});
Crie app/views/404.latte:
{extends 'layout.latte'}
{block content}
<h2>404 - {$title}</h2>
<p>Sorry, that page doesn't exist!</p>
{/block}
Próximos Passos
- Adicione Estilos: Use CSS em seus templates para uma melhor aparência.
- Banco de Dados: Substitua
posts.jsonpor um banco de dados como SQLite usando SimplePdo. - Validação: Adicione verificações para slugs duplicados ou entradas vazias.
- Middleware: Implemente autenticação para a criação de posts.
Conclusão
Você construiu um blog simples com Flight PHP! Este guia demonstra recursos principais como roteamento, templates com Latte e manipulação de envios de formulários — tudo enquanto mantém as coisas leves. Explore a documentação do Flight para recursos mais avançados e leve seu blog adiante!
License
The MIT License (MIT)
Direitos de autor © 2024 @mikecao, @n0nag0n
É concedida permissão, gratuitamente, a qualquer pessoa que obtenha uma cópia deste software e documentação associada (arquivo PDF), 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 que as pessoas a quem o software é fornecido o façam, sujeito às seguintes condições:
O aviso de direitos de autor acima e este aviso de permissão devem ser incluídos em todas as cópias ou partes 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 COMERCIALIZAÇÃO, ADEQUAÇÃO A UM FIM ESPECÍFICO E NÃO VIOLAÇÃO. EM NENHUM CASO OS AUTORES OU DETENTORES DE DIREITOS AUTORAIS SERÃO RESPONSÁVEIS POR QUALQUER REIVINDICAÇÃO, DANOS OU OUTRA RESPONSABILIDADE, SEJA EM UMA AÇÃO DE CONTRATO, DELITO OU DE OUTRA FORMA, DECORRENTE DE, FORA DE OU EM CONEXÃO COM O SOFTWARE OU O USO OU OUTROS ACORDOS NO SOFTWARE.
About
Flight PHP Framework
Flight é um framework rápido, simples e extensível para PHP—construído para desenvolvedores que querem realizar tarefas rapidamente, sem complicações. Seja você construindo um aplicativo web clássico, uma API ultrarrápida, ou trabalhando com assistentes de codificação de IA, o baixo consumo de recursos e o design direto do Flight o tornam uma escolha perfeita. O Flight foi projetado para ser leve, mas também pode atender aos requisitos de arquitetura empresarial.
Por que Escolher o Flight?
- Amigável para Iniciantes: O Flight é um ótimo ponto de partida para novos desenvolvedores PHP. Sua estrutura clara e sintaxe simples ajudam você a aprender desenvolvimento web sem se perder em código repetitivo.
- Amado pelos Profissionais: Desenvolvedores experientes adoram o Flight pela sua flexibilidade e controle. Você pode escalar de um pequeno protótipo para um aplicativo completo sem precisar trocar de frameworks.
- Compatibilidade Retroativa: Valorizamos seu tempo. O Flight v3 é uma evolução do v2, mantendo quase toda a mesma API. Acreditamos em evolução, não em revolução—sem mais "quebrar tudo" toda vez que uma versão principal é lançada.
- Zero Dependências: O núcleo do Flight é completamente livre de dependências—sem polyfills, sem pacotes externos, nem mesmo interfaces PSR. Isso significa menos vetores de ataque, um footprint menor e sem alterações inesperadas vindas de dependências de terceiros. Plugins opcionais podem incluir dependências, mas o núcleo sempre permanecerá leve e seguro.
- Amigável para IA: A pequena superfície de API do Flight e o esqueleto oficial (um layout,
AGENTS.md, injeção de construtor) facilitam para ferramentas de codificação de IA permanecerem no padrão. Mesma base de código, independentemente de você digitar cada linha ou trabalhar com um agente. Saiba mais sobre usar IA com Flight.
Visão Geral em Vídeo
Início Rápido
Para fazer uma instalação básica rápida, instale-o com o Composer:
composer require flightphp/core
Ou você pode baixar um zip do repositório aqui. Então você terá um arquivo index.php básico como o seguinte:
<?php
// se instalado com composer
require 'vendor/autoload.php';
// ou se instalado manualmente por arquivo zip
// require 'flight/Flight.php';
Flight::route('/', function() {
echo 'hello world!';
});
Flight::route('/json', function() {
Flight::json([
'hello' => 'world'
]);
});
Flight::start();
Isso é tudo! Você tem uma aplicação Flight básica. Agora você pode executar este arquivo com php -S localhost:8000 e visitar http://localhost:8000 no seu navegador para ver o resultado.
Exemplos curtos como Flight:: são ótimos para aprendizado e micro aplicativos. Para um layout de projeto completo que humanos e ferramentas de IA compartilham, use o esqueleto abaixo.
Aplicativo Esqueleto/Boilerplate
Existe um inicializador oficial para ajudar você a começar qualquer novo projeto Flight. Ele configura estrutura, configuração, scripts do Composer e instruções amigáveis para IA desde o início.
Confira flightphp/skeleton para um projeto pronto para uso, ou visite a página de exemplos para inspiração. Quer os detalhes do fluxo de trabalho com IA? Explore IA e experiência do desenvolvedor.
O que você obtém (nível alto):
- Namespaces
App\com pastas em PascalCase (app/Controller/,app/Middleware/,app/Model/, …)—a capitalização da pasta deve corresponder ao namespace (veja Autoloading) - Injeção de Dice +
Enginepara que os controladores permaneçam testáveis (prefira$this->appao invés deFlight::no código do aplicativo) - Visualizações Twig, SimplePdo + exemplo de ActiveRecord, migrate do Runway
AGENTS.mdna raiz (mais cópias com escopo) eSECURITY.mdpara assistentes e política de segurança
Instalando o Aplicativo Esqueleto
Simples o suficiente!
# Criar o novo projeto
composer create-project flightphp/skeleton my-project/
# Entrar no diretório do novo projeto
cd my-project/
# Iniciar o servidor de desenvolvimento local para começar imediatamente!
composer start
Ele cria a estrutura do projeto, copia config_sample.php → config.php (e .env.example → .env quando presente), e você está pronto para começar. Dados de amostra opcionais:
php runway migrate
# então visite /posts e /api/posts
Alto Desempenho
O Flight é um dos frameworks PHP mais rápidos disponíveis. Seu núcleo leve significa menos sobrecarga e mais velocidade—perfeito tanto para aplicativos tradicionais quanto para fluxos de trabalho modernos assistidos por IA. Você pode ver todos os benchmarks em TechEmpower
Veja o benchmark abaixo com alguns outros frameworks PHP populares.
| Framework | Requisições/sec Texto Simples | Requisições/sec JSON |
|---|---|---|
| Flight | 190,421 | 182,491 |
| Yii | 145,749 | 131,434 |
| Fat-Free | 139,238 | 133,952 |
| Slim | 89,588 | 87,348 |
| Phalcon | 95,911 | 87,675 |
| Symfony | 65,053 | 63,237 |
| Lumen | 40,572 | 39,700 |
| Laravel | 26,657 | 26,901 |
| CodeIgniter | 20,628 | 19,901 |
Flight e IA
Curioso sobre como o Flight funciona com LLMs de codificação? Descubra como AGENTS.md, comandos ai:* do Runway e o layout do esqueleto mantêm os assistentes no caminho certo.
Estabilidade e Compatibilidade Retroativa
Valorizamos seu tempo. Já vimos frameworks que se reinventam completamente a cada poucos anos, deixando desenvolvedores com código quebrado e migrações caras. O Flight é diferente. O Flight v3 foi projetado como uma evolução do v2, o que significa que a API que você conhece e ama não foi removida. Na verdade, a maioria dos projetos v2 funcionará sem nenhuma alteração no v3.
Estamos comprometidos em manter o Flight estável para que você possa focar na construção do seu aplicativo, não na correção do seu framework. O esqueleto pode ser opinativo para projetos novos; as APIs principais permanecem familiares para todos os outros.
Comunidade
Estamos no Matrix Chat
E no Discord
Contribuindo
Existem duas maneiras de você contribuir com o Flight:
- Contribua com o framework principal visitando o repositório principal.
- Ajude a melhorar a documentação! Este site de documentação está hospedado no Github. Se você encontrar um erro ou quiser melhorar algo, sinta-se à vontade para enviar um pull request. Adoramos atualizações e novas ideias—especialmente relacionadas a IA e novas tecnologias!
Requisitos
O Flight requer PHP 7.4 ou superior.
Nota: O PHP 7.4 é suportado porque no momento atual da escrita (2024) o PHP 7.4 é a versão padrão para algumas distribuições Linux LTS. Forçar uma migração para PHP >8 causaria muitos problemas para esses usuários. O framework também suporta PHP >8.
Licença
O Flight é lançado sob a licença MIT.
Awesome-plugins/php_cookie
Cookies
overclokk/cookie é uma biblioteca simples para gerenciar cookies em seu aplicativo.
Instalação
A instalação é simples com o composer.
composer require overclokk/cookie
Uso
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');
}
}
}Awesome-plugins/php_encryption
Criptografia PHP
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.
Instalação
A instalação é simples com o composer.
composer require defuse/php-encryption
Configuração
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.
Uso
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;
});Awesome-plugins/php_file_cache
flightphp/cache
Classe leve, simples e independente de cache em PHP em arquivo, bifurcada de Wruczek/PHP-File-Cache
Vantagens
- Leve, independente e simples
- Todo o código em um único arquivo - sem drivers desnecessários.
- Seguro - todo arquivo de cache gerado possui um cabeçalho PHP com die, tornando o acesso direto impossível mesmo que alguém conheça o caminho e seu servidor não esteja configurado corretamente
- Bem documentado e testado
- Lida corretamente com concorrência via flock
- Suporta PHP 7.4+
- Gratuito sob licença MIT
Este site de documentação está usando esta biblioteca para fazer cache de cada uma das páginas!
Clique aqui para ver o código.
Instalação
Instale via composer:
composer require flightphp/cache
Uso
O uso é bastante simples. Isso salva um arquivo de cache no diretório de cache.
use flight\Cache;
$app = Flight::app();
// Você passa o diretório onde o cache será armazenado no construtor
$app->register('cache', Cache::class, [ __DIR__ . '/../cache/' ], function(Cache $cache) {
// Isso garante que o cache seja usado apenas quando estiver em modo de produção
// ENVIRONMENT é uma constante que é definida no seu arquivo bootstrap ou em outro lugar da sua aplicação
$cache->setDevMode(ENVIRONMENT === 'development');
});
Obter um Valor de Cache
Você usa o método get() para obter um valor em cache. Se quiser um método de conveniência que atualize o cache se ele estiver expirado, pode usar refreshIfExpired().
// Obter instância do cache
$cache = Flight::cache();
$data = $cache->refreshIfExpired('simple-cache-test', function () {
return date("H:i:s"); // retorna os dados a serem armazenados em cache
}, 10); // 10 segundos
// ou
$data = $cache->get('simple-cache-test');
if(empty($data)) {
$data = date("H:i:s");
$cache->set('simple-cache-test', $data, 10); // 10 segundos
}
Armazenar um Valor de Cache
Você usa o método set() para armazenar um valor no cache.
Flight::cache()->set('simple-cache-test', 'meus dados em cache', 10); // 10 segundos
Apagar um Valor de Cache
Você usa o método delete() para apagar um valor no cache.
Flight::cache()->delete('simple-cache-test');
Verificar se um Valor de Cache Existe
Você usa o método exists() para verificar se um valor existe no cache.
if(Flight::cache()->exists('simple-cache-test')) {
// fazer algo
}
Limpar o Cache
Você usa o método flush() para limpar todo o cache.
Flight::cache()->flush();
Extrair metadados com cache
Se você quiser extrair timestamps e outros metadados sobre uma entrada de cache, certifique-se de passar true como parâmetro correto.
$data = $cache->refreshIfExpired("simple-cache-meta-test", function () {
echo "Atualizando dados!" . PHP_EOL;
return date("H:i:s"); // retorna os dados a serem armazenados em cache
}, 10, true); // true = retorna com metadados
// ou
$data = $cache->get("simple-cache-meta-test", true); // true = retorna com metadados
/*
Exemplo de item em cache recuperado com metadados:
{
"time":1511667506, <-- timestamp unix de salvamento
"expire":10, <-- tempo de expiração em segundos
"data":"04:38:26", <-- dados desserializados
"permanent":false
}
Usando metadados, podemos, por exemplo, calcular quando o item foi salvo ou quando expira
Também podemos acessar os próprios dados com a chave "data"
*/
$expiresin = ($data["time"] + $data["expire"]) - time(); // obtém o timestamp unix quando os dados expiram e subtrai o timestamp atual dele
$cacheddate = $data["data"]; // acessamos os próprios dados com a chave "data"
echo "Último salvamento do cache: $cacheddate, expira em $expiresin segundos";
Código Fonte
Visite https://github.com/flightphp/cache para ver o código.
Awesome-plugins/permissions
FlightPHP/Permissions
Este é um módulo de permissões que pode ser usado em seus projetos se você tiver múltiplas funções em sua aplicação e cada função tiver uma funcionalidade um pouco diferente. Este módulo permite que você defina permissões para cada função e então verifique se o usuário atual tem a permissão para acessar uma determinada página ou realizar uma determinada ação.
Clique aqui para o repositório no GitHub.
Instalação
Execute composer require flightphp/permissions e você está pronto!
Uso
Primeiro você precisa configurar suas permissões, depois você informa ao seu aplicativo o que as permissões significam. Em última análise, você verificará suas permissões com $Permissions->has(), ->can(), ou is(). has() e can() têm a mesma funcionalidade, mas são nomeados de forma diferente para tornar seu código mais legível.
Exemplo Básico
Vamos supor que você tenha um recurso em sua aplicação que verifica se um usuário está logado. Você pode criar um objeto de permissões assim:
// index.php
require 'vendor/autoload.php';
// algum código
// então você provavelmente tem algo que lhe diz qual é a função atual da pessoa
// provavelmente você tem algo onde puxa a função atual
// de uma variável de sessão que define isso
// depois que alguém faz login, caso contrário terão uma função 'guest' ou 'public'.
$current_role = 'admin';
// configura permissões
$permission = new \flight\Permission($current_role);
$permission->defineRule('loggedIn', function($current_role) {
return $current_role !== 'guest';
});
// Você provavelmente vai querer persistir este objeto no Flight em algum lugar
Flight::set('permission', $permission);
Então em algum controlador, você pode ter algo assim.
<?php
// algum controlador
class SomeController {
public function someAction() {
$permission = Flight::get('permission');
if ($permission->has('loggedIn')) {
// faz algo
} else {
// faz outra coisa
}
}
}
Você também pode usar isso para rastrear se eles têm permissão para fazer algo em sua aplicação. Por exemplo, se você tem uma forma dos usuários interagirem com postagens em seu software, você pode verificar se eles têm permissão para realizar determinadas ações.
$current_role = 'admin';
// configura permissões
$permission = new \flight\Permission($current_role);
$permission->defineRule('post', function($current_role) {
if($current_role === 'admin') {
$permissions = ['create', 'read', 'update', 'delete'];
} else if($current_role === 'editor') {
$permissions = ['create', 'read', 'update'];
} else if($current_role === 'author') {
$permissions = ['create', 'read'];
} else if($current_role === 'contributor') {
$permissions = ['create'];
} else {
$permissions = [];
}
return $permissions;
});
Flight::set('permission', $permission);
Então em algum controlador...
class PostController {
public function create() {
$permission = Flight::get('permission');
if ($permission->can('post.create')) {
// faz algo
} else {
// faz outra coisa
}
}
}
Injetando dependências
Você pode injetar dependências no closure que define as permissões. Isso é útil se você tiver algum tipo de alternância, id, ou qualquer outro ponto de dados que você queira verificar. O mesmo funciona para chamadas do tipo Class->Method, exceto que você define os argumentos no método.
Closures
$Permission->defineRule('order', function(string $current_role, MyDependency $MyDependency = null) {
// ... código
});
// no seu arquivo de controlador
public function createOrder() {
$MyDependency = Flight::myDependency();
$permission = Flight::get('permission');
if ($permission->can('order.create', $MyDependency)) {
// faz algo
} else {
// faz outra coisa
}
}
Classes
namespace MyApp;
class Permissions {
public function order(string $current_role, MyDependency $MyDependency = null) {
// ... código
}
}
Atalho para definir permissões com classes
Você também pode usar classes para definir suas permissões. Isso é útil se você tiver muitas permissões e quiser manter seu código limpo. Você pode fazer algo assim:
<?php
// código bootstrap
$Permissions = new \flight\Permission($current_role);
$Permissions->defineRule('order', 'MyApp\Permissions->order');
// myapp/Permissions.php
namespace MyApp;
class Permissions {
public function order(string $current_role, int $user_id) {
// Assumindo que você configurou isso previamente
/** @var \flight\database\SimplePdo $db */
$db = Flight::db();
$allowed_permissions = [ 'read' ]; // todos podem visualizar um pedido
if($current_role === 'manager') {
$allowed_permissions[] = 'create'; // gerentes podem criar pedidos
}
$some_special_toggle_from_db = $db->fetchField('SELECT some_special_toggle FROM settings WHERE id = ?', [ $user_id ]);
if($some_special_toggle_from_db) {
$allowed_permissions[] = 'update'; // se o usuário tiver um alternador especial, eles podem atualizar pedidos
}
if($current_role === 'admin') {
$allowed_permissions[] = 'delete'; // administradores podem excluir pedidos
}
return $allowed_permissions;
}
}
A parte legal é que também existe um atalho que você pode usar (que também pode ser armazenado em cache!!!) onde você simplesmente informa à classe de permissões para mapear todos os métodos em uma classe para permissões. Então se você tem um método chamado order() e um método chamado company(), estes serão automaticamente mapeados para que você possa simplesmente executar $Permissions->has('order.read') ou $Permissions->has('company.read') e funcionará. Definir isso é muito difícil, então fique comigo aqui. Você só precisa fazer isso:
Crie a classe de permissões que você quer agrupar.
class MyPermissions {
public function order(string $current_role, int $order_id = 0): array {
// código para determinar permissões
return $permissions_array;
}
public function company(string $current_role, int $company_id): array {
// código para determinar permissões
return $permissions_array;
}
}
Então torne as permissões descobríveis usando esta biblioteca.
$Permissions = new \flight\Permission($current_role);
$Permissions->defineRulesFromClassMethods(MyApp\Permissions::class);
Flight::set('permissions', $Permissions);
Finalmente, chame a permissão em sua base de código para verificar se o usuário está autorizado a realizar uma determinada permissão.
class SomeController {
public function createOrder() {
if(Flight::get('permissions')->can('order.create') === false) {
die('Você não pode criar um pedido. Desculpe!');
}
}
}
Cache
Para habilitar o cache, veja a simples biblioteca wruczak/phpfilecache. Um exemplo de como habilitar isso está abaixo.
// este $app pode fazer parte do seu código, ou
// você pode simplesmente passar null e ele
// puxará de Flight::app() no construtor
$app = Flight::app();
// Por enquanto aceita isso como um cache de arquivo. Outros podem ser facilmente
// adicionados no futuro.
$Cache = new Wruczek\PhpFileCache\PhpFileCache;
$Permissions = new \flight\Permission($current_role, $app, $Cache);
$Permissions->defineRulesFromClassMethods(MyApp\Permissions::class, 3600); // 3600 é quantos segundos para armazenar em cache. Deixe isso de fora para não usar cache
E pronto!
Awesome-plugins/simple_job_queue
Fila de Trabalho Simples
A Fila de Trabalho Simples é uma biblioteca que pode ser usada para processar trabalhos de forma assíncrona. Pode ser usada com beanstalkd, MySQL/MariaDB, SQLite e PostgreSQL.
Instalação
composer require n0nag0n/simple-job-queue
Uso
Para que isso funcione, você precisa de uma maneira de adicionar trabalhos à fila e uma maneira de processar os trabalhos (um trabalhador). Abaixo estão exemplos de como adicionar um trabalho à fila e como processar o trabalho.
Adicionando ao Flight
Adicionar isso ao Flight é simples e é feito usando o método register(). Abaixo está um exemplo de como adicionar isso ao Flight.
<?php
require 'vendor/autoload.php';
// Mude ['mysql'] para ['beanstalkd'] se você quiser usar beanstalkd
Flight::register('queue', n0nag0n\Job_Queue::class, ['mysql'], function($Job_Queue) {
// se você já tiver uma conexão PDO em Flight::db();
$Job_Queue->addQueueConnection(Flight::db());
// ou se você estiver usando beanstalkd/Pheanstalk
$pheanstalk = Pheanstalk\Pheanstalk::create('127.0.0.1');
$Job_Queue->addQueueConnection($pheanstalk);
});
Adicionando um novo trabalho
Quando você adiciona um trabalho, precisa especificar um pipeline (fila). Isso é comparável a um canal no RabbitMQ ou um tubo no beanstalkd.
<?php
Flight::queue()->selectPipeline('send_important_emails');
Flight::queue()->addJob(json_encode([ 'something' => 'that', 'ends' => 'up', 'a' => 'string' ]));
Executando um trabalhador
Aqui está um arquivo de exemplo de como executar um trabalhador.
<?php
require 'vendor/autoload.php';
$Job_Queue = new n0nag0n\Job_Queue('mysql');
// Conexão PDO
$PDO = new PDO('mysql:dbname=testdb;host=127.0.0.1', 'user', 'pass');
$Job_Queue->addQueueConnection($PDO);
// ou se você estiver usando beanstalkd/Pheanstalk
$pheanstalk = Pheanstalk\Pheanstalk::create('127.0.0.1');
$Job_Queue->addQueueConnection($pheanstalk);
$Job_Queue->watchPipeline('send_important_emails');
while(true) {
$job = $Job_Queue->getNextJobAndReserve();
// ajuste para o que te faz dormir melhor à noite (para filas de banco de dados apenas, beanstalkd não precisa dessa instrução if)
if(empty($job)) {
usleep(500000);
continue;
}
echo "Processando {$job['id']}\n";
$payload = json_decode($job['payload'], true);
try {
$result = doSomethingThatDoesSomething($payload);
if($result === true) {
$Job_Queue->deleteJob($job);
} else {
// isso o tira da fila de prontos e o coloca em outra fila que pode ser coletada e "chutada" mais tarde.
$Job_Queue->buryJob($job);
}
} catch(Exception $e) {
$Job_Queue->buryJob($job);
}
}
Manipulando Processos Longos com Supervisord
O Supervisord é um sistema de controle de processos que garante que seus processos de trabalhadores permaneçam em execução continuamente. Aqui está um guia mais completo sobre como configurá-lo com seu trabalhador da Fila de Trabalho Simples:
Instalando o Supervisord
# No Ubuntu/Debian
sudo apt-get install supervisor
# No CentOS/RHEL
sudo yum install supervisor
# No macOS com Homebrew
brew install supervisor
Criando um Script de Trabalhador
Primeiro, salve seu código de trabalhador em um arquivo PHP dedicado:
<?php
require 'vendor/autoload.php';
$Job_Queue = new n0nag0n\Job_Queue('mysql');
// Conexão PDO
$PDO = new PDO('mysql:dbname=your_database;host=127.0.0.1', 'username', 'password');
$Job_Queue->addQueueConnection($PDO);
// Defina o pipeline a ser monitorado
$Job_Queue->watchPipeline('send_important_emails');
// Registre o início do trabalhador
echo date('Y-m-d H:i:s') . " - Trabalhador iniciado\n";
while(true) {
$job = $Job_Queue->getNextJobAndReserve();
if(empty($job)) {
usleep(500000); // Durma por 0,5 segundos
continue;
}
echo date('Y-m-d H:i:s') . " - Processando trabalho {$job['id']}\n";
$payload = json_decode($job['payload'], true);
try {
$result = doSomethingThatDoesSomething($payload);
if($result === true) {
$Job_Queue->deleteJob($job);
echo date('Y-m-d H:i:s') . " - Trabalho {$job['id']} completado com sucesso\n";
} else {
$Job_Queue->buryJob($job);
echo date('Y-m-d H:i:s') . " - Trabalho {$job['id']} falhou, enterrado\n";
}
} catch(Exception $e) {
$Job_Queue->buryJob($job);
echo date('Y-m-d H:i:s') . " - Exceção ao processar o trabalho {$job['id']}: {$e->getMessage()}\n";
}
}
Configurando o Supervisord
Crie um arquivo de configuração para seu trabalhador:
[program:email_worker]
command=php /path/to/worker.php
directory=/path/to/project
autostart=true
autorestart=true
startretries=3
stderr_logfile=/var/log/simple_job_queue_err.log
stdout_logfile=/var/log/simple_job_queue.log
user=www-data
numprocs=2
process_name=%(program_name)s_%(process_num)02d
Principais Opções de Configuração:
command: O comando para executar seu trabalhadordirectory: Diretório de trabalho para o trabalhadorautostart: Iniciar automaticamente quando o supervisord iniciarautorestart: Reiniciar automaticamente se o processo sairstartretries: Número de tentativas para reiniciar se falharstderr_logfile/stdout_logfile: Localizações dos arquivos de loguser: Usuário do sistema para executar o processonumprocs: Número de instâncias de trabalhador a serem executadasprocess_name: Formato de nomenclatura para vários processos de trabalhadores
Gerenciando Trabalhadores com Supervisorctl
Após criar ou modificar a configuração:
# Recarregar a configuração do supervisor
sudo supervisorctl reread
sudo supervisorctl update
# Controlar processos de trabalhadores específicos
sudo supervisorctl start email_worker:*
sudo supervisorctl stop email_worker:*
sudo supervisorctl restart email_worker:*
sudo supervisorctl status email_worker:*
Executando Múltiplos Pipelines
Para múltiplos pipelines, crie arquivos de trabalhadores e configurações separados:
[program:email_worker]
command=php /path/to/email_worker.php
# ... outras configurações ...
[program:notification_worker]
command=php /path/to/notification_worker.php
# ... outras configurações ...
Monitoramento e Logs
Verifique os logs para monitorar a atividade dos trabalhadores:
# Ver logs
sudo tail -f /var/log/simple_job_queue.log
# Verificar status
sudo supervisorctl status
Essa configuração garante que seus trabalhadores de tarefas continuem em execução mesmo após falhas, reinicializações de servidor ou outros problemas, tornando seu sistema de fila confiável para ambientes de produção.
Awesome-plugins/jwt
Firebase JWT - Autenticação com JSON Web Token
JWT (JSON Web Tokens) são uma forma compacta e segura para URLs de representar reivindicações entre sua aplicação e um cliente. Eles são perfeitos para autenticação de API sem estado — sem necessidade de armazenamento de sessão no lado do servidor! Este guia mostra como integrar Firebase JWT com Flight para autenticação segura baseada em tokens.
Visite o repositório no Github para documentação completa e detalhes.
O que é JWT?
Um JSON Web Token é uma string que contém três partes:
- Cabeçalho: Metadados sobre o token (algoritmo, tipo)
- Carga útil: Seus dados (ID do usuário, papéis, expiração, etc.)
- Assinatura: Assinatura criptográfica para verificar a autenticidade
Exemplo de JWT: eyJ0eXAiOiJKV1QiLCJhbGc... (parece gibberish, mas é dado estruturado!)
Por que Usar JWT?
- Sem estado: Não é necessário armazenamento de sessão no lado do servidor — perfeito para microsserviços e APIs
- Escalável: Funciona muito bem com balanceadores de carga, pois não há requisito de afinidade de sessão
- Cross-Domain: Pode ser usado em diferentes domínios e serviços
- Amigável para Mobile: Ótimo para aplicativos móveis onde cookies podem não funcionar bem
- Padronizado: Abordagem padrão da indústria (RFC 7519)
Instalação
Instale via Composer:
composer require firebase/php-jwt
Uso Básico
Aqui está um exemplo rápido de criação e verificação de um JWT:
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
// Sua chave secreta (MANTENHA ISSO SEGURO!)
$secretKey = 'your-256-bit-secret-key-here-keep-it-safe';
// Crie um token
$payload = [
'user_id' => 123,
'username' => 'johndoe',
'role' => 'admin',
'iat' => time(), // Emitido em
'exp' => time() + 3600 // Expira em 1 hora
];
$jwt = JWT::encode($payload, $secretKey, 'HS256');
echo "Token: " . $jwt;
// Verifique e decodifique um token
try {
$decoded = JWT::decode($jwt, new Key($secretKey, 'HS256'));
echo "User ID: " . $decoded->user_id;
} catch (Exception $e) {
echo "Token inválido: " . $e->getMessage();
}
Middleware JWT para Flight (Abordagem Recomendada)
A forma mais comum e útil de usar JWT com Flight é como middleware para proteger suas rotas de API. Aqui está um exemplo completo, pronto para produção:
Passo 1: Crie uma Classe de Middleware JWT
// app/middleware/JwtMiddleware.php
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
use Firebase\JWT\ExpiredException;
use Firebase\JWT\SignatureInvalidException;
use flight\Engine;
class JwtMiddleware {
protected Engine $app;
protected string $secretKey;
public function __construct(Engine $app) {
$this->app = $app;
// Armazene sua chave secreta em app/config/config.php, NÃO hardcoded!
$this->secretKey = $app->get('config')['jwt_secret'];
}
public function before(array $params) {
$authHeader = $this->app->request()->getHeader('Authorization');
// Verifique se o cabeçalho Authorization existe
if (empty($authHeader)) {
$this->app->jsonHalt(['error' => 'Nenhum token de autorização fornecido'], 401);
}
// Extraia o token do formato "Bearer <token>"
if (!preg_match('/Bearer\s+(.*)$/i', $authHeader, $matches)) {
$this->app->jsonHalt(['error' => 'Formato de autorização inválido. Use: Bearer <token>'], 401);
}
$jwt = $matches[1];
try {
// Decodifique e verifique o token
$decoded = JWT::decode($jwt, new Key($this->secretKey, 'HS256'));
// Armazene os dados do usuário na requisição para uso em manipuladores de rota
$this->app->request()->data->user = $decoded;
} catch (ExpiredException $e) {
$this->app->jsonHalt(['error' => 'Token expirou'], 401);
} catch (SignatureInvalidException $e) {
$this->app->jsonHalt(['error' => 'Assinatura de token inválida'], 401);
} catch (Exception $e) {
$this->app->jsonHalt(['error' => 'Token inválido: ' . $e->getMessage()], 401);
}
}
}
Passo 2: Registre a Chave Secreta JWT na Sua Configuração
// app/config/config.php
return [
'jwt_secret' => getenv('JWT_SECRET') ?: 'your-fallback-secret-for-development'
];
// app/config/bootstrap.php ou index.php
// certifique-se de adicionar esta linha se quiser expor a configuração para a app
$app->set('config', $config);
Nota de Segurança: Nunca codifique sua chave secreta! Use variáveis de ambiente em produção.
Passo 3: Proteja Suas Rotas com Middleware
// Proteja uma rota única
Flight::route('GET /api/user/profile', function() {
$user = Flight::request()->data->user; // Definido pelo middleware
Flight::json([
'user_id' => $user->user_id,
'username' => $user->username,
'role' => $user->role
]);
})->addMiddleware(JwtMiddleware::class);
// Proteja um grupo inteiro de rotas (mais comum!)
Flight::group('/api', function() {
Flight::route('GET /users', function() { /* ... */ });
Flight::route('GET /posts', function() { /* ... */ });
Flight::route('POST /posts', function() { /* ... */ });
Flight::route('DELETE /posts/@id', function($id) { /* ... */ });
}, [ JwtMiddleware::class ]); // Todas as rotas neste grupo estão protegidas!
Para mais detalhes sobre middleware, veja a documentação de middleware.
Casos de Uso Comuns
1. Endpoint de Login (Geração de Token)
Crie uma rota que gera um JWT após autenticação bem-sucedida:
Flight::route('POST /api/login', function() {
$data = Flight::request()->data;
$username = $data->username ?? '';
$password = $data->password ?? '';
// Valide credenciais (exemplo - use sua própria lógica!)
$user = validateUserCredentials($username, $password);
if (!$user) {
Flight::jsonHalt(['error' => 'Credenciais inválidas'], 401);
}
// Gere JWT
$secretKey = Flight::get('config')['jwt_secret'];
$payload = [
'user_id' => $user->id,
'username' => $user->username,
'role' => $user->role,
'iat' => time(),
'exp' => time() + (60 * 60) // Expiração em 1 hora
];
$jwt = JWT::encode($payload, $secretKey, 'HS256');
Flight::json([
'success' => true,
'token' => $jwt,
'expires_in' => 3600
]);
});
function validateUserCredentials($username, $password) {
// Sua consulta ao banco de dados e verificação de senha aqui
// Exemplo:
$db = Flight::db();
$user = $db->fetchRow("SELECT * FROM users WHERE username = ?", [$username]);
if ($user && password_verify($password, $user['password_hash'])) {
return (object) [
'id' => $user['id'],
'username' => $user['username'],
'role' => $user['role']
];
}
return null;
}
2. Fluxo de Atualização de Token
Implemente um sistema de token de atualização para sessões de longa duração:
Flight::route('POST /api/login', function() {
// ... valide credenciais ...
$secretKey = Flight::get('config')['jwt_secret'];
$refreshSecret = Flight::get('config')['jwt_refresh_secret'];
// Token de acesso de curta duração (15 minutos)
$accessToken = JWT::encode([
'user_id' => $user->id,
'type' => 'access',
'iat' => time(),
'exp' => time() + (15 * 60)
], $secretKey, 'HS256');
// Token de atualização de longa duração (7 dias)
$refreshToken = JWT::encode([
'user_id' => $user->id,
'type' => 'refresh',
'iat' => time(),
'exp' => time() + (7 * 24 * 60 * 60)
], $refreshSecret, 'HS256');
Flight::json([
'access_token' => $accessToken,
'refresh_token' => $refreshToken,
'expires_in' => 900
]);
});
Flight::route('POST /api/refresh', function() {
$refreshToken = Flight::request()->data->refresh_token ?? '';
$refreshSecret = Flight::get('config')['jwt_refresh_secret'];
try {
$decoded = JWT::decode($refreshToken, new Key($refreshSecret, 'HS256'));
// Verifique se é um token de atualização
if ($decoded->type !== 'refresh') {
Flight::jsonHalt(['error' => 'Tipo de token inválido'], 401);
}
// Gere novo token de acesso
$secretKey = Flight::get('config')['jwt_secret'];
$accessToken = JWT::encode([
'user_id' => $decoded->user_id,
'type' => 'access',
'iat' => time(),
'exp' => time() + (15 * 60)
], $secretKey, 'HS256');
Flight::json([
'access_token' => $accessToken,
'expires_in' => 900
]);
} catch (Exception $e) {
Flight::jsonHalt(['error' => 'Token de atualização inválido'], 401);
}
});
3. Controle de Acesso Baseado em Papéis
Estenda seu middleware para verificar papéis de usuário:
class JwtRoleMiddleware {
protected Engine $app;
protected array $allowedRoles;
public function __construct(Engine $app, array $allowedRoles = []) {
$this->app = $app;
$this->allowedRoles = $allowedRoles;
}
public function before(array $params) {
// Assuma que JwtMiddleware já executou e definiu os dados do usuário
$user = $this->app->request()->data->user ?? null;
if (!$user) {
$this->app->jsonHalt(['error' => 'Autenticação necessária'], 401);
}
// Verifique se o usuário tem o papel necessário
if (!empty($this->allowedRoles) && !in_array($user->role, $this->allowedRoles)) {
$this->app->jsonHalt(['error' => 'Permissões insuficientes'], 403);
}
}
}
// Uso: Rota apenas para admin
Flight::route('DELETE /api/users/@id', function($id) {
// Lógica de exclusão de usuário
})->addMiddleware([
JwtMiddleware::class,
new JwtRoleMiddleware(Flight::app(), ['admin'])
]);
4. API Pública com Limitação de Taxa por Usuário
Use JWT para rastrear e limitar a taxa de usuários sem sessões:
class RateLimitMiddleware {
public function before(array $params) {
$user = Flight::request()->data->user ?? null;
$userId = $user ? $user->user_id : Flight::request()->ip;
$cacheKey = "rate_limit:$userId";
// Certifique-se de configurar um serviço de cache em app/config/services.php
$requests = Flight::cache()->get($cacheKey, 0);
if ($requests >= 100) { // 100 requisições por hora
Flight::jsonHalt(['error' => 'Limite de taxa excedido'], 429);
}
Flight::cache()->set($cacheKey, $requests + 1, 3600);
}
}
Melhores Práticas de Segurança
1. Use Chaves Secretas Fortes
// Gere uma chave secreta segura (execute uma vez, salve no arquivo .env)
$secretKey = base64_encode(random_bytes(32));
echo $secretKey; // Armazene isso no seu arquivo .env!
2. Armazene Segredos em Variáveis de Ambiente
// Nunca commite segredos no controle de versão!
// Use um arquivo .env e uma biblioteca como vlucas/phpdotenv
// Arquivo .env:
// JWT_SECRET=your-base64-encoded-secret-here
// JWT_REFRESH_SECRET=another-base64-encoded-secret-here
// Você também pode usar o arquivo app/config/config.php para armazenar seus segredos
// apenas certifique-se de que o arquivo de configuração não seja commitado no controle de versão
// return [
// 'jwt_secret' => 'your-base64-encoded-secret-here',
// 'jwt_refresh_secret' => 'another-base64-encoded-secret-here',
// ];
// Na sua app:
$secretKey = getenv('JWT_SECRET');
3. Defina Tempos de Expiração Apropriados
// Boa prática: tokens de acesso de curta duração
'exp' => time() + (15 * 60) // 15 minutos
// Para tokens de atualização: expiração mais longa
'exp' => time() + (7 * 24 * 60 * 60) // 7 dias
4. Use HTTPS em Produção
JWTs devem sempre ser transmitidos via HTTPS. Nunca envie tokens via HTTP simples em produção!
5. Valide Reivindicações de Token
Sempre valide as reivindicações que você se importa:
$decoded = JWT::decode($jwt, new Key($secretKey, 'HS256'));
// Verificação de expiração é tratada automaticamente pela biblioteca
// Mas você pode adicionar validações personalizadas:
if ($decoded->iat > time()) {
throw new Exception('Token usado antes de ser emitido');
}
if (isset($decoded->nbf) && $decoded->nbf > time()) {
throw new Exception('Token ainda não válido');
}
6. Considere Blacklisting de Tokens para Logout
Para segurança extra, mantenha uma lista negra de tokens invalidados:
Flight::route('POST /api/logout', function() {
$authHeader = Flight::request()->getHeader('Authorization');
preg_match('/Bearer\s+(.*)$/i', $authHeader, $matches);
$jwt = $matches[1];
// Extraia a expiração do token
$decoded = Flight::request()->data->user;
$ttl = $decoded->exp - time();
// Armazene no cache/redis até a expiração
Flight::cache()->set("blacklist:$jwt", true, $ttl);
Flight::json(['message' => 'Logout realizado com sucesso']);
});
// Adicione ao seu JwtMiddleware:
public function before(array $params) {
// ... extraia JWT ...
// Verifique a lista negra
if (Flight::cache()->get("blacklist:$jwt")) {
$this->app->jsonHalt(['error' => 'Token foi revogado'], 401);
}
// ... verifique o token ...
}
Algoritmos e Tipos de Chave
Firebase JWT suporta múltiplos algoritmos:
Algoritmos Simétricos (HMAC)
- HS256 (Recomendado para a maioria dos apps): Usa uma única chave secreta
- HS384, HS512: Variantes mais fortes
$jwt = JWT::encode($payload, $secretKey, 'HS256');
$decoded = JWT::decode($jwt, new Key($secretKey, 'HS256'));
Algoritmos Assimétricos (RSA/ECDSA)
- RS256, RS384, RS512: Usa pares de chaves pública/privada
- ES256, ES384, ES512: Variantes de curva elíptica
// Gere chaves: openssl genrsa -out private.key 2048
// openssl rsa -in private.key -pubout -out public.key
$privateKey = file_get_contents('/path/to/private.key');
$publicKey = file_get_contents('/path/to/public.key');
// Codifique com chave privada
$jwt = JWT::encode($payload, $privateKey, 'RS256');
// Decodifique com chave pública
$decoded = JWT::decode($jwt, new Key($publicKey, 'RS256'));
Quando usar RSA: Use RSA quando precisar distribuir a chave pública para verificação (ex.: microsserviços, integrações de terceiros). Para uma única aplicação, HS256 é mais simples e suficiente.
Solução de Problemas
Erro "Token expirado"
A reivindicação exp do seu token está no passado. Emita um novo token ou implemente atualização de token.
"Falha na verificação de assinatura"
- Você está usando uma chave secreta diferente para decodificar do que usou para codificar
- O token foi adulterado
- Desvio de relógio entre servidores (adicione um buffer de leeway)
use Firebase\JWT\JWT;
JWT::$leeway = 60; // Permita 60 segundos de desvio de relógio
$decoded = JWT::decode($jwt, new Key($secretKey, 'HS256'));
Token Não Sendo Enviado nas Requisições
Certifique-se de que seu cliente está enviando o cabeçalho Authorization:
// Exemplo em JavaScript
fetch('/api/users', {
headers: {
'Authorization': 'Bearer ' + token
}
});
Métodos
A biblioteca Firebase JWT fornece estes métodos principais:
JWT::encode(array $payload, string $key, string $alg): Cria um JWT a partir de uma carga útilJWT::decode(string $jwt, Key $key): Decodifica e verifica um JWTJWT::urlsafeB64Encode(string $input): Codificação Base64 segura para URLJWT::urlsafeB64Decode(string $input): Decodificação Base64 segura para URLJWT::$leeway: Propriedade estática para definir leeway de tempo para validação (em segundos)
Por que Usar Esta Biblioteca?
- Padrão da Indústria: Firebase JWT é a biblioteca JWT mais popular e amplamente confiável para PHP
- Manutenção Ativa: Mantida pela equipe do Google/Firebase
- Focada em Segurança: Atualizações regulares e patches de segurança
- API Simples: Fácil de entender e implementar
- Bem Documentada: Documentação extensa e suporte da comunidade
- Flexível: Suporta múltiplos algoritmos e opções configuráveis
Veja Também
- Repositório Github do Firebase JWT
- JWT.io - Depure e decodifique JWTs
- RFC 7519 - Especificação oficial do JWT
- Documentação de Middleware do Flight
- Plugin de Sessão do Flight - Para autenticação baseada em sessão tradicional
Licença
A biblioteca Firebase JWT está licenciada sob a Licença BSD 3-Clause. Veja o repositório Github para detalhes.
Awesome-plugins/n0nag0n_wordpress
Integração com WordPress: n0nag0n/wordpress-integration-for-flight-framework
Quer usar o Flight PHP dentro do seu site WordPress? Este plugin facilita muito! Com n0nag0n/wordpress-integration-for-flight-framework, você pode executar um aplicativo Flight completo ao lado da sua instalação WordPress—perfeito para criar APIs personalizadas, microservices ou até aplicativos completos sem sair do conforto do WordPress.
O Que Ele Faz?
- Integra o Flight PHP ao WordPress de forma perfeita
- Roteia solicitações para o Flight ou WordPress com base em padrões de URL
- Organize seu código com controllers, models e views (MVC)
- Configure facilmente a estrutura de pastas recomendada do Flight
- Use a conexão de banco de dados do WordPress ou a sua própria
- Ajuste como o Flight e o WordPress interagem
- Interface administrativa simples para configuração
Instalação
- Carregue a pasta
flight-integrationno diretório/wp-content/plugins/. - Ative o plugin no admin do WordPress (menu Plugins).
- Vá para Configurações > Flight Framework para configurar o plugin.
- Defina o caminho do vendor para a sua instalação do Flight (ou use Composer para instalar o Flight).
- Configure o caminho da pasta do seu app e crie a estrutura de pastas (o plugin pode ajudar com isso!).
- Comece a criar o seu aplicativo Flight!
Exemplos de Uso
Exemplo Básico de Rota
No seu arquivo app/config/routes.php:
Flight::route('GET /api/hello', function() {
Flight::json(['message' => 'Hello World!']);
});
Exemplo de Controller
Crie um controller em app/controllers/ApiController.php:
namespace app\controllers;
use Flight;
class ApiController {
public function getUsers() {
// Você pode usar funções do WordPress dentro do Flight!
$users = get_users();
$result = [];
foreach($users as $user) {
$result[] = [
'id' => $user->ID,
'name' => $user->display_name,
'email' => $user->user_email
];
}
Flight::json($result);
}
}
Em seguida, no seu routes.php:
Flight::route('GET /api/users', [app\controllers\ApiController::class, 'getUsers']);
FAQ
P: Preciso saber sobre Flight para usar este plugin?
R: Sim, isso é para desenvolvedores que querem usar o Flight dentro do WordPress. Conhecimento básico sobre roteamento e manipulação de solicitações do Flight é recomendado.
P: Isso vai deixar meu site WordPress mais lento?
R: Não! O plugin processa apenas as solicitações que correspondem às rotas do Flight. Todas as outras solicitações vão para o WordPress como de costume.
P: Posso usar funções do WordPress no meu app Flight?
R: Absolutamente! Você tem acesso total a todas as funções, hooks e globals do WordPress dentro das suas rotas e controllers do Flight.
P: Como crio rotas personalizadas?
R: Defina suas rotas no arquivo config/routes.php na pasta do seu app. Consulte o arquivo de amostra criado pelo gerador de estrutura de pastas para exemplos.
Registro de Alterações
1.0.0
Lançamento inicial.
Para mais informações, consulte o GitHub repo.
Awesome-plugins/ghost_session
Ghostff/Session
Gerenciador de Sessões PHP (não bloqueante, flash, segmento, criptografia de sessão). Usa PHP open_ssl para criptografia/descriptografia opcional dos dados de sessão. Suporta File, MySQL, Redis e Memcached.
Clique here para visualizar o código.
Instalação
Instale com composer.
composer require ghostff/session
Configuração Básica
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 a lembrar é que você deve commitar sua sessão em cada carregamento de página
// ou você precisará executar auto_commit em sua configuração.
Exemplo Simples
Aqui vai 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 você escrever na sessão, deve commitá-la deliberadamente.
$session->commit();
});
// Esta verificação poderia estar na lógica da página restrita, ou envolvida com middleware.
Flight::route('/some-restricted-page', function() {
$session = Flight::session();
if(!$session->get('is_logged_in')) {
Flight::redirect('/login');
}
// faça sua lógica de página restrita aqui
});
// a versão com middleware
Flight::route('/some-restricted-page', function() {
// lógica de página regular
})->addMiddleware(function() {
$session = Flight::session();
if(!$session->get('is_logged_in')) {
Flight::redirect('/login');
}
});
Exemplo Mais Complexo
Aqui vai 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 sessão como o primeiro argumento
// ou forneça o array personalizado
$app->register('session', Session::class, [
[
// se você quiser armazenar seus dados de sessão em um banco de dados (bom para algo como, "deslogar de todos os dispositivos" funcionalidade)
Session::CONFIG_DRIVER => Ghostff\Session\Drivers\MySql::class,
Session::CONFIG_ENCRYPT_DATA => true,
Session::CONFIG_SALT_KEY => hash('sha256', 'my-super-S3CR3T-salt'), // por favor, mude isso para algo mais
Session::CONFIG_AUTO_COMMIT => true, // faça isso apenas se necessário e/ou se for difícil commitar() sua sessão.
// adicionalmente, você poderia fazer Flight::after('start', function() { Flight::session()->commit(); });
Session::CONFIG_MYSQL_DS => [
'driver' => 'mysql', # Driver do banco de dados para PDO dns ex(mysql:host=...;dbname=...)
'host' => '127.0.0.1', # Host do banco de dados
'db_name' => 'my_app_database', # Nome do banco de dados
'db_table' => 'sessions', # Tabela do banco de dados
'db_user' => 'root', # Usuário do banco de dados
'db_pass' => '', # Senha do banco de dados
'persistent_conn'=> false, # Evite a sobrecarga de estabelecer uma nova conexão toda vez que um script precisa falar com um banco de dados, resultando em uma aplicação web mais rápida. ENCONTRE O LADO NEGATIVO VOCÊ MESMO
]
]
]);
Ajuda! Meus Dados de Sessão Não Estão Persistindo!
Você está definindo seus dados de sessão e eles não estão persistindo entre as solicitações? Talvez você tenha esquecido de commitar seus dados de sessão. Você pode fazer isso chamando $session->commit() após definir seus dados de sessão.
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 você escrever na sessão, deve commitá-la deliberadamente.
$session->commit();
});
A outra forma de contornar isso é quando você configura seu serviço de sessão, você tem que definir auto_commit como true em sua configuração. Isso fará com que seus dados de sessão sejam commitados automaticamente após cada solicitação.
$app->register('session', Session::class, [ 'path/to/session_config.php', bin2hex(random_bytes(32)) ], function(Session $session) {
$session->updateConfiguration([
Session::CONFIG_AUTO_COMMIT => true,
]);
}
);
Adicionalmente, você poderia fazer Flight::after('start', function() { Flight::session()->commit(); }); para commitar seus dados de sessão após cada solicitação.
Documentação
Visite o Github Readme para a documentação completa. As opções de configuração são bem documentadas no arquivo default_config.php em si. O código é simples de entender se você quiser explorar este pacote você mesmo.
Awesome-plugins/mcp
Servidor MCP do FlightPHP
O Servidor MCP do FlightPHP fornece a qualquer assistente de codificação de IA compatível com MCP acesso instantâneo e estruturado a toda a documentação do FlightPHP — roteamento, middleware, plugins, guias e mais. Em vez de sua IA alucinar detalhes de API ou adivinhar assinaturas de métodos, ele busca os documentos reais sob demanda. Sem chaves de API, sem instalação necessária para a versão hospedada.
Visite o repositório no Github para o código-fonte completo e detalhes.
Início Rápido
O servidor está hospedado publicamente e pronto para uso:
https://mcp.flightphp.com/mcp
Basta adicionar essa URL à sua extensão de codificação de IA. Sem cadastro, sem credenciais. Veja a seção Configuração de IDE abaixo para configurações de copiar e colar para as ferramentas mais populares.
O Que Ele Faz
Uma vez conectado, seu assistente de IA pode:
- Navegar por todos os documentos disponíveis — listar todos os tópicos principais, guias e páginas de plugins
- Buscar qualquer página de documentação — recuperar o conteúdo completo para roteamento, middleware, requisições, segurança e mais
- Consultar documentação de plugins — obter a documentação completa para ActiveRecord, Session, Tracy, Runway e todos os outros plugins oficiais
- Seguir guias passo a passo — acessar walkthroughs completos para construir blogs, APIs REST e aplicações testadas
- Pesquisar em tudo — encontrar páginas relevantes em documentos principais, guias e plugins de uma só vez
Pontos Principais
- Configuração zero — o servidor hospedado em
https://mcp.flightphp.com/mcpnão requer instalação ou chaves de API. - Sempre atualizado — o servidor busca documentos ao vivo de docs.flightphp.com, então está sempre em dia.
- Funciona em qualquer lugar — qualquer ferramenta que suporte o transporte HTTP Streamable do MCP pode se conectar.
- Auto-hospedável — execute sua própria instância com PHP >= 8.1 e Composer se preferir.
Configuração de IDE / Extensão de IA
O servidor usa o transporte HTTP Streamable. Escolha sua extensão abaixo e cole a configuração.
Claude Code (CLI)
Execute o seguinte comando para adicioná-lo ao seu projeto:
claude mcp add --transport http flightphp-docs https://mcp.flightphp.com/mcp
Ou adicione manualmente ao .mcp.json do seu projeto:
{
"mcpServers": {
"flightphp-docs": {
"type": "http",
"url": "https://mcp.flightphp.com/mcp"
}
}
}
GitHub Copilot (VS Code)
Adicione ao .vscode/mcp.json no seu workspace:
{
"servers": {
"flightphp-docs": {
"type": "http",
"url": "https://mcp.flightphp.com/mcp"
}
}
}
Kilo Code (VS Code)
Adicione ao settings.json do seu VS Code:
{
"kilocode.mcpServers": {
"flightphp-docs": {
"url": "https://mcp.flightphp.com/mcp",
"transport": "streamable-http"
}
}
}
Continue.dev (VS Code / JetBrains)
Adicione ao ~/.continue/config.json:
{
"mcpServers": [
{
"name": "flightphp-docs",
"transport": {
"type": "http",
"url": "https://mcp.flightphp.com/mcp"
}
}
]
}
Ferramentas Disponíveis
O servidor MCP expõe as seguintes ferramentas para o seu assistente de IA:
| Ferramenta | Descrição |
|---|---|
list_docs_pages |
Lista todos os tópicos de documentação principal disponíveis com slugs e descrições |
get_docs_page |
Busca uma página de documentação principal por slug de tópico (ex: routing, middleware, security) |
list_guide_pages |
Lista todos os guias passo a passo disponíveis |
get_guide_page |
Busca um guia completo por slug (ex: blog, unit-testing) |
list_plugin_pages |
Lista todas as páginas de plugins e extensões disponíveis |
get_plugin_docs |
Busca a documentação completa de um plugin por slug (ex: active-record, session, jwt) |
search_docs |
Pesquisa em todos os documentos, guias e plugins por uma palavra-chave ou tópico |
fetch_url |
Busca qualquer página diretamente por sua URL completa docs.flightphp.com |
Auto-Hospedagem
Prefere executar sua própria instância? Você precisará de PHP >= 8.1 e Composer.
git clone https://github.com/flightphp/mcp.git
cd mcp
composer install
php server.php
O servidor inicia em http://0.0.0.0:8890/mcp por padrão. Atualize a configuração da sua IDE para apontar para o endereço local:
{
"mcpServers": {
"flightphp-docs": {
"type": "http",
"url": "http://localhost:8890/mcp"
}
}
}Awesome-plugins/async
Async
Async é um pequeno pacote para o framework Flight que permite executar seus aplicativos Flight dentro de servidores e runtimes assíncronos como Swoole, AdapterMan, ReactPHP, Amp, RoadRunner, Workerman, etc. De fábrica, ele inclui adaptadores para Swoole e AdapterMan.
O objetivo: desenvolver e depurar com PHP-FPM (ou o servidor integrado) e alternar para Swoole (ou outro driver assíncrono) para produção com mudanças mínimas.
Requisitos
- PHP 7.4 ou superior
- Framework Flight 3.16.1 ou superior
- Extensão Swoole
Instalação
Instale via composer:
composer require flightphp/async
Se você planeja executar com Swoole, instale a extensão:
# usando pecl
pecl install swoole
# ou openswoole
pecl install openswoole
# ou com um gerenciador de pacotes (exemplo Debian/Ubuntu)
sudo apt-get install php-swoole
Exemplo rápido com Swoole
Abaixo está uma configuração mínima que mostra como suportar tanto PHP-FPM (ou servidor integrado) quanto Swoole usando o mesmo código base.
Arquivos que você precisará no seu projeto:
- index.php
- swoole_server.php
- SwooleServerDriver.php
index.php
Este arquivo é um simples interruptor que força o aplicativo a executar no modo PHP para desenvolvimento.
// index.php
<?php
define('NOT_SWOOLE', true);
include 'swoole_server.php';
swoole_server.php
Este arquivo inicializa seu aplicativo Flight e iniciará o driver Swoole quando NOT_SWOOLE não estiver definido.
// swoole_server.php
<?php
require_once __DIR__ . '/vendor/autoload.php';
$app = Flight::app();
$app->route('/', function() use ($app) {
$app->json(['hello' => 'world']);
});
if (!defined('NOT_SWOOLE')) {
// Require a classe SwooleServerDriver quando executando no modo Swoole.
require_once __DIR__ . '/SwooleServerDriver.php';
Swoole\Runtime::enableCoroutine();
$Swoole_Server = new SwooleServerDriver('127.0.0.1', 9501, $app);
$Swoole_Server->start();
} else {
$app->start();
}
SwooleServerDriver.php
Um driver conciso que mostra como conectar requisições Swoole ao Flight usando o AsyncBridge e os adaptadores Swoole.
// SwooleServerDriver.php
<?php
use flight\adapter\SwooleAsyncRequest;
use flight\adapter\SwooleAsyncResponse;
use flight\AsyncBridge;
use flight\Engine;
use Swoole\HTTP\Server as SwooleServer;
use Swoole\HTTP\Request as SwooleRequest;
use Swoole\HTTP\Response as SwooleResponse;
class SwooleServerDriver {
protected $Swoole;
protected $app;
public function __construct(string $host, int $port, Engine $app) {
$this->Swoole = new SwooleServer($host, $port);
$this->app = $app;
$this->setDefault();
$this->bindWorkerEvents();
$this->bindHttpEvent();
}
protected function setDefault() {
$this->Swoole->set([
'daemonize' => false,
'dispatch_mode' => 1,
'max_request' => 8000,
'open_tcp_nodelay' => true,
'reload_async' => true,
'max_wait_time' => 60,
'enable_reuse_port' => true,
'enable_coroutine' => true,
'http_compression' => false,
'enable_static_handler' => true,
'document_root' => __DIR__,
'static_handler_locations' => ['/css', '/js', '/images', '/.well-known'],
'buffer_output_size' => 4 * 1024 * 1024,
'worker_num' => 4,
]);
$app = $this->app;
$app->map('stop', function (?int $code = null) use ($app) {
if ($code !== null) {
$app->response()->status($code);
}
});
}
protected function bindHttpEvent() {
$app = $this->app;
$AsyncBridge = new AsyncBridge($app);
$this->Swoole->on('Start', function(SwooleServer $server) {
echo "Servidor HTTP Swoole iniciado em http://127.0.0.1:9501\n";
});
$this->Swoole->on('Request', function (SwooleRequest $request, SwooleResponse $response) use ($AsyncBridge) {
$SwooleAsyncRequest = new SwooleAsyncRequest($request);
$SwooleAsyncResponse = new SwooleAsyncResponse($response);
$AsyncBridge->processRequest($SwooleAsyncRequest, $SwooleAsyncResponse);
$response->end();
gc_collect_cycles();
});
}
protected function bindWorkerEvents() {
$createPools = function() {
// criar pools de conexão específicos do worker aqui
};
$closePools = function() {
// fechar pools / limpeza aqui
};
$this->Swoole->on('WorkerStart', $createPools);
$this->Swoole->on('WorkerStop', $closePools);
$this->Swoole->on('WorkerError', $closePools);
}
public function start() {
$this->Swoole->start();
}
}
Executando o servidor
- Desenvolvimento (servidor integrado PHP / PHP-FPM):
- php -S localhost:8000 (ou adicione -t public/ se seu index estiver em public/)
- Produção (Swoole):
- php swoole_server.php
Dica: Para produção, use um proxy reverso (Nginx) na frente do Swoole para lidar com TLS, arquivos estáticos e balanceamento de carga.
Notas de configuração
O driver Swoole expõe várias opções de configuração:
- worker_num: número de processos de worker
- max_request: requisições por worker antes do reinício
- enable_coroutine: usar corrotinas para concorrência
- buffer_output_size: tamanho do buffer de saída
Ajuste esses valores para se adequar aos recursos do seu host e padrões de tráfego.
Tratamento de erros
AsyncBridge traduz erros do Flight em respostas HTTP adequadas. Você também pode adicionar tratamento de erros em nível de rota:
$app->route('/*', function() use ($app) {
try {
// lógica da rota
} catch (Exception $e) {
$app->response()->status(500);
$app->json(['error' => $e->getMessage()]);
}
});
AdapterMan e outros runtimes
AdapterMan é suportado como um adaptador de runtime alternativo. O pacote é projetado para ser adaptável — adicionar ou usar outros adaptadores geralmente segue o mesmo padrão: converter a requisição/resposta do servidor em requisição/resposta do Flight via AsyncBridge e os adaptadores específicos do runtime.
Awesome-plugins/migrations
Migrations
Uma migração para o seu projeto é o acompanhamento de todas as alterações de banco de dados envolvidas no seu projeto. byjg/php-migration é uma biblioteca central muito útil para começar.
Instalando
Biblioteca PHP
Se você quiser usar apenas a Biblioteca PHP em seu projeto:
composer require "byjg/migration"
Interface de Linha de Comando
A interface de linha de comando é autônoma e não requer instalação com seu projeto.
Você pode instalar globalmente e criar um link simbólico
composer require "byjg/migration-cli"
Por favor, visite byjg/migration-cli para obter mais informações sobre Migration CLI.
Bancos de dados suportados
| Banco de dados | Driver | String de Conexão |
|---|---|---|
| Sqlite | pdo_sqlite | sqlite:///caminho/para/arquivo |
| MySql/MariaDb | pdo_mysql | mysql://usuario:senha@hostname:porta/banco |
| Postgres | pdo_pgsql | pgsql://usuario:senha@hostname:porta/banco |
| Sql Server | pdo_dblib, pdo_sysbase Linux | dblib://usuario:senha@hostname:porta/banco |
| Sql Server | pdo_sqlsrv Windows | sqlsrv://usuario:senha@hostname:porta/banco |
Como Funciona?
A Migração de Banco de Dados utiliza SQL PURO para gerenciar a versionamento do banco de dados. Para funcionar, você precisa de:
- Criar os Scripts SQL
- Gerenciar usando a Linha de Comando ou a API.
Os Scripts SQL
Os scripts são divididos em três conjuntos de scripts:
- O script BASE contém TODOS os comandos sql para criar um banco de dados novo;
- Os scripts UP contêm todos os comandos de migração sql para "subir" a versão do banco de dados;
- Os scripts DOWN contêm todos os comandos de migração sql para "descer" ou reverter a versão do banco de dados;
O diretório dos scripts é:
<diretório raiz>
|
+-- base.sql
|
+-- /migrations
|
+-- /up
|
+-- 00001.sql
+-- 00002.sql
+-- /down
|
+-- 00000.sql
+-- 00001.sql
- "base.sql" é o script base
- A pasta "up" contém os scripts para migrar para cima na versão. Por exemplo: 00002.sql é o script para mover o banco de dados da versão '1' para '2'.
- A pasta "down" contém os scripts para migrar para baixo na versão. Por exemplo: 00001.sql é o script para mover o banco de dados da versão '2' para '1'. A pasta "down" é opcional.
Ambiente de Desenvolvimento Múltiplo
Se você trabalha com vários desenvolvedores e múltiplas ramificações, pode ser muito difícil determinar qual é o próximo número.
Nesse caso, você tem o sufixo "-dev" após o número da versão.
Veja o cenário:
- O Desenvolvedor 1 cria uma ramificação e a versão mais recente é, por exemplo, 42.
- O Desenvolvedor 2 cria uma ramificação ao mesmo tempo e tem o mesmo número de versão do banco de dados.
Em ambos os casos, os desenvolvedores criarão um arquivo chamado 43-dev.sql. Ambos os desenvolvedores migrarão para CIMA e para BAIXO sem problemas e sua versão local será 43.
Mas o desenvolvedor 1 mesclou suas alterações e criou uma versão final 43.sql (git mv 43-dev.sql 43.sql). Se o desenvolvedor 2
atualizar sua ramificação local, ele terá um arquivo 43.sql (do dev 1) e seu arquivo 43-dev.sql.
Se ele tentar migrar para CIMA ou para BAIXO,
o script de migração irá falhar e alertá-lo que há DUAS versões 43. Nesse caso, o desenvolvedor 2 terá que atualizar seu
arquivo para 44-dev.sql e continuar a trabalhar até mesclar suas alterações e gerar uma versão final.
Usando a API PHP e Integrando-a em seus projetos
O uso básico é
- Criar uma conexão com um objeto ConnectionManagement. Para mais informações, veja o componente "byjg/anydataset".
- Criar um objeto Migration com essa conexão e a pasta onde os scripts sql estão localizados.
- Use o comando apropriado para "resetar", "subir" ou "descer" os scripts de migração.
Veja um exemplo:
<?php
// Criar o URI de Conexão
// Veja mais: https://github.com/byjg/anydataset#connection-based-on-uri
$connectionUri = new \ByJG\Util\Uri('mysql://migrateuser:migratepwd@localhost/migratedatabase');
// Registrar o Banco de Dados ou Bancos de Dados que podem manipular esse URI:
\ByJG\DbMigration\Migration::registerDatabase(\ByJG\DbMigration\Database\MySqlDatabase::class);
// Criar a instância de Migração
$migration = new \ByJG\DbMigration\Migration($connectionUri, '.');
// Adicionar uma função de progresso de callback para receber informações da execução
$migration->addCallbackProgress(function ($action, $currentVersion, $fileInfo) {
echo "$action, $currentVersion, ${fileInfo['description']}\n";
});
// Restaurar o banco de dados usando o script "base.sql"
// e executar TODOS os scripts existentes para subir a versão do banco de dados para a versão mais recente
$migration->reset();
// Executar TODOS os scripts existentes para subir ou descer a versão do banco de dados
// da versão atual até o número $version;
// Se o número da versão não for especificado, migrar até a última versão do banco de dados
$migration->update($version = null);
O objeto Migration controla a versão do banco de dados.
Criando um controle de versão em seu projeto
<?php
// Registrar o Banco de Dados ou Bancos de Dados que podem manipular esse URI:
\ByJG\DbMigration\Migration::registerDatabase(\ByJG\DbMigration\Database\MySqlDatabase::class);
// Criar a instância de Migração
$migration = new \ByJG\DbMigration\Migration($connectionUri, '.');
// Este comando criará a tabela de versões em seu banco de dados
$migration->createVersion();
Obtendo a versão atual
<?php
$migration->getCurrentVersion();
Adicionar Callback para controlar o progresso
<?php
$migration->addCallbackProgress(function ($command, $version, $fileInfo) {
echo "Executando Comando: $command na versão $version - ${fileInfo['description']}, ${fileInfo['exists']}, ${fileInfo['file']}, ${fileInfo['checksum']}\n";
});
Obtendo a instância do Driver de Db
<?php
$migration->getDbDriver();
Para utilizá-lo, visite: https://github.com/byjg/anydataset-db
Evitando Migração Parcial (não disponível para MySQL)
Uma migração parcial é quando o script de migração é interrompido no meio do processo devido a um erro ou uma interrupção manual.
A tabela de migração ficará com o status partial up ou partial down e precisará ser corrigida manualmente antes que possa ser migrada novamente.
Para evitar essa situação, você pode especificar que a migração será executada em um contexto transacional.
Se o script de migração falhar, a transação será revertida e a tabela de migração será marcada como complete e
a versão será a imediatamente anterior antes do script que causou o erro.
Para habilitar esse recurso, você precisa chamar o método withTransactionEnabled passando true como parâmetro:
<?php
$migration->withTransactionEnabled(true);
NOTA: Este recurso não está disponível para MySQL, pois não suporta comandos DDL dentro de uma transação. Se você usar este método com MySQL, a Migração irá ignorá-lo silenciosamente. Mais informações: https://dev.mysql.com/doc/refman/8.0/en/cannot-roll-back.html
Dicas para escrever migrações SQL para Postgres
Ao criar gatilhos e funções SQL
-- FAÇA
CREATE FUNCTION emp_stamp() RETURNS trigger AS $emp_stamp$
BEGIN
-- Verifique se empname e salary foram fornecidos
IF NEW.empname IS NULL THEN
RAISE EXCEPTION 'empname não pode ser nulo'; -- não importa se esses comentários estão em branco ou não
END IF; --
IF NEW.salary IS NULL THEN
RAISE EXCEPTION '% não pode ter salário nulo', NEW.empname; --
END IF; --
-- Quem trabalha para nós quando eles devem pagar por isso?
IF NEW.salary < 0 THEN
RAISE EXCEPTION '% não pode ter um salário negativo', NEW.empname; --
END IF; --
-- Lembre-se de quem mudou a folha de pagamento quando
NEW.last_date := current_timestamp; --
NEW.last_user := current_user; --
RETURN NEW; --
END; --
$emp_stamp$ LANGUAGE plpgsql;
-- NÃO FAÇA
CREATE FUNCTION emp_stamp() RETURNS trigger AS $emp_stamp$
BEGIN
-- Verifique se empname e salary foram fornecidos
IF NEW.empname IS NULL THEN
RAISE EXCEPTION 'empname não pode ser nulo';
END IF;
IF NEW.salary IS NULL THEN
RAISE EXCEPTION '% não pode ter salário nulo', NEW.empname;
END IF;
-- Quem trabalha para nós quando eles devem pagar por isso?
IF NEW.salary < 0 THEN
RAISE EXCEPTION '% não pode ter um salário negativo', NEW.empname;
END IF;
-- Lembre-se de quem mudou a folha de pagamento quando
NEW.last_date := current_timestamp;
NEW.last_user := current_user;
RETURN NEW;
END;
$emp_stamp$ LANGUAGE plpgsql;
Como a camada de abstração de banco de dados PDO não pode executar lotes de instruções SQL,
quando byjg/migration lê um arquivo de migração, ele tem que dividir todo o conteúdo do arquivo SQL nos ponto e vírgula e executar as instruções uma a uma. No entanto, há um tipo de instrução que pode ter múltiplos ponto e vírgula em seu corpo: funções.
Para ser capaz de analisar funções corretamente, byjg/migration 2.1.0 começou a dividir arquivos de migração na sequência ponto e vírgula + EOL em vez de apenas no ponto e vírgula. Dessa forma, se você adicionar um comentário vazio após cada ponto e vírgula interno de uma definição de função, byjg/migration poderá analisá-lo.
Infelizmente, se você esquecer de adicionar algum desses comentários, a biblioteca dividirá a instrução CREATE FUNCTION em várias partes e a migração falhará.
Evitar o caractere de dois-pontos (:)
-- FAÇA
CREATE TABLE bookings (
booking_id UUID PRIMARY KEY,
booked_at TIMESTAMPTZ NOT NULL CHECK (CAST(booked_at AS DATE) <= check_in),
check_in DATE NOT NULL
);
-- NÃO FAÇA
CREATE TABLE bookings (
booking_id UUID PRIMARY KEY,
booked_at TIMESTAMPTZ NOT NULL CHECK (booked_at::DATE <= check_in),
check_in DATE NOT NULL
);
Como o PDO usa o caractere dois-pontos para prefixar parâmetros nomeados em instruções preparadas, seu uso causará problemas em outros contextos.
Por exemplo, as instruções PostgreSQL podem usar :: para converter valores entre tipos. Por outro lado, o PDO lerá isso como um parâmetro nomeado inválido em um contexto inválido e falhará ao tentar executá-lo.
A única maneira de corrigir essa inconsistência é evitar completamente os dois-pontos (neste caso, o PostgreSQL também tem uma sintaxe alternativa: CAST(value AS type)).
Use um editor SQL
Finalmente, escrever migrações SQL manuais pode ser cansativo, mas é significativamente mais fácil se você usar um editor capaz de entender a sintaxe SQL, fornecendo autocompletar, introspectando seu esquema de banco de dados atual e/ou autoformatando seu código.
Lidar com diferentes migrações dentro de um esquema
Se você precisar criar diferentes scripts de migração e versões dentro do mesmo esquema, é possível mas é muito arriscado e eu não recomendo de forma alguma.
Para fazer isso, você precisa criar diferentes "tabelas de migração" passando o parâmetro para o construtor.
<?php
$migration = new \ByJG\DbMigration\Migration("db:/uri", "/path", true, "NEW_MIGRATION_TABLE_NAME");
Por razões de segurança, esse recurso não está disponível na linha de comando, mas você pode usar a variável de ambiente
MIGRATION_VERSION para armazenar o nome.
Recomendamos fortemente não usar esse recurso. A recomendação é uma migração para um esquema.
Executando Testes Unitários
Testes unitários básicos podem ser executados com:
vendor/bin/phpunit
Executando testes de banco de dados
Executar testes de integração requer que você tenha os bancos de dados em funcionamento. Fornecemos um básico docker-compose.yml e você
pode usá-lo para iniciar os bancos de dados para testes.
Executando os bancos de dados
docker-compose up -d postgres mysql mssql
Executar os testes
vendor/bin/phpunit
vendor/bin/phpunit tests/SqliteDatabase*
vendor/bin/phpunit tests/MysqlDatabase*
vendor/bin/phpunit tests/PostgresDatabase*
vendor/bin/phpunit tests/SqlServerDblibDatabase*
vendor/bin/phpunit tests/SqlServerSqlsrvDatabase*
Opcionalmente, você pode definir o host e a senha usados pelos testes unitários
export MYSQL_TEST_HOST=localhost # padrão é localhost
export MYSQL_PASSWORD=newpassword # use '.' se quiser ter uma senha nula
export PSQL_TEST_HOST=localhost # padrão é localhost
export PSQL_PASSWORD=newpassword # use '.' se quiser ter uma senha nula
export MSSQL_TEST_HOST=localhost # padrão é localhost
export MSSQL_PASSWORD=Pa55word
export SQLITE_TEST_HOST=/tmp/test.db # padrão é /tmp/test.dbAwesome-plugins/flightmail
FlightMail
Plugin de terceiros - mantido por Ryan Stubbs (ryanstubbs/flightmail, licença MIT). Não faz parte do núcleo do Flight - por favor reporte problemas no repositório GitHub.
ryanstubbs/flightmail deixa você enviar e-mail da sua app Flight sem as dores de cabeça. Ele envolve o Symfony Mailer - a biblioteca de e-mail mais consolidada do PHP - e faz parecer parte do Flight. Uma linha para instalar, um encadeamento fluente para enviar:
Flight::mail()->compose()
->to('someone@example.com')
->subject('Você conseguiu!')
->text('Seu primeiro e-mail está a caminho.')
->send();
Recursos
- Qualquer provedor, uma linha para cada. SMTP, Postmark, Sendgrid, Mailgun, Amazon SES, Brevo e companhia funcionam todos através de strings DSN simples.
- Use vários provedores ao mesmo tempo. E-mail transacional pelo Postmark, newsletters pelo seu próprio SMTP - escolha por mensagem.
- Templates se você quiser. Renderize os corpos com Twig ou Latte. Não quer templates? Só passe strings e não instale nada extra.
- Polimento na hora do envio. Inlining de CSS opcional e partes de texto puro automáticas derivadas do seu HTML, impulsionadas por bibliotecas que você só instala se for usar.
- Entediante da melhor forma. Conexões preguiçosas, erros claros em vez de e-mail engolido em silêncio, e tudo é substituível se você precisar de algo personalizado.
Requisitos
| O quê | Versão |
|---|---|
| PHP | 8.2 ou mais recente |
| Flight PHP | core ^3.15 |
| Symfony Mailer | ^7.2 ou ^8.0 (instalado automaticamente) |
Instalação
composer require ryanstubbs/flightmail
É só isso para enviar e-mails de texto puro e HTML. A renderização de templates é opcional - adicione um motor só se for usar:
composer require twig/twig # para templates .twig
composer require latte/latte # para templates .latte
Mais duas bibliotecas opcionais impulsionam as melhorias na hora do envio cobertas abaixo:
composer require pelago/emogrifier # para CSS inline ("inline_css")
composer require league/html-to-markdown # para partes de texto Markdown ("text_from_html")
Todas elas podem ser instaladas lado a lado; o FlightMail escolhe a certa com base no que você configurar.
Seu primeiro e-mail
Adicione isto ao seu bootstrap (o mesmo lugar onde você define as rotas):
<?php
require 'vendor/autoload.php';
use ryanstubbs\FlightMail\MailPlugin;
// Diga ao FlightMail de onde e por onde enviar o e-mail.
MailPlugin::install([
'dsns' => [
'default' => 'smtp://user:pass@localhost:1025',
],
'from' => 'no-reply@example.com',
]);
Flight::route('/signup', function () {
Flight::mail()->compose()
->to('new-user@example.com')
->subject('Bem-vindo a bordo!')
->html('<h1>Bem-vindo!</h1><p>Estamos felizes que você esteja aqui.</p>')
->send();
});
Flight::start();
Usando o esqueleto Flight PHP? Registre em app/config/services.php com o estilo de instância em vez disso:
use ryanstubbs\FlightMail\MailPlugin;
MailPlugin::register($app, [
'dsns' => ['default' => 'smtp://user:pass@localhost:1025'],
'from' => 'no-reply@example.com',
]);
Os dois estilos expõem o mesmo mailer: Flight::mail() e $app->mail() são intercambiáveis.
Testando localmente? Se o seu projeto roda no DDEV, aponte o DSN para
smtp://127.0.0.1:1025e leia cada e-mail capturado no Mailpit emhttp://<project>.ddev.site:8025. Nada sai da sua máquina.
Enviando e-mail
Strings simples (nenhum motor de template necessário)
->text() e ->html() recebem strings brutas e não precisam de mais nada instalado:
Flight::mail()->compose()
->to('ops@example.com')
->subject('Backup concluído')
->text('O backup noturno foi concluído em 42 minutos.')
->send();
Flight::mail()->compose()
->to('billing@example.com')
->subject('Fatura #123')
->html('<h1>Fatura #123</h1><p>Total devido: $42.00</p>')
->send();
Templates Twig
// welcome.html.twig contém: Olá {{ name }}, obrigado por se cadastrar!
Flight::mail()->compose()
->to('someone@example.com')
->subject('Bem-vindo!')
->template('welcome.html.twig', ['name' => 'Ryan'])
->send();
Templates Latte
Mesma ideia, extensão .latte:
// welcome.latte contém: Olá {$name}, obrigado por se cadastrar!
Flight::mail()->compose()
->to('someone@example.com')
->subject('Bem-vindo!')
->template('welcome.latte', ['name' => 'Ryan'])
->send();
HTML + texto puro juntos
Boa prática para entregabilidade - dê aos clientes de e-mail as duas versões:
Flight::mail()->compose()
->to('someone@example.com')
->subject('Bem-vindo!')
->template('welcome.html.twig', ['name' => 'Ryan']) // versão rica
->textTemplate('welcome.txt.twig', ['name' => 'Ryan']) // versão de fallback
->send();
Algumas coisas que vale a pena saber sobre templates:
- Eles são renderizados de forma preguiçosa, no momento do envio - compose agora, renderize depois.
- O motor é escolhido pela extensão:
.twig→ Twig,.latte→ Latte, qualquer outra coisa → o padrão configurado (opçãorenderer). - Um corpo explícito com
->html()ou->text()sempre ganha de um template, então você pode definir um template padrão e sobrescrevê-lo por mensagem.
Estilizando HTML e gerando partes de texto
Duas melhorias opcionais na hora do envio, ambas desligadas por padrão e ambas impulsionadas por bibliotecas que você só instala se quiser:
| Recurso | Instalar | Chave de configuração |
|---|---|---|
| CSS inline | pelago/emogrifier |
inline_css |
| Parte de texto a partir do HTML | league/html-to-markdown |
text_from_html |
Inserir CSS inline no seu e-mail HTML
O Gmail e a maioria dos clientes de webmail removem blocos <style> - atributos style="" inline são o único estilo que eles honram de forma confiável. Escrevê-los na mão é miserável; deixe o Emogrifier fazer isso na hora do envio:
composer require pelago/emogrifier
MailPlugin::install([
'dsns' => ['default' => 'smtp://user:pass@localhost:1025'],
'inline_css' => true,
]);
Com isso ligado, cada corpo HTML recebe o CSS inline logo antes do envio - tenha vindo de um template ou de ->html(). Uma mensagem como <style>p { color: red; }</style><p>Olá</p> sai como <p style="color: red;">Olá</p>.
Para injetar estilos compartilhados em todo e-mail (cores da marca, resets) sem repeti-los em cada template, passe as regras diretamente ou aponte para um arquivo de stylesheet:
'inline_css' => ['css_file' => __DIR__ . '/mail-styles/base.css'],
// ou
'inline_css' => ['css' => '.button { background: #0a84ff; color: #fff; }'],
Controle por mensagem:
$message->inlineCss(); // forçar inlining para esta mensagem
$message->withoutInlineCss(); // pular mesmo quando habilitado globalmente
Gerar a parte de texto a partir do seu HTML
A boa prática é enviar uma versão HTML e uma de texto puro juntas, mas escrever as duas é tedioso. O FlightMail pode derivar a parte de texto do HTML final automaticamente - a conversão básica não precisa de nenhuma dependência extra, já que o conversor vem com o Symfony Mime:
MailPlugin::install([
'dsns' => ['default' => 'smtp://user:pass@localhost:1025'],
'text_from_html' => true, // Markdown quando possível, puro caso contrário
]);
Modos:
trueou'auto'- saída Markdown seleague/html-to-markdownestiver instalado, senão uma simples remoção de tags.'markdown'- forçar Markdown (composer require league/html-to-markdown; títulos viram==, links[text](url), negrito**bold**).'plain'- sempre remover tags; funciona com zero pacotes extras.
A geração roda depois da renderização e do CSS inline, e só quando a mensagem tem um corpo HTML mas nenhum corpo de texto - um ->text() ou ->textTemplate() explícito sempre ganha. As sobrescritas por mensagem espelham o inlining:
$message->textFromHtml('plain'); // forçar remoção de tags nesta
$message->withoutTextFromHtml(); // e-mail só HTML
Ative um modo cuja biblioteca não esteja instalada e você recebe um erro claro nomeando o composer require exato a rodar - nunca degradação silenciosa.
Escolhendo um provedor
Provedores se conectam através de strings DSN. Instale o pacote ponte, cole o DSN em dsns, pronto.
| Provedor | Instalar | Exemplo de DSN |
|---|---|---|
| SMTP | embutido | smtp://user:pass@host:587 |
| Sendmail | embutido | sendmail://default |
| Dev/null (descartar e-mail) | embutido | null://null |
| Postmark | composer require symfony/postmark-mailer |
postmark+api://KEY@api.postmarkapp.com |
| Sendgrid | composer require symfony/sendgrid-mailer |
sendgrid+api://KEY@default |
| Mailgun | composer require symfony/mailgun-mailer |
mailgun+https://KEY:DOMAIN@api.mailgun.net |
| Amazon SES | composer require symfony/amazon-mailer |
ses+https://KEY:SECRET@default |
| Brevo | composer require symfony/brevo-mailer |
brevo+api://KEY@default |
| MailerSend | composer require symfony/mailersend-mailer |
mailersend+api://KEY@default |
A lista completa vive na documentação do Symfony Mailer - qualquer coisa documentada lá funciona aqui sem mudanças.
Vários provedores ao mesmo tempo
Nomeie cada transporte, depois escolha por mensagem:
MailPlugin::install([
'dsns' => [
'transactional' => 'postmark+api://KEY@api.postmarkapp.com',
'bulk' => 'smtp://user:pass@bulk.example.com:587',
],
'from' => 'no-reply@example.com',
]);
// Sem chamada a ->transport() = primeira chave em "dsns" ("transactional" aqui).
Flight::mail()->compose()->to('...')->text('recibo')->send();
// Escolha outra rota explicitamente.
Flight::mail()->compose()->to('...')->text('newsletter')->transport('bulk')->send();
Referência de configuração
Tudo é opcional, exceto dsns.
MailPlugin::install([
// OBRIGATÓRIO - nome do transporte => Symfony DSN.
// A primeira entrada é usada quando uma mensagem não nomeia uma.
'dsns' => [
'default' => 'smtp://user:pass@localhost:1025',
],
// Transporte usado quando uma mensagem não tem um ->transport() explícito e
// você não quer a primeira chave. Deve existir em "dsns".
'default_transport' => 'default',
// Remetente global. String, Symfony Address, ou ['email' => 'Name'].
// Aplicado somente quando uma mensagem não define o próprio ->from().
'from' => ['no-reply@example.com' => 'Meu App'],
// Motor de template padrão: 'twig', 'latte', ou um nome personalizado.
// Consultado somente para templates cuja extensão não é um renderer registrado.
'renderer' => 'twig',
// Onde os templates vivem, buscados em ordem; mais um dir de cache opcional.
'templates' => [
'paths' => [__DIR__ . '/mail-templates'],
'cache' => __DIR__ . '/cache/mail',
],
// Opções extras passadas direto para Twig\Environment.
'twig' => ['options' => ['strict_variables' => true]],
// Ajuste o motor Latte no boot: fn(Latte\Engine $engine): void.
'latte' => ['setup' => static fn (Latte\Engine $e) => $e->addExtension(new MyExtension())],
// Melhorias do corpo na hora do envio (veja "Estilizando HTML e gerando partes de texto").
'inline_css' => true, // ou ['css' => '...', 'css_file' => '...']
'text_from_html' => true, // ou 'plain' / 'markdown'
// Esquemas DSN personalizados, renderers personalizados, hooks pré-envio (veja abaixo).
'transport_factories' => [],
'renderers' => [],
'hooks' => [],
// Infraestrutura opcional entregue a cada transporte.
'event_dispatcher' => $dispatcher, // Symfony MessageEvents
'logger' => $psr3Logger,
]);
Indo além
Tudo abaixo é opcional. Os padrões cobrem a maioria das apps.
Adicionar um esquema DSN personalizado
Implemente o TransportFactoryInterface do Symfony e registre-o - então o seu próprio esquema funciona exatamente como um embutido:
use ryanstubbs\FlightMail\MailPlugin;
use Symfony\Component\Mailer\Transport\Dsn;
use Symfony\Component\Mailer\Transport\TransportFactoryInterface;
use Symfony\Component\Mailer\Transport\TransportInterface;
class MyCarrierFactory implements TransportFactoryInterface
{
public function supports(Dsn $dsn): bool
{
return $dsn->getScheme() === 'mycarrier';
}
public function create(Dsn $dsn): TransportInterface
{
// ... construa um transporte que fale com a sua operadora
}
}
$plugin = MailPlugin::install(['dsns' => ['carrier' => 'mycarrier://key']]);
$plugin->addTransportFactory(new MyCarrierFactory());
Adicionar um renderizador de templates personalizado
Qualquer coisa que transforme um nome de template mais params em uma string serve:
use ryanstubbs\FlightMail\MailPlugin;
use ryanstubbs\FlightMail\Render\RendererInterface;
$plugin = MailPlugin::install($config);
$plugin->addRenderer('markdown', fn (array $config): RendererInterface =>
new MarkdownMailRenderer($config['templates']['paths'] ?? [])
);
// Templates terminando em .markdown agora o usam automaticamente:
Flight::mail()->compose()->to('...')->template('welcome.markdown', ['name' => 'Ryan'])->send();
Executar algo logo antes de enviar
Hooks recebem a mensagem pronta - depois da renderização, depois dos padrões, logo antes do fio:
$plugin->addHook(function (ryanstubbs\FlightMail\Message $message): void {
$message->getHeaders()->addTextHeader('X-Mailer', 'MyApp/1.0');
});
Eventos e logging
Passe um event dispatcher do Symfony e/ou um logger PSR-3 e cada transporte vai usá-los:
$plugin->eventDispatcher($dispatcher); // recebe MessageEvent antes de cada envio
$plugin->logger($logger); // logs no nível do transporte
Folha de consulta da API
// Configuração
MailPlugin::install($config) // registrar na app Flight global
MailPlugin::register($app, $config) // registrar em um Engine específico
$mailer = Flight::mail(); // a instância Mailer compartilhada
// Construindo mensagens
$mailer->compose(): Message
$message->to(...)->from(...)->subject(...) // métodos padrão do Symfony Mime
$message->text(string) // corpo em string de texto puro
$message->html(string) // corpo em string HTML
$message->template($name, $params) // corpo HTML a partir de um template
$message->htmlTemplate($name, $params) // alias de template()
$message->textTemplate($name, $params) // corpo de texto a partir de um template
$message->inlineCss() / ->withoutInlineCss() // CSS inline por mensagem
$message->textFromHtml($mode) // parte de texto auto: true/'auto'/'plain'/'markdown'/false
$message->withoutTextFromHtml() // e-mail só HTML
$message->transport($name) // rotear via um DSN nomeado
$message->send(): ?SentMessage // renderizar + enviar
// No próprio mailer
$mailer->send($message): ?SentMessage // alternativa explícita a $message->send()
$mailer->render($template, $params): string // renderizar sem enviar
$mailer->addHook(callable): static // fn(Message $message): void
$mailer->transports(): TransportManager // get() / has() / names()
$mailer->renderers(): RendererFactory // create() / has() / add()
Como Message estende Symfony\Component\Mime\Email, todo método Symfony que você já conhece - attach(), embed(), priority(), replyTo() - funciona de imediato.
Solução de Problemas
"No mail DSNs configured"
Você chamou Flight::mail() antes de registrar o plugin, ou o array de config não incluía dsns. Este erro é deliberado - o FlightMail se recusa a adivinhar para onde seu e-mail deve ir em vez de descartá-lo em silêncio.
"Unknown mail template renderer ..."
Você usou um template cujo motor não está instalado. Corrija com composer require twig/twig ou composer require latte/latte, ou registre um renderer personalizado nomeado conforme a extensão.
"Unknown mail transport ..."
Um ->transport('name') (ou default_transport) não corresponde a nenhuma chave em dsns. Confira a grafia - o erro lista os nomes configurados.
O e-mail não está chegando
Aponte dsns para null://null para confirmar que o resto do seu código funciona, depois volte para o DSN real. No DDEV, use smtp://127.0.0.1:1025 e inspecione as mensagens no Mailpit na porta 8025.
Para relatos de bugs, pull requests e o código-fonte completo, visite o repositório GitHub.
Awesome-plugins/comment_template
CommentTemplate
CommentTemplate é um poderoso motor de templates PHP com compilação de assets, herança de templates e processamento de variáveis. Ele fornece uma maneira simples, mas flexível, de gerenciar templates com minificação integrada de CSS/JS e cache.
Recursos
- Herança de Templates: Use layouts e inclua outros templates
- Compilação de Assets: Minificação automática de CSS/JS e cache
- Processamento de Variáveis: Variáveis de template com filtros e comandos
- Codificação Base64: Assets inline como URIs de dados
- Integração com o Framework Flight: Integração opcional com o framework PHP Flight
Instalação
Instale com o composer.
composer require knifelemon/comment-template
Configuração Básica
Existem algumas opções de configuração básicas para começar. Você pode ler mais sobre elas no Repositório CommentTemplate.
Método 1: Usando Função de Callback
<?php
require_once 'vendor/autoload.php';
use KnifeLemon\CommentTemplate\Engine;
$app = Flight::app();
$app->register('view', Engine::class, [], function (Engine $engine) use ($app) {
// Diretório raiz (onde index.php está) - a raiz de documentos da sua aplicação web
$engine->setPublicPath(__DIR__);
// Diretório de arquivos de template - suporta caminhos relativos e absolutos
$engine->setSkinPath('views'); // Relativo ao caminho público
// Onde os assets compilados serão armazenados - suporta caminhos relativos e absolutos
$engine->setAssetPath('assets'); // Relativo ao caminho público
// Extensão de arquivo de template
$engine->setFileExtension('.php');
});
$app->map('render', function(string $template, array $data) use ($app): void {
echo $app->view()->render($template, $data);
});
Método 2: Usando Parâmetros do Construtor
<?php
require_once 'vendor/autoload.php';
use KnifeLemon\CommentTemplate\Engine;
$app = Flight::app();
// __construct(string $publicPath = "", string $skinPath = "", string $assetPath = "", string $fileExtension = "")
$app->register('view', Engine::class, [
__DIR__, // publicPath - diretório raiz (onde index.php está)
'views', // skinPath - caminho de templates (suporta relativo/absoluto)
'assets', // assetPath - caminho de assets compilados (suporta relativo/absoluto)
'.php' // fileExtension - extensão de arquivo de template
]);
$app->map('render', function(string $template, array $data) use ($app): void {
echo $app->view()->render($template, $data);
});
Configuração de Caminhos
O CommentTemplate fornece tratamento inteligente de caminhos para caminhos relativos e absolutos:
Caminho Público
O Caminho Público é o diretório raiz da sua aplicação web, tipicamente onde index.php reside. Este é o raiz de documentos de onde os servidores web servem arquivos.
// Exemplo: se o seu index.php estiver em /var/www/html/myapp/index.php
$template->setPublicPath('/var/www/html/myapp'); // Diretório raiz
// Exemplo Windows: se o seu index.php estiver em C:\xampp\htdocs\myapp\index.php
$template->setPublicPath('C:\\xampp\\htdocs\\myapp');
Configuração de Caminho de Templates
O caminho de templates suporta caminhos relativos e absolutos:
$template = new Engine();
$template->setPublicPath('/var/www/html/myapp'); // Diretório raiz (onde index.php está)
// Caminhos relativos - combinados automaticamente com o caminho público
$template->setSkinPath('views'); // → /var/www/html/myapp/views/
$template->setSkinPath('templates/pages'); // → /var/www/html/myapp/templates/pages/
// Caminhos absolutos - usados como estão (Unix/Linux)
$template->setSkinPath('/var/www/templates'); // → /var/www/templates/
$template->setSkinPath('/full/path/to/templates'); // → /full/path/to/templates/
// Caminhos absolutos Windows
$template->setSkinPath('C:\\www\\templates'); // → C:\www\templates\
$template->setSkinPath('D:/projects/templates'); // → D:/projects/templates/
// Caminhos UNC (compartilhamentos de rede Windows)
$template->setSkinPath('\\\\server\\share\\templates'); // → \\server\share\templates\
Configuração de Caminho de Assets
O caminho de assets também suporta caminhos relativos e absolutos:
// Caminhos relativos - combinados automaticamente com o caminho público
$template->setAssetPath('assets'); // → /var/www/html/myapp/assets/
$template->setAssetPath('static/files'); // → /var/www/html/myapp/static/files/
// Caminhos absolutos - usados como estão (Unix/Linux)
$template->setAssetPath('/var/www/cdn'); // → /var/www/cdn/
$template->setAssetPath('/full/path/to/assets'); // → /full/path/to/assets/
// Caminhos absolutos Windows
$template->setAssetPath('C:\\www\\static'); // → C:\www\static\
$template->setAssetPath('D:/projects/assets'); // → D:/projects/assets/
// Caminhos UNC (compartilhamentos de rede Windows)
$template->setAssetPath('\\\\server\\share\\assets'); // → \\server\share\assets\
Detecção Inteligente de Caminhos:
- Caminhos Relativos: Sem separadores iniciais (
/,\) ou letras de unidade - Absolutos Unix: Começa com
/(ex.:/var/www/assets) - Absolutos Windows: Começa com letra de unidade (ex.:
C:\www,D:/assets) - Caminhos UNC: Começa com
\\(ex.:\\server\share)
Como funciona:
- Todos os caminhos são resolvidos automaticamente com base no tipo (relativo vs absoluto)
- Caminhos relativos são combinados com o caminho público
@csse@jscriam arquivos minificados em:{resolvedAssetPath}/css/ou{resolvedAssetPath}/js/@assetcopia arquivos únicos para:{resolvedAssetPath}/{relativePath}@assetDircopia diretórios para:{resolvedAssetPath}/{relativePath}- Cache inteligente: arquivos só são copiados quando a fonte é mais nova que o destino
Integração com Tracy Debugger
CommentTemplate inclui integração com Tracy Debugger para registro e depuração em desenvolvimento.

Instalação
composer require tracy/tracy
Uso
<?php
use KnifeLemon\CommentTemplate\Engine;
use Tracy\Debugger;
// Habilitar Tracy (deve ser chamado antes de qualquer saída)
Debugger::enable(Debugger::DEVELOPMENT);
Flight::set('flight.content_length', false);
// Substituição de template
$app->register('view', Engine::class, [], function (Engine $builder) use ($app) {
$builder->setPublicPath($app->get('flight.views.topPath'));
$builder->setAssetPath($app->get('flight.views.assetPath'));
$builder->setSkinPath($app->get('flight.views.path'));
$builder->setFileExtension($app->get('flight.views.extension'));
});
$app->map('render', function(string $template, array $data) use ($app): void {
echo $app->view()->render($template, $data);
});
$app->start();
Recursos do Painel de Depuração
CommentTemplate adiciona um painel personalizado à barra de depuração do Tracy com quatro abas:
- Overview: Configuração, métricas de desempenho e contadores
- Assets: Detalhes de compilação CSS/JS com taxas de compressão
- Variables: Valores originais e transformados com filtros aplicados
- Timeline: Visualização cronológica de todas as operações de template
O Que É Registrado
- Renderização de template (início/fim, duração, layouts, importações)
- Compilação de ativos (arquivos CSS/JS, tamanhos, taxas de compressão)
- Processamento de variáveis (valores originais/transformados, filtros)
- Operações de ativos (codificação base64, cópia de arquivos)
- Métricas de desempenho (duração, uso de memória)
Nota: Nenhum impacto no desempenho quando Tracy não está instalado ou desabilitado.
Consulte o exemplo completo funcional com Flight PHP.
Diretivas de Template
Herança de Layout
Use layouts para criar uma estrutura comum:
layout/global_layout.php:
<!DOCTYPE html>
<html>
<head>
<title>{$title}</title>
</head>
<body>
<!--@contents-->
</body>
</html>
view/page.php:
<!--@layout(layout/global_layout)-->
<h1>{$title}</h1>
<p>{$content}</p>
Gerenciamento de Assets
Arquivos CSS
<!--@css(/css/styles.css)--> <!-- Minificado e em cache -->
<!--@cssSingle(/css/critical.css)--> <!-- Arquivo único, não minificado -->
Arquivos JavaScript
O CommentTemplate suporta diferentes estratégias de carregamento de JavaScript:
<!--@js(/js/script.js)--> <!-- Minificado, carregado no final -->
<!--@jsAsync(/js/analytics.js)--> <!-- Minificado, carregado no final com async -->
<!--@jsDefer(/js/utils.js)--> <!-- Minificado, carregado no final com defer -->
<!--@jsTop(/js/critical.js)--> <!-- Minificado, carregado na head -->
<!--@jsTopAsync(/js/tracking.js)--> <!-- Minificado, carregado na head com async -->
<!--@jsTopDefer(/js/polyfill.js)--> <!-- Minificado, carregado na head com defer -->
<!--@jsSingle(/js/widget.js)--> <!-- Arquivo único, não minificado -->
<!--@jsSingleAsync(/js/ads.js)--> <!-- Arquivo único, não minificado, async -->
<!--@jsSingleDefer(/js/social.js)--> <!-- Arquivo único, não minificado, defer -->
Diretivas de Assets em Arquivos CSS/JS
O CommentTemplate também processa diretivas de assets dentro de arquivos CSS e JavaScript durante a compilação:
Exemplo CSS:
/* Em seus arquivos CSS */
@font-face {
font-family: 'CustomFont';
src: url('<!--@asset(fonts/custom.woff2)-->') format('woff2');
}
.background-image {
background: url('<!--@asset(images/bg.jpg)-->');
}
.inline-icon {
background: url('<!--@base64(icons/star.svg)-->');
}
Exemplo JavaScript:
/* Em seus arquivos JS */
const fontUrl = '<!--@asset(fonts/custom.woff2)-->';
const imageData = '<!--@base64(images/icon.png)-->';
Codificação Base64
<!--@base64(images/logo.png)--> <!-- Inline como URI de dados -->
Exemplo:
<!-- Inline imagens pequenas como URIs de dados para carregamento mais rápido -->
<img src="<!--@base64(images/logo.png)-->" alt="Logo">
<div style="background-image: url('<!--@base64(icons/star.svg)-->');">
Ícone pequeno como fundo
</div>
Cópia de Assets
<!--@asset(images/photo.jpg)--> <!-- Copia asset único para o diretório público -->
<!--@assetDir(assets)--> <!-- Copia diretório inteiro para o diretório público -->
Exemplo:
<!-- Copia e referencia assets estáticos -->
<img src="<!--@asset(images/hero-banner.jpg)-->" alt="Hero Banner">
<a href="<!--@asset(documents/brochure.pdf)-->" download>Baixar Brochura</a>
<!-- Copia diretório inteiro (fontes, ícones, etc.) -->
<!--@assetDir(assets/fonts)-->
<!--@assetDir(assets/icons)-->
Inclusões de Template
<!--@import(components/header)--> <!-- Inclui outros templates -->
Exemplo:
<!-- Inclui componentes reutilizáveis -->
<!--@import(components/header)-->
<main>
<h1>Bem-vindo ao nosso site</h1>
<!--@import(components/sidebar)-->
<div class="content">
<p>Conteúdo principal aqui...</p>
</div>
</main>
<!--@import(components/footer)-->
Processamento de Variáveis
Variáveis Básicas
<h1>{$title}</h1>
<p>{$description}</p>
Filtros de Variáveis
{$title|upper} <!-- Converte para maiúsculas -->
{$content|lower} <!-- Converte para minúsculas -->
{$html|striptag} <!-- Remove tags HTML -->
{$text|escape} <!-- Escapa HTML -->
{$multiline|nl2br} <!-- Converte quebras de linha para <br> -->
{$html|br2nl} <!-- Converte tags <br> para quebras de linha -->
{$description|trim} <!-- Remove espaços em branco -->
{$subject|title} <!-- Converte para título case -->
Comandos de Variáveis
{$title|default=Default Title} <!-- Define valor padrão -->
{$name|concat= (Admin)} <!-- Concatena texto -->
Comandos de Variáveis
{$content|striptag|trim|escape} <!-- Encadeia múltiplos filtros -->
Comentários
Os comentários de template são completamente removidos da saída e não aparecerão no HTML final:
{* Este é um comentário de template de linha única *}
{*
Este é um comentário de
template multi-linha
que se estende por várias linhas
*}
<h1>{$title}</h1>
{* Comentário de debug: verificando se a variável title funciona *}
<p>{$content}</p>
Nota: Os comentários de template {* ... *} são diferentes dos comentários HTML <!-- ... -->. Os comentários de template são removidos durante o processamento e nunca chegam ao navegador.
Estrutura de Projeto Exemplo
project/
├── source/
│ ├── layouts/
│ │ └── default.php
│ ├── components/
│ │ ├── header.php
│ │ └── footer.php
│ ├── css/
│ │ ├── bootstrap.min.css
│ │ └── custom.css
│ ├── js/
│ │ ├── app.js
│ │ └── bootstrap.min.js
│ └── homepage.php
├── public/
│ └── assets/ # Assets gerados
│ ├── css/
│ └── js/
└── vendor/Awesome-plugins/easy_query
EasyQuery
knifelemon/easy-query é um construtor de consultas SQL leve e fluente que gera SQL e parâmetros para declarações preparadas. Funciona com SimplePdo.
Recursos
- 🔗 API Fluente - Encadeie métodos para construção de consultas legível
- 🛡️ Proteção contra Injeção SQL - Ligação automática de parâmetros com declarações preparadas
- 🔧 Suporte a SQL Bruto - Insira expressões SQL brutas com
raw() - 📝 Múltiplos Tipos de Consulta - SELECT, INSERT, UPDATE, DELETE, COUNT
- 🔀 Suporte a JOIN - INNER, LEFT, RIGHT joins com aliases
- 🎯 Condições Avançadas - LIKE, IN, NOT IN, BETWEEN, operadores de comparação
- 🌐 Agnóstico de Banco de Dados - Retorna SQL + params, use com qualquer conexão de BD
- 🪶 Leve - Pegada mínima com zero dependências
Instalação
composer require knifelemon/easy-query
Início Rápido
use KnifeLemon\EasyQuery\Builder;
$q = Builder::table('users')
->select(['id', 'name', 'email'])
->where(['status' => 'active'])
->orderBy('created_at DESC')
->limit(10)
->build();
// Use com SimplePdo do Flight
$users = Flight::db()->fetchAll($q['sql'], $q['params']);
Entendendo build()
O método build() retorna um array com sql e params. Essa separação mantém seu banco de dados seguro usando declarações preparadas.
$q = Builder::table('users')
->where(['email' => 'user@example.com'])
->build();
// Retorna:
// [
// 'sql' => 'SELECT * FROM users WHERE email = ?',
// 'params' => ['user@example.com']
// ]
Tipos de Consulta
SELECT
// Seleciona todas as colunas
$q = Builder::table('users')->build();
// SELECT * FROM users
// Seleciona colunas específicas
$q = Builder::table('users')
->select(['id', 'name', 'email'])
->build();
// SELECT id, name, email FROM users
// Com alias de tabela
$q = Builder::table('users')
->alias('u')
->select(['u.id', 'u.name'])
->build();
// SELECT u.id, u.name FROM users AS u
INSERT
$q = Builder::table('users')
->insert([
'name' => 'John Doe',
'email' => 'john@example.com',
'status' => 'active'
])
->build();
// INSERT INTO users SET name = ?, email = ?, status = ?
Flight::db()->runQuery($q['sql'], $q['params']);
$userId = Flight::db()->lastInsertId();
UPDATE
$q = Builder::table('users')
->update(['status' => 'inactive', 'updated_at' => date('Y-m-d H:i:s')])
->where(['id' => 123])
->build();
// UPDATE users SET status = ?, updated_at = ? WHERE id = ?
Flight::db()->runQuery($q['sql'], $q['params']);
DELETE
$q = Builder::table('users')
->delete()
->where(['id' => 123])
->build();
// DELETE FROM users WHERE id = ?
Flight::db()->runQuery($q['sql'], $q['params']);
COUNT
$q = Builder::table('users')
->count()
->where(['status' => 'active'])
->build();
// SELECT COUNT(*) AS cnt FROM users WHERE status = ?
$count = Flight::db()->fetchField($q['sql'], $q['params']);
Condições WHERE
Igualdade Simples
$q = Builder::table('users')
->where(['id' => 123, 'status' => 'active'])
->build();
// WHERE id = ? AND status = ?
Operadores de Comparação
$q = Builder::table('users')
->where([
'age' => ['>=', 18],
'score' => ['<', 100],
'name' => ['!=', 'admin']
])
->build();
// WHERE age >= ? AND score < ? AND name != ?
LIKE
$q = Builder::table('users')
->where(['name' => ['LIKE', '%john%']])
->build();
// WHERE name LIKE ?
IN / NOT IN
// IN
$q = Builder::table('users')
->where(['id' => ['IN', [1, 2, 3, 4, 5]]])
->build();
// WHERE id IN (?, ?, ?, ?, ?)
// NOT IN
$q = Builder::table('users')
->where(['status' => ['NOT IN', ['banned', 'deleted']]])
->build();
// WHERE status NOT IN (?, ?)
BETWEEN
$q = Builder::table('products')
->where(['price' => ['BETWEEN', [100, 500]]])
->build();
// WHERE price BETWEEN ? AND ?
Condições OR
Use orWhere() para adicionar condições agrupadas OR:
$q = Builder::table('users')
->where(['status' => 'active'])
->orWhere([
'role' => 'admin',
'permissions' => ['LIKE', '%manage%']
])
->build();
// WHERE status = ? AND (role = ? OR permissions LIKE ?)
JOIN
INNER JOIN
$q = Builder::table('users')
->alias('u')
->select(['u.id', 'u.name', 'p.title'])
->innerJoin('posts', 'u.id = p.user_id', 'p')
->build();
// SELECT u.id, u.name, p.title FROM users AS u INNER JOIN posts AS p ON u.id = p.user_id
LEFT JOIN
$q = Builder::table('users')
->alias('u')
->select(['u.name', 'o.total'])
->leftJoin('orders', 'u.id = o.user_id', 'o')
->build();
// ... LEFT JOIN orders AS o ON u.id = o.user_id
Múltiplos JOINs
$q = Builder::table('orders')
->alias('o')
->select(['o.id', 'u.name AS customer', 'p.title AS product'])
->innerJoin('users', 'o.user_id = u.id', 'u')
->leftJoin('order_items', 'o.id = oi.order_id', 'oi')
->leftJoin('products', 'oi.product_id = p.id', 'p')
->where(['o.status' => 'completed'])
->build();
Ordenação, Agrupamento e Limites
ORDER BY
$q = Builder::table('users')
->orderBy('created_at DESC')
->build();
// ORDER BY created_at DESC
GROUP BY
$q = Builder::table('orders')
->select(['user_id', 'COUNT(*) as order_count'])
->groupBy('user_id')
->build();
// SELECT user_id, COUNT(*) as order_count FROM orders GROUP BY user_id
LIMIT e OFFSET
$q = Builder::table('users')
->limit(10)
->build();
// LIMIT 10
$q = Builder::table('users')
->limit(10, 20) // limit, offset
->build();
// LIMIT 10 OFFSET 20
Expressões SQL Brutas
Use raw() quando precisar de funções ou expressões SQL que não devem ser tratadas como parâmetros vinculados.
Raw Básico
$q = Builder::table('users')
->update([
'login_count' => Builder::raw('login_count + 1'),
'updated_at' => Builder::raw('NOW()')
])
->where(['id' => 123])
->build();
// SET login_count = login_count + 1, updated_at = NOW()
Raw com Parâmetros Vinculados
$q = Builder::table('orders')
->update([
'total' => Builder::raw('COALESCE(subtotal, ?) + ?', [0, 10])
])
->where(['id' => 1])
->build();
// SET total = COALESCE(subtotal, ?) + ?
// params: [0, 10, 1]
Raw em WHERE (Subconsulta)
$q = Builder::table('products')
->where([
'price' => ['>', Builder::raw('(SELECT AVG(price) FROM products)')]
])
->build();
// WHERE price > (SELECT AVG(price) FROM products)
Identificadores Seguros para Entrada do Usuário
Quando nomes de colunas vêm de entrada do usuário, use safeIdentifier() para prevenir injeção SQL:
$sortColumn = $_GET['sort']; // e.g., 'created_at'
$safeColumn = Builder::safeIdentifier($sortColumn);
$q = Builder::table('users')
->orderBy($safeColumn . ' DESC')
->build();
// Se o usuário tentar: "name; DROP TABLE users--"
// Lança InvalidArgumentException
rawSafe para Nomes de Colunas Fornecidos pelo Usuário
$userColumn = $_GET['aggregate_column'];
$q = Builder::table('orders')
->select([
Builder::rawSafe('SUM({col})', ['col' => $userColumn])->value . ' AS total'
])
->build();
// Valida o nome da coluna, lança exceção se inválido
Aviso: Nunca concatene entrada do usuário diretamente em
raw(). Sempre use parâmetros vinculados ousafeIdentifier().
Reutilização do Construtor de Consultas
Métodos de Limpeza
Limpe partes específicas para reutilizar o construtor:
$query = Builder::table('users')
->select(['id', 'name'])
->where(['status' => 'active'])
->orderBy('created_at DESC');
// Primeira consulta
$q1 = $query->limit(10)->build();
// Limpe e reutilize
$query->clearWhere()->clearLimit();
// Segunda consulta com condições diferentes
$q2 = $query
->where(['status' => 'pending'])
->limit(5)
->build();
Métodos de Limpeza Disponíveis
| Método | Descrição |
|---|---|
clearWhere() |
Limpa condições WHERE e parâmetros |
clearSelect() |
Redefine colunas SELECT para o padrão '*' |
clearJoin() |
Limpa todas as cláusulas JOIN |
clearGroupBy() |
Limpa cláusula GROUP BY |
clearOrderBy() |
Limpa cláusula ORDER BY |
clearLimit() |
Limpa LIMIT e OFFSET |
clearAll() |
Redefine o construtor para o estado inicial |
Exemplo de Paginação
$baseQuery = Builder::table('users')
->select(['id', 'name', 'email'])
->where(['status' => 'active'])
->orderBy('created_at DESC');
// Obtém contagem total
$countQuery = clone $baseQuery;
$countResult = $countQuery->clearSelect()->count()->build();
$total = Flight::db()->fetchField($countResult['sql'], $countResult['params']);
// Obtém resultados paginados
$page = 1;
$perPage = 20;
$listResult = $baseQuery->limit($perPage, ($page - 1) * $perPage)->build();
$users = Flight::db()->fetchAll($listResult['sql'], $listResult['params']);
Construção Dinâmica de Consultas
$query = Builder::table('products')->alias('p');
if (!empty($categoryId)) {
$query->where(['p.category_id' => $categoryId]);
}
if (!empty($minPrice)) {
$query->where(['p.price' => ['>=', $minPrice]]);
}
if (!empty($maxPrice)) {
$query->where(['p.price' => ['<=', $maxPrice]]);
}
if (!empty($searchTerm)) {
$query->where(['p.name' => ['LIKE', "%{$searchTerm}%"]]);
}
$result = $query->orderBy('p.created_at DESC')->limit(20)->build();
$products = Flight::db()->fetchAll($result['sql'], $result['params']);
Exemplo Completo com FlightPHP
use KnifeLemon\EasyQuery\Builder;
// Lista usuários com paginação
Flight::route('GET /users', function() {
$page = (int) (Flight::request()->query['page'] ?? 1);
$perPage = 20;
$q = Builder::table('users')
->select(['id', 'name', 'email', 'created_at'])
->where(['status' => 'active'])
->orderBy('created_at DESC')
->limit($perPage, ($page - 1) * $perPage)
->build();
$users = Flight::db()->fetchAll($q['sql'], $q['params']);
Flight::json(['users' => $users, 'page' => $page]);
});
// Cria usuário
Flight::route('POST /users', function() {
$data = Flight::request()->data;
$q = Builder::table('users')
->insert([
'name' => $data->name,
'email' => $data->email,
'created_at' => Builder::raw('NOW()')
])
->build();
Flight::db()->runQuery($q['sql'], $q['params']);
Flight::json(['id' => Flight::db()->lastInsertId()]);
});
// Atualiza usuário
Flight::route('PUT /users/@id', function($id) {
$data = Flight::request()->data;
$q = Builder::table('users')
->update([
'name' => $data->name,
'email' => $data->email,
'updated_at' => Builder::raw('NOW()')
])
->where(['id' => $id])
->build();
Flight::db()->runQuery($q['sql'], $q['params']);
Flight::json(['success' => true]);
});
// Deleta usuário
Flight::route('DELETE /users/@id', function($id) {
$q = Builder::table('users')
->delete()
->where(['id' => $id])
->build();
Flight::db()->runQuery($q['sql'], $q['params']);
Flight::json(['success' => true]);
});
Referência da API
Métodos Estáticos
| Método | Descrição |
|---|---|
Builder::table(string $table) |
Cria uma nova instância do construtor para a tabela |
Builder::raw(string $sql, array $bindings = []) |
Cria uma expressão SQL bruta |
Builder::rawSafe(string $expr, array $identifiers, array $bindings = []) |
Expressão bruta com substituição segura de identificadores |
Builder::safeIdentifier(string $identifier) |
Valida e retorna um nome de coluna/tabela seguro |
Métodos de Instância
| Método | Descrição |
|---|---|
alias(string $alias) |
Define alias da tabela |
select(string\|array $columns) |
Define colunas a selecionar (padrão: '*') |
where(array $conditions) |
Adiciona condições WHERE (AND) |
orWhere(array $conditions) |
Adiciona condições OR WHERE |
join(string $table, string $condition, string $alias, string $type) |
Adiciona cláusula JOIN |
innerJoin(string $table, string $condition, string $alias) |
Adiciona INNER JOIN |
leftJoin(string $table, string $condition, string $alias) |
Adiciona LEFT JOIN |
groupBy(string $groupBy) |
Adiciona cláusula GROUP BY |
orderBy(string $orderBy) |
Adiciona cláusula ORDER BY |
limit(int $limit, int $offset = 0) |
Adiciona LIMIT e OFFSET |
count(string $column = '*') |
Define consulta para COUNT |
insert(array $data) |
Define consulta para INSERT |
update(array $data) |
Define consulta para UPDATE |
delete() |
Define consulta para DELETE |
build() |
Constrói e retorna ['sql' => ..., 'params' => ...] |
get() |
Alias para build() |
Integração com Tracy Debugger
EasyQuery integra automaticamente com Tracy Debugger se instalado. Nenhuma configuração necessária!
composer require tracy/tracy
use Tracy\Debugger;
Debugger::enable();
// Todas as consultas são automaticamente registradas no painel Tracy
$q = Builder::table('users')->where(['status' => 'active'])->build();
O painel Tracy mostra:
- Total de consultas e divisão por tipo
- SQL gerado (com destaque de sintaxe)
- Array de parâmetros
- Detalhes da consulta (tabela, where, joins, etc.)
Para documentação completa, visite o repositório GitHub.
Awesome-plugins/twig
Twig
Twig é um mecanismo de template flexível, rápido e seguro para PHP. É a linguagem de modelagem usada pelo Symfony e muitos outros projetos, o que significa que ferramentas de codificação com IA e a maioria dos desenvolvedores PHP já conhecem bem sua sintaxe. O Twig compila templates em PHP otimizado, escapa automaticamente a saída por padrão (ótimo para proteção contra XSS) e é fácil de estender com filtros, funções e extensões.
Instalação
Instale com o composer.
composer require twig/twig
Configuração Básica
Existem algumas opções básicas de configuração para começar. Você pode ler mais sobre elas na Documentação do Twig.
require 'vendor/autoload.php';
$app = Flight::app();
$app->map('render', function(string $template, array $data): void {
$loader = new \Twig\Loader\FilesystemLoader(Flight::get('flight.views.path'));
$twig = new \Twig\Environment($loader, [
// Onde o Twig armazena seus templates compilados
'cache' => __DIR__ . '/../cache/twig',
// Recompila templates quando a fonte muda (útil no desenvolvimento)
'auto_reload' => true,
]);
echo $twig->render($template, $data);
});
Registrando o Twig como a Classe de Visualização
Se preferir reutilizar um único ambiente Twig (recomendado para produção), registre-o e aponte render para ele:
require 'vendor/autoload.php';
$app = Flight::app();
$app->register('view', \Twig\Environment::class, [
new \Twig\Loader\FilesystemLoader($app->get('flight.views.path')),
[
'cache' => __DIR__ . '/../cache/twig',
'auto_reload' => true,
],
]);
$app->map('render', function(string $template, array $data): void {
echo Flight::view()->render($template, $data);
});
Exemplo Simples de Layout
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.twig #}
<!doctype html>
<html lang="en">
<head>
<title>{% if title %}{{ title }} - {% endif %}My App</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<header>
<nav>
{# seus elementos de navegação aqui #}
</nav>
</header>
<div id="content">
{# Esta é a mágica aqui #}
{% block content %}{% endblock %}
</div>
<div id="footer">
© Copyright
</div>
</body>
</html>
E agora temos seu arquivo que será renderizado dentro daquele bloco de conteúdo:
{# app/views/home.twig #}
{# Isso diz ao Twig que este arquivo está "dentro" do arquivo layout.twig #}
{% extends 'layout.twig' %}
{# Este é o conteúdo que será renderizado dentro do layout no bloco de conteúdo #}
{% block content %}
<h1>Página Inicial</h1>
<p>Bem-vindo ao meu aplicativo!</p>
{% endblock %}
Então, quando você for renderizar isso dentro de sua função ou controlador, você faria algo assim:
// rota simples
Flight::route('/', function () {
Flight::render('home.twig', [
'title' => 'Página Inicial'
]);
});
// ou se você estiver usando um controlador
Flight::route('/', [HomeController::class, 'index']);
// HomeController.php
class HomeController
{
public function index()
{
Flight::render('home.twig', [
'title' => 'Página Inicial'
]);
}
}
Consulte a Documentação do Twig para mais informações sobre como usar o Twig em todo o seu potencial!
Depuração
O Twig vem com uma Extensão de Depuração que adiciona uma função dump() que você pode usar dentro dos templates. Ative-a apenas no desenvolvimento:
$app->register('view', \Twig\Environment::class, [
new \Twig\Loader\FilesystemLoader($app->get('flight.views.path')),
[
'cache' => __DIR__ . '/../cache/twig',
'debug' => true, // necessário para a função dump()
'auto_reload' => true,
],
], function (\Twig\Environment $twig): void {
$twig->addExtension(new \Twig\Extension\DebugExtension());
});
Então em um template:
{{ dump(user) }}
Você também pode combinar o Twig com Tracy para depuração em nível de PHP. Para métricas em nível de template (tempo de renderização, memória, quais templates/blocos foram executados), use o painel Twig opcional em flightphp/tracy-extensions: passe um Twig\Profiler\Profile como twig_profile para TracyExtensionLoader. O TwigTracyExtension opcional expõe {{ dump() }} / {{ bdump() }} / {{ dumpe() }} em templates quando o Tracy está ativo.
Nota de Segurança
O Twig escapa automaticamente a saída por padrão, o que ajuda a proteger contra ataques XSS. Prefira {{ variable }} para texto. Use o filtro |raw apenas quando confiar intencionalmente no conteúdo HTML (por exemplo, markdown sanitizado que você já processou no lado do servidor).
Awesome-plugins/session
FlightPHP Session - Manipulador de Sessão Leve Baseado em Arquivos
Isto é um plugin leve e baseado em arquivos para manipular sessões no Flight PHP Framework. Ele oferece uma solução simples e poderosa para gerenciar sessões, com recursos como leituras de sessão não bloqueantes, criptografia opcional, funcionalidade de auto-commit e um modo de teste para desenvolvimento. Os dados da sessão são armazenados em arquivos, tornando-o ideal para aplicações que não requerem um banco de dados.
Se você quiser usar um banco de dados, verifique o plugin ghostff/session que possui muitos desses mesmos recursos, mas com backend de banco de dados.
Visite o repositório no Github para o código-fonte completo e detalhes.
Instalação
Instale o plugin via Composer:
composer require flightphp/session
Uso Básico
Aqui está um exemplo simples de como usar o plugin flightphp/session na sua aplicação Flight:
require 'vendor/autoload.php';
use flight\Session;
$app = Flight::app();
// Registra o serviço de sessão
$app->register('session', Session::class);
// Exemplo de rota com uso de sessão
Flight::route('/login', function() {
$session = Flight::session();
$session->set('user_id', 123);
$session->set('username', 'johndoe');
$session->set('is_admin', false);
echo $session->get('username'); // Saída: johndoe
echo $session->get('preferences', 'default_theme'); // Saída: default_theme
if ($session->get('user_id')) {
Flight::json(['message' => 'Usuário está logado!', 'user_id' => $session->get('user_id')]);
}
});
Flight::route('/logout', function() {
$session = Flight::session();
$session->clear(); // Limpa todos os dados da sessão
Flight::json(['message' => 'Deslogado com sucesso']);
});
Flight::start();
Pontos Chave
- Não Bloqueante: Usa
read_and_closepara iniciar a sessão por padrão, impedindo problemas de bloqueio de sessão. - Auto-Commit: Ativado por padrão, então as alterações são salvas automaticamente no desligamento, a menos que desativado.
- Armazenamento em Arquivos: As sessões são armazenadas no diretório temporário do sistema sob
/flight_sessionspor padrão.
Configuração
Você pode personalizar o manipulador de sessão passando um array de opções ao registrar:
// Sim, é um array duplo :)
$app->register('session', Session::class, [ [
'save_path' => '/custom/path/to/sessions', // Diretório para os arquivos de sessão
'prefix' => 'myapp_', // Prefixo para os arquivos de sessão
'encryption_key' => 'a-secure-32-byte-key-here', // Ativa criptografia (32 bytes recomendados para AES-256-CBC)
'auto_commit' => false, // Desativa auto-commit para controle manual
'start_session' => true, // Inicia a sessão automaticamente (padrão: true)
'test_mode' => false, // Ativa modo de teste para desenvolvimento
'serialization' => 'json', // Método de serialização: 'json' (padrão) ou 'php' (legado)
] ]);
Opções de Configuração
| Option | Description | Default Value |
|---|---|---|
save_path |
Diretório onde os arquivos de sessão são armazenados | sys_get_temp_dir() . '/flight_sessions' |
prefix |
Prefixo para o arquivo de sessão salvo | sess_ |
encryption_key |
Chave para criptografia AES-256-CBC (opcional) | null (sem criptografia) |
auto_commit |
Auto-salvar dados da sessão no desligamento | true |
start_session |
Iniciar a sessão automaticamente | true |
test_mode |
Executar no modo de teste sem afetar sessões PHP | false |
test_session_id |
ID de sessão personalizado para modo de teste (opcional) | Gerado aleatoriamente se não definido |
serialization |
Método de serialização: 'json' (padrão, seguro) ou 'php' (legado, permite objetos) | 'json' |
Modos de Serialização
Por padrão, esta biblioteca usa serialização JSON para os dados da sessão, o que é seguro e previne vulnerabilidades de injeção de objetos PHP. Se você precisar armazenar objetos PHP na sessão (não recomendado para a maioria dos apps), você pode optar pela serialização PHP legada:
'serialization' => 'json'(padrão):- Apenas arrays e primitivos são permitidos nos dados da sessão.
- Mais seguro: imune a injeção de objetos PHP.
- Arquivos são prefixados com
J(JSON simples) ouF(JSON criptografado).
'serialization' => 'php':- Permite armazenar objetos PHP (use com cautela).
- Arquivos são prefixados com
P(serialização PHP simples) ouE(serialização PHP criptografada).
Nota: Se você usar serialização JSON, tentar armazenar um objeto lançará uma exceção.
Uso Avançado
Commit Manual
Se você desativar o auto-commit, você deve commitar as alterações manualmente:
$app->register('session', Session::class, ['auto_commit' => false]);
Flight::route('/update', function() {
$session = Flight::session();
$session->set('key', 'value');
$session->commit(); // Salva explicitamente as alterações
});
Segurança de Sessão com Criptografia
Ative a criptografia para dados sensíveis:
$app->register('session', Session::class, [
'encryption_key' => 'your-32-byte-secret-key-here'
]);
Flight::route('/secure', function() {
$session = Flight::session();
$session->set('credit_card', '4111-1111-1111-1111'); // Criptografado automaticamente
echo $session->get('credit_card'); // Descriptografado na recuperação
});
Regeneração de Sessão
Regenere o ID da sessão por segurança (ex.: após o login):
Flight::route('/post-login', function() {
$session = Flight::session();
$session->regenerate(); // Novo ID, mantém os dados
// OU
$session->regenerate(true); // Novo ID, deleta os dados antigos
});
Exemplo de Middleware
Proteja rotas com autenticação baseada em sessão:
Flight::route('/admin', function() {
Flight::json(['message' => 'Bem-vindo ao painel de administração']);
})->addMiddleware(function() {
$session = Flight::session();
if (!$session->get('is_admin')) {
Flight::halt(403, 'Acesso negado');
}
});
Isto é apenas um exemplo simples de como usar isso em middleware. Para um exemplo mais detalhado, veja a documentação de middleware.
Métodos
A classe Session fornece esses métodos:
set(string $key, $value): Armazena um valor na sessão.get(string $key, $default = null): Recupera um valor, com um padrão opcional se a chave não existir.delete(string $key): Remove uma chave específica da sessão.clear(): Deleta todos os dados da sessão, mas mantém o mesmo nome de arquivo para a sessão.commit(): Salva os dados atuais da sessão no sistema de arquivos.id(): Retorna o ID da sessão atual.regenerate(bool $deleteOldFile = false): Regenera o ID da sessão, incluindo a criação de um novo arquivo de sessão, mantendo todos os dados antigos e o arquivo antigo permanece no sistema. Se$deleteOldFilefortrue, o arquivo de sessão antigo é deletado.destroy(string $id): Destrói uma sessão pelo ID e deleta o arquivo de sessão do sistema. Isso faz parte daSessionHandlerInterfacee$idé obrigatório. Uso típico seria$session->destroy($session->id()).getAll(): Retorna todos os dados da sessão atual.
Todos os métodos, exceto get() e id(), retornam a instância Session para encadeamento.
Por Que Usar Este Plugin?
- Leve: Sem dependências externas – apenas arquivos.
- Não Bloqueante: Evita bloqueio de sessão com
read_and_closepor padrão. - Seguro: Suporta criptografia AES-256-CBC para dados sensíveis.
- Flexível: Opções de auto-commit, modo de teste e controle manual.
- Nativo do Flight: Construído especificamente para o framework Flight.
Detalhes Técnicos
- Formato de Armazenamento: Arquivos de sessão são prefixados com
sess_e armazenados nosave_pathconfigurado. Prefixos de conteúdo do arquivo:J: JSON simples (padrão, sem criptografia)F: JSON criptografado (padrão com criptografia)P: Serialização PHP simples (legado, sem criptografia)E: Serialização PHP criptografada (legado com criptografia)
- Criptografia: Usa AES-256-CBC com IV aleatório por gravação de sessão quando uma
encryption_keyé fornecida. A criptografia funciona para ambos os modos de serialização JSON e PHP. - Serialização: JSON é o método padrão e mais seguro. Serialização PHP está disponível para uso legado/avançado, mas é menos segura.
- Coleta de Lixo: Implementa
SessionHandlerInterface::gc()do PHP para limpar sessões expiradas.
Contribuindo
Contribuições são bem-vindas! Faça fork no repositório, faça suas alterações e envie um pull request. Relate bugs ou sugira recursos via o rastreador de issues do Github.
Licença
Este plugin é licenciado sob a Licença MIT. Veja o repositório no Github para detalhes.
Awesome-plugins/runway
Runway
Runway é uma aplicação CLI que ajuda você a gerenciar suas aplicações Flight. Ele pode gerar controllers, exibir todas as rotas, executar assistentes de configuração de IA, migrations (no skeleton) e mais. É baseado na excelente biblioteca adhocore/php-cli.
Clique aqui para ver o código.
Os comandos de scaffolding estão intencionalmente alinhados com o skeleton oficial para que ferramentas de codificação de IA e humanos obtenham os mesmos caminhos, namespaces e estilo de injeção de construtor toda vez.
Instalação
Instale com composer.
composer require flightphp/runway
O skeleton já depende do Runway; use php runway a partir da raiz do projeto.
Configuração Básica
Na primeira vez que você executar o Runway, ele tentará encontrar uma configuração runway em app/config/config.php através da chave 'runway'.
<?php
// app/config/config.php
return [
'runway' => [
'app_root' => 'app/',
'public_root' => 'public/',
// opcional; o skeleton também usa index_root para a entrada pública
'index_root' => 'public/index.php',
],
];
NOTA - A partir da v1.2.0,
.runway-config.jsonestá obsoleto em favor deapp/config/config.php. Migre comphp runway config:migrateao atualizar projetos antigos. O skeleton ainda pode escrever um pequeno.runway-config.jsonem create-project para compatibilidade; prefira a chaverunwayemconfig.phpa partir de agora.
Detecção da Raiz do Projeto
O Runway é inteligente o suficiente para detectar a raiz do seu projeto, mesmo que você o execute a partir de um subdiretório. Ele procura indicadores como composer.json, .git ou app/config/config.php para determinar onde está a raiz do projeto. Isso significa que você pode executar comandos do Runway de qualquer lugar do seu projeto!
Uso
O Runway tem vários comandos que você pode usar para gerenciar sua aplicação Flight. Existem duas maneiras fáceis de usar o Runway.
- Se você estiver usando o projeto skeleton, pode executar
php runway [comando]a partir da raiz do seu projeto. - Se você estiver usando o Runway como um pacote instalado via composer, pode executar
vendor/bin/runway [comando]a partir da raiz do seu projeto.
Lista de Comandos
Você pode ver uma lista de todos os comandos disponíveis executando o comando php runway.
php runway
Confie apenas nos comandos que realmente aparecem nessa lista para sua instalação (comandos principais do Runway vs comandos específicos do projeto como o migrate do skeleton).
Ajuda do Comando
Para qualquer comando, você pode passar a flag --help para obter mais informações sobre como usar o comando.
php runway routes --help
php runway make:controller --help
Aqui estão alguns exemplos:
Gerar um Controller
make:controller cria um scaffold de controller que corresponde ao layout do skeleton oficial:
| Caminho | app/Controller/{Nome}.php |
| Namespace | App\Controller |
| Estilo | Injeção de construtor de flight\Engine (sem Flight:: no corpo da classe) |
php runway make:controller MeuController
# → app/Controller/MeuController.php
# namespace App\Controller;
Exemplo do formato que você deve esperar (simplificado):
<?php
declare(strict_types=1);
namespace App\Controller;
use flight\Engine;
class MeuController
{
protected Engine $app;
public function __construct(Engine $app)
{
$this->app = $app;
}
public function index(): void
{
// ex. $this->app->render('…', […]);
}
}
Registre-o com um callable de classe para que o Dice possa construir o controller:
// app/config/routes.php
use App\Controller\MeuController;
$router->get('/meu', [MeuController::class, 'index']);
Por que esse layout? A capitalização da pasta deve corresponder ao namespace (Controller não controllers) para o Composer PSR-4 no Linux—veja Autoloading. O mesmo caminho é o que os arquivos AGENTS.md raiz e com escopo dizem para as ferramentas de IA usarem, então controllers gerados e escritos manualmente permanecem idênticos.
Documentações antigas e projetos comunitários às vezes usavam
app/controllers/eapp\controllers. Isso permanece válido se sua árvore ainda usar pastas em minúsculas. Novos projetos skeleton e a saída atual domake:controllerusamapp/Controller/+App\Controller.
Gerar um Modelo Active Record
Primeiro certifique-se de que você instalou o plugin Active Record.
php runway make:record usuarios
No skeleton oficial, os modelos vivem em app/Model/ com namespace App\Model, e a conexão com o banco de dados é SimplePdo (injete-o ou passe-o para o construtor do ActiveRecord). Nomes de arquivos/namespaces gerados seguem os padrões atuais do Runway e sua configuração runway—prefira alinhar novos modelos com App\Model para que correspondam ao autoloading e AGENTS.md.
Exemplo de um modelo consistente com a demonstração de posts do skeleton:
<?php
declare(strict_types=1);
namespace App\Model;
use flight\ActiveRecord;
/**
* @property int $id
* @property string $titulo
* // …
*/
class Post extends ActiveRecord
{
protected array $relations = [];
public function __construct($databaseConnection)
{
parent::__construct($databaseConnection, 'posts');
}
}
Se um gerador antigo ainda emitir app/records / app\records, você pode manter essa convenção em aplicações legadas ou mover arquivos para app/Model/ e atualizar o namespace para corresponder à capitalização da pasta.
Migrations (skeleton)
O skeleton oficial inclui um comando do projeto (descoberto de app/commands/) como:
php runway migrate
As migrations são arquivos SQL em migrations/ (por exemplo YYYYMMDDHHMMSS_descricao.sql para SQLite e …_descricao.mysql.sql para MySQL), selecionados a partir da sua configuração/env do driver de banco de dados. Flags e comportamentos exatos são definidos por esse comando do projeto—execute php runway migrate --help na sua aplicação.
Auxiliares de IA
O Runway expõe comandos orientados para IA usados com IA e experiência do desenvolvedor:
php runway ai:init
php runway ai:generate-instructions
Estes armazenam credenciais LLM e geram instruções do projeto (principalmente AGENTS.md). No skeleton, trate AGENTS.md (e cópias com escopo em app/) mais SECURITY.md como a fonte da verdade para agentes.
Exibir Todas as Rotas
Isso exibirá todas as rotas que estão atualmente registradas com o Flight.
php runway routes
Se você quiser visualizar apenas rotas específicas, pode passar uma flag para filtrar as rotas.
# Exibir apenas rotas GET
php runway routes --get
# Exibir apenas rotas POST
php runway routes --post
# etc.
Adicionando Comandos Personalizados ao Runway
Se você estiver criando um pacote para o Flight, ou quiser adicionar seus próprios comandos personalizados ao seu projeto, pode fazer isso criando um diretório src/commands/, flight/commands/, app/commands/ ou commands/ para seu projeto/pacote. Se você precisar de mais personalização, veja a seção abaixo sobre Configuração.
No skeleton, os comandos do projeto vivem em app/commands/ com namespace App\Command. O Runway os descobre por caminho; mantenha essa pasta em sincronia com o classmap/PSR-4 do Composer como seu projeto já faz.
Para criar um comando, você simplesmente estende a classe AbstractBaseCommand e implementa pelo menos um método __construct e um método execute.
<?php
declare(strict_types=1);
namespace App\Command;
use flight\commands\AbstractBaseCommand;
class ComandoExemplo extends AbstractBaseCommand
{
/**
* Construtor
*
* @param array<string,mixed> $config Config de app/config/config.php
*/
public function __construct(array $config)
{
parent::__construct('make:exemplo', 'Cria um exemplo para a documentação', $config);
$this->argument('<gif-engraçado>', 'O nome do gif engraçado');
}
/**
* Executa a função
*
* @return void
*/
public function execute()
{
$io = $this->app()->io();
$io->info('Criando exemplo...');
// Faça algo aqui
$io->ok('Exemplo criado!');
}
}
Veja a Documentação adhocore/php-cli para mais informações sobre como construir seus próprios comandos personalizados em sua aplicação Flight!
Gerenciamento de Configuração
Como a configuração foi movida para app/config/config.php a partir da v1.2.0, existem alguns comandos auxiliares para gerenciar a configuração.
Dica do skeleton: Mantenha
config.phpcomo valores PHP literais. Segredos pertencem ao.env. Evite expressões$_ENV[...]dentro deconfig.php—config:setreescreve esse arquivo como dados estáticos e pode incorporar segredos no arquivo. Veja Configuração.
Migrar Configuração Antiga
Se você tiver um arquivo .runway-config.json antigo, pode facilmente migrá-lo para app/config/config.php com o seguinte comando:
php runway config:migrate
Definir Valor de Configuração
Você pode definir um valor de configuração usando o comando config:set. Isso é útil se você quiser atualizar um valor de configuração sem abrir o arquivo.
php runway config:set app_root "app/"
Obter Valor de Configuração
Você pode obter um valor de configuração usando o comando config:get.
php runway config:get app_root
Todas as Configurações do Runway
Se você precisar personalizar a configuração para o Runway, pode definir esses valores em app/config/config.php. Abaixo estão algumas configurações adicionais que você pode definir:
<?php
// app/config/config.php
return [
// ... outros valores de configuração ...
'runway' => [
// Este é o local do diretório da sua aplicação
'app_root' => 'app/',
// Este é o diretório onde seu arquivo index raiz está localizado
'index_root' => 'public/',
// Estes são os caminhos para as raízes de outros projetos
'root_paths' => [
'/home/usuario/projeto-diferente',
'/var/www/outro-projeto'
],
// Caminhos base provavelmente não precisam ser configurados, mas estão aqui se você quiser
'base_paths' => [
'/includes/libs/vendor', // se você tiver um caminho muito único para seu diretório vendor ou algo assim
],
// Caminhos finais são locais dentro de um projeto para procurar os arquivos de comando
'final_paths' => [
'src/caminho-diferente/commands',
'app/module/admin/commands',
],
// Se você quiser apenas adicionar o caminho completo, vá em frente (absoluto ou relativo à raiz do projeto)
'paths' => [
'/home/usuario/projeto-diferente/src/caminho-diferente/commands',
'/var/www/outro-projeto/app/module/admin/commands',
'app/meus-comandos-unicos'
]
]
];
Acessando a Configuração
Se você precisar acessar os valores de configuração efetivamente, pode acessá-los através do método __construct ou do método app(). Também é importante notar que se você tiver um arquivo app/config/services.php, esses serviços também estarão disponíveis para seu comando.
public function execute()
{
$io = $this->app()->io();
// Acessar configuração
$app_root = $this->config['runway']['app_root'];
// Acessar serviços como talvez uma conexão de banco de dados
$database = $this->config['database']
// ...
}
Wrappers Auxiliares de IA
O Runway tem alguns wrappers auxiliares que facilitam para a IA gerar comandos. Você pode usar addOption e addArgument de uma forma que parece similar ao Symfony Console. Isso é útil se você estiver usando ferramentas de IA para gerar seus comandos.
public function __construct(array $config)
{
parent::__construct('make:exemplo', 'Cria um exemplo para a documentação', $config);
// O argumento mode é anulável e padrão para completamente opcional
$this->addOption('nome', 'O nome do exemplo', null);
}
Veja Também
- Instalação - Árvore do skeleton e padrões create-project
- Autoloading -
App\e capitalização de pastas - Injeção de Dependência - Injeção Dice + Engine para controllers gerados
- IA e Experiência do Desenvolvedor -
ai:init,ai:generate-instructions,AGENTS.md - Active Record - Modelos usados com
make:record/ skeletonApp\Model - SimplePdo - Conexão DB usada por migrations e modelos do skeleton
Awesome-plugins/tracy_extensions
Extensões do Painel Tracy para Flight
Este é um conjunto de extensões para tornar o trabalho com o Flight um pouco mais rico.
- Flight - Analise todas as variáveis do Flight.
- Database - Analise todas as consultas que foram executadas na página (se você iniciar corretamente a conexão com o banco de dados)
- Request - Analise todas as variáveis
$_SERVERe examine todos os payloads globais ($_GET,$_POST,$_FILES) - Session - Analise todas as variáveis
$_SESSIONse as sessões estiverem ativas. - Twig (opcional) - Analise o tempo de renderização do template Twig, memória e quais templates/blocks/macros foram executados (requer
twig/twige uma configuraçãotwig_profile)
Isso é especialmente útil com o esqueleto oficial, que usa Twig por padrão: o mesmo layout ferramentas de IA também aparece claramente na barra do Tracy.
Este é o Painel

E cada painel exibe informações muito úteis sobre sua aplicação!

Clique aqui para ver o código.
Instalação
Execute composer require flightphp/tracy-extensions --dev e pronto!
O Twig não é uma dependência obrigatória do pacote. Instale twig/twig apenas se quiser o painel Twig (o esqueleto já faz isso para as views).
Configuração
Há muito pouca configuração necessária para começar. Você precisará iniciar o debugger 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 sua aplicação, há um
// wrapper PDO obrigatório para usar APENAS EM DESENVOLVIMENTO (não em produção, por favor!)
// Ele tem os mesmos parâmetros de uma conexão PDO regular
$pdo = new PdoQueryCapture('sqlite:test.db', 'user', 'pass');
// ou se você anexar isso ao framework Flight
Flight::register('db', PdoQueryCapture::class, ['sqlite:test.db', 'user', 'pass']);
// agora sempre que você fizer uma consulta, ele capturará o tempo, a consulta e os parâmetros
// Isso conecta os pontos
if(Debugger::$showBar === true) {
// Isso precisa ser false ou o Tracy não consegue renderizar de verdade :(
Flight::set('flight.content_length', false);
new TracyExtensionLoader(Flight::app());
}
// mais código
Flight::start();
Configuração Adicional
Dados de Sessão
Se você tiver um manipulador de sessão personalizado (como ghostff/session), pode passar qualquer array de dados de sessão para o Tracy e ele automaticamente os exibirá para você. Você passa isso com a chave session_data no segundo parâmetro do construtor TracyExtensionLoader.
use Ghostff\Session\Session;
// ou use flight\Session;
require 'vendor/autoload.php';
$app = Flight::app();
$app->register('session', Session::class);
if(Debugger::$showBar === true) {
// Isso precisa ser false ou o Tracy não consegue renderizar de verdade :(
Flight::set('flight.content_length', false);
new TracyExtensionLoader(Flight::app(), [ 'session_data' => Flight::session()->getAll() ]);
}
// rotas e outras coisas...
Flight::start();
Painel Twig (opcional)
Se sua aplicação usa Twig (incluindo o esqueleto oficial), você pode mostrar métricas de template na barra do Tracy. Crie um Profile do Twig, anexe o ProfilerExtension ao seu ambiente, então passe esse perfil para o loader sob a chave twig_profile. Anexe a profilagem apenas em desenvolvimento.
<?php
use flight\debug\tracy\TracyExtensionLoader;
use flight\debug\tracy\TwigTracyExtension;
use Tracy\Debugger;
use Twig\Environment;
use Twig\Extension\ProfilerExtension;
use Twig\Loader\FilesystemLoader;
use Twig\Profiler\Profile;
$loader = new FilesystemLoader(__DIR__ . '/views');
$twig = new Environment($loader, [
'debug' => true,
'cache' => false,
]);
// Opcional: exponha os helpers dump do Tracy nos templates
// {{ dump(var) }}, {{ bdump(var) }}, {{ dumpe(var) }}
$twig->addExtension(new TwigTracyExtension());
$tracyConfig = [];
if (Debugger::$showBar === true) {
$profile = new Profile();
$twig->addExtension(new ProfilerExtension($profile));
$tracyConfig['twig_profile'] = $profile;
}
if (Debugger::$showBar === true) {
Flight::set('flight.content_length', false);
new TracyExtensionLoader(Flight::app(), $tracyConfig);
}
// Mapeie Flight::render() para Twig (exemplo)
Flight::map('render', function (string $template, array $data = []) use ($twig) {
if (substr($template, -5) !== '.twig') {
$template .= '.twig';
}
echo $twig->render($template, $data);
});
O que o painel mostra
- Tempo total de renderização e memória do Twig
- Contagens de chamadas de template / block / macro
- Cada template que foi renderizado, com seu próprio tempo e memória
A aba Twig fica oculta quando nenhum template foi renderizado para a requisição, ou quando você omite twig_profile (ou não tem o Twig instalado) - outros painéis do Flight continuam funcionando.
Em um services.php no estilo do esqueleto, construa o mesmo $profile / ProfilerExtension quando o debug estiver ativo, passe twig_profile para o TracyExtensionLoader, e continue usando seu ambiente Twig compartilhado para $app->render().
Latte
PHP 8.1+ é necessário para esta seção.
Se você tem o Latte instalado em seu projeto, o Tracy tem uma integração nativa com o Latte para analisar seus templates. Você simplesmente registra a extensão com sua instância do Latte (esta é a própria ponte Tracy do Latte, não o painel Twig acima).
require 'vendor/autoload.php';
$app = Flight::app();
$app->map('render', function($template, $data, $block = null) {
$latte = new Latte\Engine;
// outras configurações...
// adicione a extensão apenas se a Barra de Debug do Tracy estiver habilitada
if(Debugger::$showBar === true) {
// aqui é onde você adiciona o Painel Latte ao Tracy
$latte->addExtension(new Latte\Bridges\Tracy\TracyExtension);
}
$latte->render($template, $data, $block);
});
Veja Também
- Tracy - Configuração base do Tracy para o Flight
- Twig - Templating usado pelo esqueleto e pelo painel Twig
- Templates - Como o Flight mapeia
renderpara Twig/Latte - Installation - O esqueleto inclui tracy-extensions em dev
Awesome-plugins/apm
Documentação do FlightPHP APM
Bem-vindo ao FlightPHP APM—seu coach pessoal de desempenho para aplicativos! Este guia é seu roteiro para configurar, usar e dominar o Monitoramento de Desempenho de Aplicativos (APM) com FlightPHP. Seja você caçando solicitações lentas ou apenas querendo se empolgar com gráficos de latência, nós temos tudo coberto. Vamos tornar seu aplicativo mais rápido, seus usuários mais felizes e suas sessões de depuração mais fáceis!
Veja uma demonstração do painel para o site Flight Docs.

Por que o APM é Importante
Imagine isto: seu aplicativo é um restaurante movimentado. Sem uma forma de rastrear quanto tempo os pedidos levam ou onde a cozinha está emperrando, você está adivinhando por que os clientes estão saindo insatisfeitos. O APM é seu sous-chef—ele observa cada etapa, desde as solicitações recebidas até as consultas de banco de dados, e sinaliza qualquer coisa que esteja te atrasando. Páginas lentas perdem usuários (estudos dizem que 53% saem se um site demora mais de 3 segundos para carregar!), e o APM ajuda você a capturar esses problemas antes que eles doam. É paz de espírito proativa—menos momentos de "por que isso está quebrado?" e mais vitórias de "veja como isso funciona perfeitamente!"
Instalação
Comece com o Composer:
composer require flightphp/apm
Você precisará de:
- PHP 7.4+: Nos mantém compatíveis com distribuições Linux LTS enquanto suportamos PHP moderno.
- FlightPHP Core v3.15+: O framework leve que estamos potencializando.
Bancos de Dados Suportados
O FlightPHP APM atualmente suporta os seguintes bancos de dados para armazenar métricas:
- SQLite3: Simples, baseado em arquivo e ótimo para desenvolvimento local ou aplicativos pequenos. Opção padrão na maioria das configurações.
- MySQL/MariaDB: Ideal para projetos maiores ou ambientes de produção onde você precisa de armazenamento robusto e escalável.
Você pode escolher seu tipo de banco de dados durante a etapa de configuração (veja abaixo). Certifique-se de que seu ambiente PHP tenha as extensões necessárias instaladas (ex: pdo_sqlite ou pdo_mysql).
Primeiros Passos
Aqui está seu passo a passo para a excelência do APM:
1. Registre o APM
Coloque isto no seu index.php ou em um arquivo services.php para começar a rastrear:
use flight\apm\logger\LoggerFactory;
use flight\database\SimplePdo;
use flight\Apm;
$ApmLogger = LoggerFactory::create(__DIR__ . '/../../.runway-config.json');
$Apm = new Apm($ApmLogger);
$Apm->bindEventsToFlightInstance($app);
// If you're adding a database connection
// Prefer SimplePdo (or PdoQueryCapture from Tracy Extensions in dev).
// Enable APM query tracking via the options array (5th argument).
$pdo = new SimplePdo('mysql:host=localhost;dbname=example', 'user', 'pass', null, [
'trackApmQueries' => true, // required to capture queries for the APM
]);
$Apm->addPdoConnection($pdo);
O que está acontecendo aqui?
LoggerFactory::create()pega sua configuração (mais sobre isso em breve) e configura um logger—SQLite por padrão.Apmé a estrela—ele escuta os eventos do Flight (solicitações, rotas, erros, etc.) e coleta métricas.bindEventsToFlightInstance($app)conecta tudo ao seu aplicativo Flight.
Dica Profissional: Amostragem Se seu aplicativo está ocupado, registrar todas as solicitações pode sobrecarregar as coisas. Use uma taxa de amostragem (0.0 a 1.0):
$Apm = new Apm($ApmLogger, 0.1); // Logs 10% of requests
Isso mantém o desempenho ágil enquanto ainda fornece dados sólidos.
2. Configure-o
Execute isto para criar seu .runway-config.json:
php vendor/bin/runway apm:init
O que isso faz?
- Inicia um assistente perguntando de onde vêm as métricas brutas (fonte) e para onde vão os dados processados (destino).
- O padrão é SQLite—ex:
sqlite:/tmp/apm_metrics.sqlitepara fonte, outro para destino. - Você terminará com uma configuração como:
{ "apm": { "source_type": "sqlite", "source_db_dsn": "sqlite:/tmp/apm_metrics.sqlite", "storage_type": "sqlite", "dest_db_dsn": "sqlite:/tmp/apm_metrics_processed.sqlite" } }
Este processo também perguntará se você quer executar as migrações para esta configuração. Se você está configurando isso pela primeira vez, a resposta é sim.
Por que dois locais? Métricas brutas se acumulam rapidamente (pense em logs não filtrados). O worker as processa em um destino estruturado para o painel. Mantém as coisas organizadas!
3. Processe Métricas com o Worker
O worker transforma métricas brutas em dados prontos para o painel. Execute-o uma vez:
php vendor/bin/runway apm:worker
O que ele está fazendo?
- Lê da sua fonte (ex:
apm_metrics.sqlite). - Processa até 100 métricas (tamanho de lote padrão) para seu destino.
- Para quando terminar ou se não houver mais métricas.
Mantenha-o Executando Para aplicativos ao vivo, você vai querer processamento contínuo. Aqui estão suas opções:
-
Modo Daemon:
php vendor/bin/runway apm:worker --daemonExecuta para sempre, processando métricas conforme chegam. Ótimo para desenvolvimento ou configurações pequenas.
-
Crontab: Adicione isto ao seu crontab (
crontab -e):* * * * * php /path/to/project/vendor/bin/runway apm:workerDispara a cada minuto—perfeito para produção.
-
Tmux/Screen: Inicie uma sessão destacável:
tmux new -s apm-worker php vendor/bin/runway apm:worker --daemon # Ctrl+B, then D to detach; `tmux attach -t apm-worker` to reconnectMantém-o executando mesmo se você sair.
-
Ajustes Personalizados:
php vendor/bin/runway apm:worker --batch_size 50 --max_messages 1000 --timeout 300--batch_size 50: Processa 50 métricas por vez.--max_messages 1000: Para após 1000 métricas.--timeout 300: Sai após 5 minutos.
Por que se preocupar? Sem o worker, seu painel fica vazio. É a ponte entre logs brutos e insights acionáveis.
4. Inicie o Painel
Veja os sinais vitais do seu aplicativo:
php vendor/bin/runway apm:dashboard
O que é isso?
- Inicia um servidor PHP em
http://localhost:8001/apm/dashboard. - Mostra logs de solicitações, rotas lentas, taxas de erro e mais.
Personalize-o:
php vendor/bin/runway apm:dashboard --host 0.0.0.0 --port 8080 --php-path=/usr/local/bin/php
--host 0.0.0.0: Acessível de qualquer IP (útil para visualização remota).--port 8080: Use uma porta diferente se 8001 estiver ocupada.--php-path: Aponta para o PHP se ele não estiver no seu PATH.
Acesse a URL no seu navegador e explore!
Modo de Produção
Para produção, você pode precisar tentar algumas técnicas para fazer o painel funcionar já que provavelmente há firewalls e outras medidas de segurança em vigor. Aqui estão algumas opções:
- Use um Proxy Reverso: Configure Nginx ou Apache para encaminhar solicitações para o painel.
- Túnel SSH: Se você puder fazer SSH no servidor, use
ssh -L 8080:localhost:8001 youruser@yourserverpara tunelar o painel para sua máquina local. - VPN: Se seu servidor estiver atrás de uma VPN, conecte-se a ela e acesse o painel diretamente.
- Configure o Firewall: Abra a porta 8001 para seu IP ou rede do servidor. (ou qualquer porta que você configurou).
- Configure Apache/Nginx: Se você tem um servidor web na frente de sua aplicação, pode configurá-lo para um domínio ou subdomínio. Se fizer isso, defina o document root para
/path/to/your/project/vendor/flightphp/apm/dashboard
Quer um painel diferente?
Você pode construir seu próprio painel se quiser! Olhe o diretório vendor/flightphp/apm/src/apm/presenter para ideias de como apresentar os dados para seu próprio painel!
Recursos do Painel
O painel é sua HQ do APM—aqui está o que você verá:
- Log de Solicitações: Cada solicitação com timestamp, URL, código de resposta e tempo total. Clique em "Detalhes" para middleware, consultas e erros.
- Solicitações Mais Lentas: Top 5 solicitações que consomem tempo (ex: "/api/heavy" em 2.5s).
- Rotas Mais Lentas: Top 5 rotas por tempo médio—ótimo para identificar padrões.
- Taxa de Erro: Porcentagem de solicitações que falham (ex: 2.3% de 500s).
- Percentis de Latência: Tempos de resposta do 95º (p95) e 99º (p99)—conheça seus piores cenários.
- Gráfico de Códigos de Resposta: Visualize 200s, 404s, 500s ao longo do tempo.
- Consultas/Middleware Longos: Top 5 chamadas lentas de banco de dados e camadas de middleware.
- Cache Hit/Miss: Com que frequência seu cache salva o dia.
Extras:
- Filtre por "Última Hora", "Último Dia" ou "Última Semana".
- Alterne o modo escuro para aquelas sessões noturnas.
Exemplo:
Uma solicitação para /users pode mostrar:
- Tempo Total: 150ms
- Middleware:
AuthMiddleware->handle(50ms) - Consulta:
SELECT * FROM users(80ms) - Cache: Hit em
user_list(5ms)
Adicionando Eventos Personalizados
Rastreie qualquer coisa—como uma chamada de API ou processo de pagamento:
use flight\apm\CustomEvent;
$app->eventDispatcher()->trigger('apm.custom', new CustomEvent('api_call', [
'endpoint' => 'https://api.example.com/users',
'response_time' => 0.25,
'status' => 200
]));
Onde aparece? Nos detalhes da solicitação do painel em "Eventos Personalizados"—expansível com formatação JSON bonita.
Caso de Uso:
$start = microtime(true);
$apiResponse = file_get_contents('https://api.example.com/data');
$app->eventDispatcher()->trigger('apm.custom', new CustomEvent('external_api', [
'url' => 'https://api.example.com/data',
'time' => microtime(true) - $start,
'success' => $apiResponse !== false
]));
Agora você verá se essa API está arrastando seu aplicativo!
Monitoramento de Banco de Dados
Rastreie consultas PDO assim:
use flight\database\SimplePdo;
$pdo = new SimplePdo('sqlite:/path/to/db.sqlite', null, null, null, [
'trackApmQueries' => true, // required to capture queries for the APM
]);
$Apm->addPdoConnection($pdo);
O que Você Obtém:
- Texto da consulta (ex:
SELECT * FROM users WHERE id = ?) - Tempo de execução (ex: 0.015s)
- Contagem de linhas (ex: 42)
Atenção:
- Opcional: Pule isso se você não precisar de rastreamento de BD.
- SimplePdo (preferido): Use
SimplePdocomtrackApmQueries => true. OPdoWrapperobsoleto ainda funciona (5º argumento do construtortrue). PDO core bruto ainda não está conectado—fique ligado! - Aviso de Desempenho: Registrar cada consulta em um site com muito BD pode deixar as coisas lentas. Use amostragem (
$Apm = new Apm($ApmLogger, 0.1)) para aliviar a carga.
Saída de Exemplo:
- Consulta:
SELECT name FROM products WHERE price > 100 - Tempo: 0.023s
- Linhas: 15
Opções do Worker
Ajuste o worker ao seu gosto:
--timeout 300: Para após 5 minutos—bom para testes.--max_messages 500: Limita a 500 métricas—mantém finito.--batch_size 200: Processa 200 de uma vez—equilibra velocidade e memória.--daemon: Executa sem parar—ideal para monitoramento ao vivo.
Exemplo:
php vendor/bin/runway apm:worker --daemon --batch_size 100 --timeout 3600
Executa por uma hora, processando 100 métricas por vez.
Request ID no Aplicativo
Cada solicitação tem um ID de solicitação único para rastreamento. Você pode usar este ID em seu aplicativo para correlacionar logs e métricas. Por exemplo, você pode adicionar o request ID a uma página de erro:
Flight::map('error', function($message) {
// Get the request ID from the response header X-Flight-Request-Id
$requestId = Flight::response()->getHeader('X-Flight-Request-Id');
// Additionally you could fetch it from the Flight variable
// This method won't work well in swoole or other async platforms.
// $requestId = Flight::get('apm.request_id');
echo "Error: $message (Request ID: $requestId)";
});
Atualização
Se você está atualizando para uma versão mais recente do APM, há uma chance de que haja migrações de banco de dados que precisam ser executadas. Você pode fazer isso executando o seguinte comando:
php vendor/bin/runway apm:migrate
Isso executará quaisquer migrações necessárias para atualizar o esquema do banco de dados para a versão mais recente.
Nota: Se o seu banco de dados APM for grande em tamanho, essas migrações podem levar algum tempo para serem executadas. Você pode querer executar este comando durante horários de menor movimento.
Atualizando de 0.4.3 -> 0.5.0
Se você está atualizando de 0.4.3 para 0.5.0, precisará executar o seguinte comando:
php vendor/bin/runway apm:config-migrate
Isso migrará sua configuração do formato antigo usando o arquivo .runway-config.json para o novo formato que armazena as chaves/valores no arquivo config.php.
Limpando Dados Antigos
Para manter seu banco de dados organizado, você pode limpar dados antigos. Isso é especialmente útil se você estiver executando um aplicativo movimentado e quiser manter o tamanho do banco de dados gerenciável. Você pode fazer isso executando o seguinte comando:
php vendor/bin/runway apm:purge
Isso removerá todos os dados com mais de 30 dias do banco de dados. Você pode ajustar o número de dias passando um valor diferente para a opção --days:
php vendor/bin/runway apm:purge --days 7
Isso removerá todos os dados com mais de 7 dias do banco de dados.
Solução de Problemas
Travado? Tente isto:
-
Sem Dados no Painel?
- O worker está executando? Verifique
ps aux | grep apm:worker. - Os caminhos de configuração coincidem? Verifique se os DSNs do
.runway-config.jsonapontam para arquivos reais. - Execute
php vendor/bin/runway apm:workermanualmente para processar métricas pendentes.
- O worker está executando? Verifique
-
Erros do Worker?
- Dê uma olhada nos seus arquivos SQLite (ex:
sqlite3 /tmp/apm_metrics.sqlite "SELECT * FROM apm_metrics_log LIMIT 5"). - Verifique os logs do PHP para rastreamentos de pilha.
- Dê uma olhada nos seus arquivos SQLite (ex:
-
Painel Não Inicia?
- Porta 8001 em uso? Use
--port 8080. - PHP não encontrado? Use
--php-path /usr/bin/php. - Firewall bloqueando? Abra a porta ou use
--host localhost.
- Porta 8001 em uso? Use
-
Muito Lento?
- Reduza a taxa de amostragem:
$Apm = new Apm($ApmLogger, 0.05)(5%). - Reduza o tamanho do lote:
--batch_size 20.
- Reduza a taxa de amostragem:
-
Não Está Rastreando Exceções/Erros?
- Se você tem Tracy habilitado para seu projeto, ele substituirá o tratamento de erros do Flight. Você precisará desabilitar o Tracy e então certificar-se de que
Flight::set('flight.handle_errors', true);está definido.
- Se você tem Tracy habilitado para seu projeto, ele substituirá o tratamento de erros do Flight. Você precisará desabilitar o Tracy e então certificar-se de que
-
Não Está Rastreando Consultas de Banco de Dados?
- Prefira
SimplePdocom['trackApmQueries' => true]como o 5º argumento do construtor (array de opções). - Se você ainda usa o
PdoWrapperobsoleto, passetruecomo o 5º argumento. - Chame
$Apm->addPdoConnection($pdo)após criar a conexão.
- Prefira
Awesome-plugins/tracy
Tracy
Tracy é um manipulador de erros incrível que pode ser usado com Flight. Ele possui vários painéis que podem ajudar você a depurar sua aplicação. Também é muito fácil estender e adicionar seus próprios painéis. A equipe do Flight criou alguns painéis específicos para projetos Flight com o plugin flightphp/tracy-extensions (variáveis do Flight, consultas de banco de dados, requisição, sessão e um painel opcional de Twig quando você passa um perfil de profiler—veja Extensões do Tracy).
Instalação
Instale com o composer. E você realmente vai querer instalar isso sem a versão de desenvolvimento, pois o Tracy vem com um componente de tratamento de erros para produção.
composer require tracy/tracy
Configuração Básica
Existem algumas opções básicas de configuração para começar. Você pode ler mais sobre elas na Documentação do Tracy.
require 'vendor/autoload.php';
use Tracy\Debugger;
// Habilita o 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 existe e é gravável.
Debugger::$logDirectory = __DIR__ . '/../log/';
Debugger::$strictMode = true; // exibe todos os erros
// Debugger::$strictMode = E_ALL & ~E_DEPRECATED & ~E_USER_DEPRECATED; // todos os erros exceto avisos de descontinuação
if (Debugger::$showBar) {
$app->set('flight.content_length', false); // se a barra do Debugger estiver visível, então o content-length não pode ser definido pelo Flight
// Isso é específico para a Extensão Tracy para Flight se você a incluiu
// caso contrário, comente isso.
new TracyExtensionLoader($app);
}
Dicas Úteis
Quando você está depurando seu código, existem algumas funções muito úteis para exibir dados para você.
bdump($var)- Isso irá despejar a variável na Barra do Tracy em um painel separado.dumpe($var)- Isso irá despejar a variável e então morrer imediatamente.
Awesome-plugins/active_record
Flight Active Record
Um active record é um mapeamento de uma entidade de banco de dados para um objeto PHP. Falando de forma simples, se você tem uma tabela users no seu banco de dados, você pode "traduzir" uma linha dessa tabela para uma classe User e um objeto $user no seu código. Veja exemplo básico.
Clique aqui para o repositório no GitHub.
Exemplo Básico
Vamos assumir que você tem 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($database_connection)
{
// você pode definir assim
parent::__construct($database_connection, 'users');
// ou assim
parent::__construct($database_connection, null, [ 'table' => 'users']);
}
}
Agora veja a mágica acontecer!
// para sqlite
$database_connection = new PDO('sqlite:test.db'); // isso é apenas um exemplo, você provavelmente usaria uma conexão de banco de dados real
// para mysql
$database_connection = new PDO('mysql:host=localhost;dbname=test_db&charset=utf8bm4', 'username', 'password');
// ou mysqli
$database_connection = new mysqli('localhost', 'username', 'password', 'test_db');
// ou mysqli com criação não baseada em objeto
$database_connection = mysqli_connect('localhost', 'username', 'password', 'test_db');
$user = new User($database_connection);
$user->name = 'Bobby Tables';
$user->password = password_hash('some cool password');
$user->insert();
// ou $user->save();
echo $user->id; // 1
$user->name = 'Joseph Mamma';
$user->password = password_hash('some cool password again!!!');
$user->insert();
// não pode usar $user->save() aqui ou 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 recupera?
$user->find(1); // encontre id = 1 no banco de dados e retorne-o.
echo $user->name; // 'Bobby Tables'
E se você quiser encontrar todos os usuários?
$users = $user->findAll();
E com uma condição específica?
$users = $user->like('name', '%mamma%')->findAll();
Veja como isso é divertido? Vamos instalá-lo e começar!
Instalação
Simplesmente instale com o Composer
composer require flightphp/active-record
Uso
Isso pode ser usado como uma biblioteca independente ou com o Flight PHP Framework. Completamente com você.
Independente
Apenas certifique-se de passar uma conexão PDO para o construtor.
$pdo_connection = new PDO('sqlite:test.db'); // isso é apenas um exemplo, você provavelmente usaria uma conexão de banco de dados real
$User = new User($pdo_connection);
Não quer sempre definir sua conexão de banco de dados no construtor? Veja Gerenciamento de Conexão de Banco de Dados para outras ideias!
Registrar como um método no Flight
Se você estiver usando o Flight PHP Framework, você pode registrar a classe ActiveRecord como um serviço, mas honestamente não precisa.
Flight::register('user', 'User', [ $pdo_connection ]);
// então você pode usá-lo assim em um controlador, uma função, etc.
Flight::user()->find(1);
Métodos runway
runway é uma ferramenta CLI para Flight que tem um comando personalizado para esta biblioteca.
# Uso
php runway make:record database_table_name [class_name]
# Exemplo
php runway make:record users
Isso criará uma nova classe no diretório app/records/ como UserRecord.php com o seguinte conteúdo:
<?php
declare(strict_types=1);
namespace app\records;
/**
* Classe ActiveRecord para a tabela users.
* @link https://docs.flightphp.com/awesome-plugins/active-record
*
* @property int $id
* @property string $username
* @property string $email
* @property string $password_hash
* @property string $created_dt
*/
class UserRecord extends \flight\ActiveRecord
{
/**
* @var array $relations Defina as relações para o modelo
* https://docs.flightphp.com/awesome-plugins/active-record#relationships
*/
protected array $relations = [
// 'relation_name' => [ self::HAS_MANY, 'RelatedClass', 'foreign_key' ],
];
/**
* Construtor
* @param mixed $databaseConnection A conexão com o banco de dados
*/
public function __construct($databaseConnection)
{
parent::__construct($databaseConnection, 'users');
}
}
Funções CRUD
find($id = null) : boolean|ActiveRecord
Encontra um registro e o atribui ao objeto atual. Se você passar um $id de algum tipo, ele executará uma busca na chave primária com esse valor. Se nada for passado, ele simplesmente encontrará o primeiro registro na tabela.
Adicionalmente, você pode passar outros métodos auxiliares para consultar sua tabela.
// encontre um registro com algumas condições antes
$user->notNull('password')->orderBy('id DESC')->find();
// encontre 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 (v0.4.0)
Retorna true se o registro atual foi hidratado (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($pdo_connection);
$user->name = 'demo';
$user->password = md5('demo');
$user->insert();
Chaves Primárias Baseadas em Texto
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 uma de duas maneiras.
$user = new User($pdo_connection, [ 'primaryKey' => 'uuid' ]);
$user->uuid = 'some-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ê através de eventos.
class User extends flight\ActiveRecord {
public function __construct($database_connection)
{
parent::__construct($database_connection, 'users', [ 'primaryKey' => 'uuid' ]);
// você também pode definir a primaryKey dessa maneira em vez do array acima.
$this->primaryKey = 'uuid';
}
protected function beforeInsert(self $self) {
$self->uuid = uniqid(); // ou como você precisar gerar seus ids únicos
}
}
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 na sua tabela. É por isso que é recomendado usar o evento para lidar com isso automaticamente.
update(): boolean|ActiveRecord
Atualiza o registro atual no banco de dados.
$user->greaterThan('id', 0)->orderBy('id desc')->find();
$user->email = 'test@example.com';
$user->update();
save(): boolean|ActiveRecord
Insere ou atualiza o registro atual no banco de dados. Se o registro tiver um id, ele atualizará, caso contrário, inserirá.
$user = new User($pdo_connection);
$user->name = 'demo';
$user->password = md5('demo');
$user->save();
Nota: Se você tiver relações definidas na classe, ele salvará recursivamente essas relações também se elas 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 múltiplos registros executando uma busca 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 = 'test@example.com'; // agora o email é considerado "sujo" pois foi alterado.
$user->update();
// agora não há dados sujos porque foi atualizado e persistido no banco de dados
$user->password = password_hash()'newpassword'); // agora isso é sujo
$user->dirty(); // passar nada limpará todas as entradas sujas.
$user->update(); // nada será atualizado pois nada foi capturado como sujo.
$user->dirty([ 'name' => 'something', 'password' => password_hash('a different password') ]);
$user->update(); // tanto name quanto password são atualizados.
copyFrom(array $data): ActiveRecord (v0.4.0)
Isso é um alias para o método dirty(). É um pouco mais claro o que você está fazendo.
$user->copyFrom([ 'name' => 'something', 'password' => password_hash('a different password') ]);
$user->update(); // tanto name quanto password são atualizados.
isDirty(): boolean (v0.4.0)
Retorna true se o registro atual foi alterado.
$user->greaterThan('id', 0)->orderBy('id desc')->find();
$user->email = 'test@email.com';
$user->isDirty(); // true
reset(bool $include_query_data = true): ActiveRecord
Redefine o registro atual para seu estado inicial. Isso é realmente bom para usar em comportamentos de loop. Se você passar true, ele também redefinirá os dados da consulta usados para encontrar o objeto atual (comportamento padrão).
$users = $user->greaterThan('id', 0)->orderBy('id desc')->find();
$user_company = new UserCompany($pdo_connection);
foreach($users as $user) {
$user_company->reset(); // comece com uma lousa limpa
$user_company->user_id = $user->id;
$user_company->company_id = $some_company_id;
$user_company->insert();
}
getBuiltSql(): string (v0.4.1)
Após 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.
Métodos de Consulta SQL
select(string $field1 [, string $field2 ... ])
Você pode selecionar apenas algumas das colunas em uma tabela se quiser (é mais performático em tabelas muito largas com muitas colunas)
$user->select('id', 'name')->find();
from(string $table)
Você pode tecnicamente escolher outra tabela também! Por que não?!
$user->select('id', 'name')->from('user')->find();
join(string $table_name, string $join_condition)
Você pode até juntar com outra tabela no banco de dados.
$user->join('contacts', 'contacts.user_id = users.id')->find();
where(string $where_conditions)
Você pode definir alguns argumentos where personalizados (você não pode definir params nesta declaraçã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 a ataques conhecidos como Injeção de SQL. Há muitos artigos online, por favor pesquise "sql injection attacks php" e você encontrará muitos artigos sobre o assunto. A maneira adequada de lidar com isso com esta biblioteca é, em vez de usar este método where(), você faria algo como $user->eq('id', $id)->eq('name', $name)->find(); Se você absolutamente tiver que fazer isso, a biblioteca PDO tem $pdo->quote($var) para escapá-lo para você. Somente após usar quote() você pode usá-lo em uma declaração where().
group(string $group_by_statement)/groupBy(string $group_by_statement)
Agrupe seus resultados por uma condição particular.
$user->select('COUNT(*) as count')->groupBy('name')->findAll();
order(string $order_by_statement)/orderBy(string $order_by_statement)
Ordene a consulta retornada de uma certa maneira.
$user->orderBy('name DESC')->find();
limit(string $limit)/limit(int $offset, int $limit)
Limite a quantidade de registros retornados. Se um segundo int for dado, será offset, limit assim como no SQL.
$user->orderby('name DESC')->limit(0, 10)->findAll();
Condições WHERE
equal(string $field, mixed $value) / eq(string $field, mixed $value)
Onde field = $value
$user->eq('id', 1)->find();
notEqual(string $field, mixed $value) / ne(string $field, mixed $value)
Onde field <> $value
$user->ne('id', 1)->find();
isNull(string $field)
Onde field IS NULL
$user->isNull('id')->find();
isNotNull(string $field) / notNull(string $field)
Onde field IS NOT NULL
$user->isNotNull('id')->find();
greaterThan(string $field, mixed $value) / gt(string $field, mixed $value)
Onde field > $value
$user->gt('id', 1)->find();
lessThan(string $field, mixed $value) / lt(string $field, mixed $value)
Onde 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
$user->ge('id', 1)->find();
lessThanOrEqual(string $field, mixed $value) / le(string $field, mixed $value) / lte(string $field, mixed $value)
Onde 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
$user->like('name', 'de')->find();
in(string $field, array $values) / notIn(string $field, array $values)
Onde field IN($value) ou field NOT IN($value)
$user->in('id', [1, 2])->find();
between(string $field, array $values)
Onde field BETWEEN $value AND $value1
$user->between('id', [1, 2])->find();
Condições OR
É possível envolver suas condições em uma declaração OR. Isso é feito com os métodos startWrap() e endWrap() ou preenchendo o 3º parâmetro da condição após o campo e valor.
// Método 1
$user->eq('id', 1)->startWrap()->eq('name', 'demo')->or()->eq('name', 'test')->endWrap('OR')->find();
// Isso avaliará como `id = 1 AND (name = 'demo' OR name = 'test')`
// Método 2
$user->eq('id', 1)->eq('name', 'demo', 'OR')->find();
// Isso avaliará como `id = 1 OR name = 'demo'`
Relacionamentos
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 um pouco de configuração extra na classe antes.
Definir o array $relations não é difícil, mas adivinhar a sintaxe correta pode ser confuso.
protected array $relations = [
// você pode nomear a chave como quiser. O nome do ActiveRecord provavelmente é bom. Ex: user, contact, client
'user' => [
// 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 isso referenciará
// obrigatório
// dependendo do tipo de relacionamento
// self::HAS_ONE = a chave estrangeira que referencia a junção
// self::HAS_MANY = a chave estrangeira que referencia a junção
// self::BELONGS_TO = a chave local que referencia a junção
'local_or_foreign_key',
// apenas FYI, isso também só junta com a chave primária do modelo "outro"
// opcional
[ 'eq' => [ 'client_id', 5 ], 'select' => 'COUNT(*) as count', 'limit' 5 ], // condições adicionais que você quer ao juntar a relação
// $record->eq('client_id', 5)->select('COUNT(*) as count')->limit(5))
// opcional
'back_reference_name' // isso é se você quiser referenciar de volta este relacionamento para si mesmo Ex: $user->contact->user;
];
]
class User extends ActiveRecord{
protected array $relations = [
'contacts' => [ self::HAS_MANY, Contact::class, 'user_id' ],
'contact' => [ self::HAS_ONE, Contact::class, 'user_id' ],
];
public function __construct($database_connection)
{
parent::__construct($database_connection, 'users');
}
}
class Contact extends ActiveRecord{
protected array $relations = [
'user' => [ self::BELONGS_TO, User::class, 'user_id' ],
'user_with_backref' => [ self::BELONGS_TO, User::class, 'user_id', [], 'contact' ],
];
public function __construct($database_connection)
{
parent::__construct($database_connection, 'contacts');
}
}
Agora temos as referências configuradas para que possamos usá-las facilmente!
$user = new User($pdo_connection);
// encontre o usuário mais recente.
$user->notNull('id')->orderBy('id desc')->find();
// obtenha contatos usando a relação:
foreach($user->contacts as $contact) {
echo $contact->id;
}
// ou podemos ir do outro jeito.
$contact = new Contact();
// encontre um contato
$contact->find();
// obtenha o usuário usando a relação:
echo $contact->user->name; // este é o nome do usuário
Bem legal, hein?
Carregamento Ansioso
Visão Geral
O carregamento ansioso resolve o problema de consulta N+1 carregando relacionamentos antecipadamente. Em vez de executar uma consulta separada para os relacionamentos de cada registro, o carregamento ansioso busca todos os dados relacionados em apenas uma consulta adicional por relacionamento.
Nota: O carregamento ansioso está disponível apenas para v0.7.0 e acima.
Uso Básico
Use o método with() para especificar quais relacionamentos carregar ansiosamente:
// Carregue usuários com seus contatos em 2 consultas em vez de N+1
$users = $user->with('contacts')->findAll();
foreach ($users as $u) {
foreach ($u->contacts as $contact) {
echo $contact->email; // Sem consulta adicional!
}
}
Múltiplas Relações
Carregue múltiplos relacionamentos de uma vez:
$users = $user->with(['contacts', 'profile', 'settings'])->findAll();
Tipos de Relacionamento
HAS_MANY
// Carregue ansiosamente todos os contatos para cada usuário
$users = $user->with('contacts')->findAll();
foreach ($users as $u) {
// $u->contacts já está carregado como um array
foreach ($u->contacts as $contact) {
echo $contact->email;
}
}
HAS_ONE
// Carregue ansiosamente um contato para cada usuário
$users = $user->with('contact')->findAll();
foreach ($users as $u) {
// $u->contact já está carregado como um objeto
echo $u->contact->email;
}
BELONGS_TO
// Carregue ansiosamente usuários pais para todos os contatos
$contacts = $contact->with('user')->findAll();
foreach ($contacts as $c) {
// $c->user já está carregado
echo $c->user->name;
}
Com find()
O carregamento ansioso funciona com findAll() e find() :
$user = $user->with('contacts')->find(1);
// Usuário e todos os seus contatos carregados em 2 consultas
Benefícios de Desempenho
Sem carregamento ansioso (problema N+1):
$users = $user->findAll(); // 1 consulta
foreach ($users as $u) {
$contacts = $u->contacts; // N consultas (uma por usuário!)
}
// Total: 1 + N consultas
Com carregamento ansioso:
$users = $user->with('contacts')->findAll(); // 2 consultas no total
foreach ($users as $u) {
$contacts = $u->contacts; // 0 consultas adicionais!
}
// Total: 2 consultas (1 para usuários + 1 para todos os contatos)
Para 10 usuários, isso reduz as consultas de 11 para 2 - uma redução de 82%!
Notas Importantes
- O carregamento ansioso é completamente opcional - o carregamento preguiçoso ainda funciona como antes
- Relacionamentos já carregados são automaticamente ignorados
- Referências de volta funcionam com carregamento ansioso
- Callbacks de relação são respeitados durante o carregamento ansioso
Limitações
- Carregamento ansioso aninhado (ex., with(['contacts.addresses']) ) não é suportado atualmente
- Restrições de carregamento ansioso via closures não são suportadas nesta versão
Definindo Dados Personalizados
Às vezes, você pode precisar anexar algo único ao seu ActiveRecord, como um cálculo personalizado que pode ser mais fácil de simplesmente anexar ao objeto que seria passado para, digamos, um template.
setCustomData(string $field, mixed $value)
Você anexa os dados personalizados com o método setCustomData().
$user->setCustomData('page_view_count', $page_view_count);
E então você simplesmente o referencia como uma propriedade de objeto normal.
echo $user->page_view_count;
Eventos
Uma funcionalidade super legal sobre esta biblioteca é sobre eventos. Eventos são acionados em certos momentos com base em certos métodos que você chama. Eles são muito úteis para configurar dados para você automaticamente.
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 &
// 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 dessa maneira.
$config['table'] = 'users';
}
}
beforeFind(ActiveRecord $ActiveRecord)
Isso provavelmente é útil apenas se você precisar de uma manipulação de consulta a cada vez.
class User extends flight\ActiveRecord {
public function __construct($database_connection)
{
parent::__construct($database_connection, 'users');
}
protected function beforeFind(self $self) {
// sempre execute id >= 0 se isso for o seu estilo
$self->gte('id', 0);
}
}
afterFind(ActiveRecord $ActiveRecord)
Este é provavelmente mais útil se você sempre precisar executar alguma lógica toda vez que este registro é buscado. Você precisa descriptografar algo? Você precisa executar uma consulta de contagem personalizada a cada vez (não performático, mas tanto faz)?
class User extends flight\ActiveRecord {
public function __construct($database_connection)
{
parent::__construct($database_connection, 'users');
}
protected function afterFind(self $self) {
// descriptografando algo
$self->secret = yourDecryptFunction($self->secret, $some_key);
// talvez armazenando algo personalizado como uma consulta???
$self->setCustomData('view_count', $self->select('COUNT(*) count')->from('user_views')->eq('user_id', $self->id)['count'];
}
}
beforeFindAll(ActiveRecord $ActiveRecord)
Isso provavelmente é útil apenas se você precisar de uma manipulação de consulta a cada vez.
class User extends flight\ActiveRecord {
public function __construct($database_connection)
{
parent::__construct($database_connection, 'users');
}
protected function beforeFindAll(self $self) {
// sempre execute id >= 0 se isso for o seu estilo
$self->gte('id', 0);
}
}
afterFindAll(array<int,ActiveRecord> $results)
Similar ao afterFind() mas você pode fazer isso para todos os registros!
class User extends flight\ActiveRecord {
public function __construct($database_connection)
{
parent::__construct($database_connection, 'users');
}
protected function afterFindAll(array $results) {
foreach($results as $self) {
// faça algo legal como afterFind()
}
}
}
beforeInsert(ActiveRecord $ActiveRecord)
Realmente útil se você precisar de alguns valores padrão definidos a cada vez.
class User extends flight\ActiveRecord {
public function __construct($database_connection)
{
parent::__construct($database_connection, 'users');
}
protected function beforeInsert(self $self) {
// defina alguns padrões sólidos
if(!$self->created_date) {
$self->created_date = gmdate('Y-m-d');
}
if(!$self->password) {
$self->password = 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($database_connection)
{
parent::__construct($database_connection, 'users');
}
protected function afterInsert(self $self) {
// você faz o que quiser
Flight::cache()->set('most_recent_insert_id', $self->id);
// ou o que for....
}
}
beforeUpdate(ActiveRecord $ActiveRecord)
Realmente útil se você precisar de alguns valores padrão definidos a cada vez em uma atualização.
class User extends flight\ActiveRecord {
public function __construct($database_connection)
{
parent::__construct($database_connection, 'users');
}
protected function beforeInsert(self $self) {
// defina alguns padrões sólidos
if(!$self->updated_date) {
$self->updated_date = gmdate('Y-m-d');
}
}
}
afterUpdate(ActiveRecord $ActiveRecord)
Talvez você tenha um caso de uso para alterar dados após a atualização?
class User extends flight\ActiveRecord {
public function __construct($database_connection)
{
parent::__construct($database_connection, 'users');
}
protected function afterInsert(self $self) {
// você faz o que quiser
Flight::cache()->set('most_recently_updated_user_id', $self->id);
// ou o que for....
}
}
beforeSave(ActiveRecord $ActiveRecord)/afterSave(ActiveRecord $ActiveRecord)
Isso é útil se você quiser que eventos aconteçam tanto em inserções quanto em atualizações. Vou poupá-lo da longa explicação, mas tenho certeza de que você pode adivinhar o que é.
class User extends flight\ActiveRecord {
public function __construct($database_connection)
{
parent::__construct($database_connection, 'users');
}
protected function beforeSave(self $self) {
$self->last_updated = gmdate('Y-m-d H:i:s');
}
}
beforeDelete(ActiveRecord $ActiveRecord)/afterDelete(ActiveRecord $ActiveRecord)
Não tenho certeza do que você gostaria de fazer aqui, mas sem julgamentos! Vá em frente!
class User extends flight\ActiveRecord {
public function __construct($database_connection)
{
parent::__construct($database_connection, 'users');
}
protected function beforeDelete(self $self) {
echo 'He was a brave soldier... :cry-face:';
}
}
Gerenciamento de Conexão de Banco de Dados
Quando você está usando esta biblioteca, você pode definir a conexão de banco de dados de algumas maneiras diferentes. Você pode definir a conexão no construtor, você pode defini-la via uma variável de configuração $config['connection'] ou você pode defini-la via setDatabaseConnection() (v0.4.1).
$pdo_connection = new PDO('sqlite:test.db'); // por exemplo
$user = new User($pdo_connection);
// ou
$user = new User(null, [ 'connection' => $pdo_connection ]);
// ou
$user = new User();
$user->setDatabaseConnection($pdo_connection);
Se você quiser evitar sempre definir um $database_connection toda vez que chamar um active record, há maneiras de contornar isso!
// index.php ou bootstrap.php
// Defina isso como uma classe registrada no Flight
Flight::register('db', 'PDO', [ 'sqlite:test.db' ]);
// User.php
class User extends flight\ActiveRecord {
public function __construct(array $config = [])
{
$database_connection = $config['connection'] ?? Flight::db();
parent::__construct($database_connection, 'users', $config);
}
}
// E agora, sem argumentos necessários!
$user = new User();
Nota: Se você estiver planejando testes unitários, fazer isso dessa maneira pode adicionar alguns desafios aos testes unitários, mas no geral, porque você pode injetar sua conexão com
setDatabaseConnection()ou$config['connection'], não é tão ruim.
Se você precisar atualizar a conexão de banco de dados, por exemplo, se estiver executando um script CLI de longa duração e precisar atualizar a conexão de tempos em tempos, você pode redefinir a conexão com $your_record->setDatabaseConnection($pdo_connection).
Contribuição
Por favor, faça. :D
Configuração
Quando você contribuir, certifique-se de executar composer test-coverage para manter 100% de cobertura de testes (isso não é cobertura de teste unitário real, mais como testes de integração).
Também certifique-se de executar composer beautify e composer phpcs para corrigir qualquer erro de linting.
Licença
MIT
Awesome-plugins/latte
Latte
Latte é um motor de templates completo que é muito fácil de usar e se sente mais próximo de uma sintaxe PHP do que Twig ou Smarty. Também é muito fácil de estender e adicionar seus próprios filtros e funções.
Instalação
Instale com o composer.
composer require latte/latte
Configuração Básica
Existem algumas opções de configuração básicas para começar. Você pode ler mais sobre elas na Documentação do Latte.
require 'vendor/autoload.php';
$app = Flight::app();
$app->map('render', function(string $template, array $data, ?string $block): void {
$latte = new Latte\Engine;
// Onde o latte armazena especificamente seu cache
$latte->setTempDirectory(__DIR__ . '/../cache/');
$finalPath = Flight::get('flight.views.path') . $template;
$latte->render($finalPath, $data, $block);
});
Exemplo Simples de Layout
Aqui está um exemplo simples de um arquivo de layout. Este é o arquivo que será usado para envolver todas as suas outras views.
<!-- app/views/layout.latte -->
<!doctype html>
<html lang="en">
<head>
<title>{$title ? $title . ' - '}Meu App</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<header>
<nav>
<!-- seus elementos de navegação aqui -->
</nav>
</header>
<div id="content">
<!-- Esta é a mágica bem aqui -->
{block content}{/block}
</div>
<div id="footer">
© Copyright
</div>
</body>
</html>
E agora temos o seu arquivo que vai ser renderizado dentro daquele 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 no bloco de conteúdo -->
{block content}
<h1>Página Inicial</h1>
<p>Bem-vindo ao meu app!</p>
{/block}
Então, quando você for renderizar isso dentro da sua função ou controlador, você faria algo assim:
// rota simples
Flight::route('/', function () {
Flight::render('home.latte', [
'title' => 'Página Inicial'
]);
});
// ou se você estiver usando um controlador
Flight::route('/', [HomeController::class, 'index']);
// HomeController.php
class HomeController
{
public function index()
{
Flight::render('home.latte', [
'title' => 'Página Inicial'
]);
}
}
Consulte a Documentação do Latte para mais informações sobre como usar o Latte em todo o seu potencial!
Depuração com Tracy
O PHP 8.1+ é necessário para esta seção.
Você também pode usar o Tracy para ajudar na depuração dos seus arquivos de template Latte diretamente da caixa! Se você já tiver o Tracy instalado, precisa adicionar a extensão Latte ao Tracy.
// services.php
use Tracy\Debugger;
$app->map('render', function(string $template, array $data, ?string $block): void {
$latte = new Latte\Engine;
// Onde o latte armazena especificamente seu cache
$latte->setTempDirectory(__DIR__ . '/../cache/');
$finalPath = Flight::get('flight.views.path') . $template;
// Isso só adicionará a extensão se a Barra de Depuração do Tracy estiver ativada
if (Debugger::$showBar === true) {
// é aqui que você adiciona o Painel Latte ao Tracy
$latte->addExtension(new Latte\Bridges\Tracy\TracyExtension);
}
$latte->render($finalPath, $data, $block);
});Awesome-plugins/awesome_plugins
Plugins Incríveis
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 Flight e outros são bibliotecas micro/lite para ajudar você a começar.
Ferramentas de IA
Flight pode ficar ainda mais legal com plugins alimentados por IA.
- Flight MCP - Um plugin para integrar MCP (Model Control Protocol) com Flight, permitindo funcionalidades perfeitas alimentadas por IA. Focado principalmente nas páginas de documentação, ajuda a reduzir custos de tokens fornecendo as informações mais atualizadas sobre seus projetos Flight.
Documentação de API
A documentação de API é crucial para qualquer API. Ela ajuda os desenvolvedores a entenderem como interagir com sua API e o que esperar em retorno. Existem algumas ferramentas disponíveis para ajudar você a gerar documentação de API para seus Projetos Flight.
- FlightPHP OpenAPI Generator - Post de blog escrito por Daniel Schreiber sobre como usar a Especificação OpenAPI com FlightPHP para construir sua API usando uma abordagem API-first.
- SwaggerUI - Swagger UI é uma ótima ferramenta para ajudar você a gerar documentação de API para seus projetos Flight. É muito fácil de usar e pode ser personalizado para atender suas necessidades. Esta é a biblioteca PHP para ajudar você a gerar a documentação Swagger.
Monitoramento de Performance de Aplicação (APM)
O Monitoramento de Performance de Aplicação (APM) é crucial para qualquer aplicação. Ele ajuda você a entender como sua aplicação está performando e onde estão os gargalos. Existem várias ferramentas APM que podem ser usadas com Flight.
- official flightphp/apm - Flight APM é uma biblioteca APM simples que pode ser usada para monitorar suas aplicações Flight. Pode ser usada para monitorar a performance da sua aplicação e ajudar você a identificar gargalos.
Assíncrono
Flight já é um framework rápido, mas colocar um motor turbo nele torna tudo mais divertido (e desafiador)!
- flightphp/async - Biblioteca Async oficial do Flight. Esta biblioteca é uma forma simples de adicionar processamento assíncrono à sua aplicação. Ela usa Swoole/Openswoole por baixo dos panos para fornecer uma forma simples e eficaz de executar tarefas de forma assíncrona.
Autorização/Permissões
Autorização e Permissões são cruciais para qualquer aplicação que requer controles para quem pode acessar o quê.
- official flightphp/permissions - Biblioteca de Permissões oficial do Flight. Esta biblioteca é uma forma simples de adicionar permissões de usuário e aplicação à sua aplicação.
Autenticação
A autenticação é essencial para aplicações que precisam verificar a identidade do usuário e proteger endpoints de API.
- firebase/php-jwt - Biblioteca JSON Web Token (JWT) para PHP. Uma forma simples e segura de implementar autenticação baseada em token em suas aplicações Flight. Perfeita para autenticação de API sem estado, proteger rotas com middleware e implementar fluxos de autorização estilo OAuth.
Cache
O cache é uma ótima forma de acelerar sua aplicação. Existem várias bibliotecas de cache que podem ser usadas com Flight.
- official flightphp/cache - Classe de cache em arquivo PHP leve, simples e independente
CLI
Aplicações CLI são uma ótima forma de interagir com sua aplicação. Você pode usá-las para gerar controllers, exibir todas as rotas e muito mais.
- official flightphp/runway - Runway é uma aplicação CLI que ajuda você a gerenciar suas aplicações Flight.
Cookies
Cookies são uma ótima forma 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 mais.
- overclokk/cookie - PHP Cookie é uma biblioteca PHP que fornece uma forma simples e eficaz de gerenciar cookies.
Depuração
A depuração é crucial quando você está desenvolvendo em seu ambiente local. Existem alguns plugins que podem elevar sua experiência de depuração.
- tracy/tracy - Este é um manipulador de erros completo que pode ser usado com Flight. Tem vários painéis que podem ajudar você a depurar sua aplicação. Também é muito fácil de estender e adicionar seus próprios painéis.
- official flightphp/tracy-extensions - Usado com o manipulador de erros Tracy, este plugin adiciona alguns painéis extras para ajudar na depuração especificamente para projetos Flight.
Bancos de Dados
Bancos de dados são o núcleo 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 algumas são ORMs completos.
- official flightphp/core SimplePdo - Helper PDO oficial do Flight que faz parte do núcleo. Este é um wrapper moderno com métodos auxiliares convenientes como
insert(),update(),delete()etransaction()para simplificar operações de banco de dados. Todos os resultados são retornados como Collections para acesso flexível em array/objeto. Não é um ORM, apenas uma forma melhor de trabalhar com PDO. - deprecated flightphp/core PdoWrapper - Wrapper PDO oficial do Flight que faz parte do núcleo (obsoleto a partir da v3.18.0). Use SimplePdo em vez disso.
- official flightphp/active-record - ORM/Mapper ActiveRecord oficial do Flight. Uma ótima biblioteca pequena para recuperar e armazenar dados facilmente em seu banco de dados.
- byjg/php-migration - Plugin para acompanhar todas as mudanças de banco de dados do seu projeto.
- knifelemon/easy-query - Construtor de consultas SQL fluente e leve que gera SQL e parâmetros para prepared statements. Funciona bem com SimplePdo.
Criptografia
A criptografia é crucial para qualquer aplicação que armazena dados sensíveis. Criptografar e descriptografar os dados não é muito 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 commitá-la em seu repositório de código.
- defuse/php-encryption - Esta é uma biblioteca que pode ser usada para criptografar e descriptografar dados. Começar é bastante simples para começar a criptografar e descriptografar dados.
Enviar e-mail é uma necessidade central para a maioria das aplicações web - mensagens de boas-vindas, redefinição de senha, notificações. Essas bibliotecas tornam isso simples sem perder a confiabilidade da entrega.
- ryanstubbs/flightmail - FlightMail envolve o Symfony Mailer com uma API fluente e amigável ao Flight. Envie por SMTP ou qualquer provedor importante via strings DSN simples, roteie provedores diferentes por mensagem e renderize corpos com templates Twig ou Latte. Este é um plugin não oficial para Flight e não é mantido pela equipe do Flight.
Fila de Jobs
Filas de jobs são realmente úteis para processar tarefas de forma assíncrona. Isso pode ser enviar emails, processar imagens ou qualquer coisa que não precise ser feita em tempo real.
- n0nag0n/simple-job-queue - Simple Job Queue é uma biblioteca que pode ser usada para processar jobs de forma assíncrona. Pode ser usada com beanstalkd, MySQL/MariaDB, SQLite e PostgreSQL.
Sessão
Sessões não são realmente úteis para APIs, mas para construir uma aplicação web, sessões podem ser cruciais para manter estado e informações de login.
- official flightphp/session - Biblioteca de Sessão oficial do Flight. Esta é uma biblioteca de sessão simples que pode ser usada para armazenar e recuperar dados de sessão. Ela usa o gerenciamento de sessão integrado do PHP.
- Ghostff/Session - Gerenciador de Sessão PHP (não-bloqueante, flash, segment, criptografia de sessão). Usa PHP open_ssl para criptografia/descriptografia opcional de dados de sessão.
Templates
Templates são essenciais para qualquer aplicação web com interface. Existem vários motores de template que podem ser usados com Flight.
- deprecated flightphp/core View - Este é um motor de template muito básico que faz parte do núcleo. Não é recomendado usá-lo se você tiver mais de algumas páginas no seu projeto.
- latte/latte - Latte é um motor de template completo que é muito fácil de usar e parece mais próximo da sintaxe PHP que Twig ou Smarty. Também é muito fácil de estender e adicionar seus próprios filtros e funções.
- twig/twig - Twig é um motor de template flexível, rápido e seguro (o mesmo usado pelo Symfony). Ferramentas de IA e muitos desenvolvedores PHP o conhecem bem, ele escapa automaticamente a saída por padrão e tem um enorme ecossistema de extensões.
- knifelemon/comment-template - CommentTemplate é um poderoso motor de template PHP com compilação de assets, herança de templates e processamento de variáveis. Possui minificação automática de CSS/JS, cache, codificação Base64 e integração opcional com o framework Flight PHP.
Integração com WordPress
Quer usar Flight no seu projeto WordPress? Tem um plugin prático para isso!
- n0nag0n/wordpress-integration-for-flight-framework - Este plugin WordPress permite que você execute Flight junto com o WordPress. É perfeito para adicionar APIs personalizadas, microsserviços ou até mesmo aplicações completas ao seu site WordPress usando o framework Flight. Super útil se você quer o melhor dos dois mundos!
Contribuindo
Tem um plugin que gostaria de compartilhar? Envie um pull request para adicioná-lo à lista!
Media
Mídia
Tentamos rastrear o que pudemos dos vários tipos de mídia na internet sobre o Flight. Veja abaixo diferentes recursos que você pode usar para aprender mais sobre o Flight.
Artigos e Escritos
- Unit Testing and SOLID Principles por Brian Fenton (2015?)
- PHP Web Framework Flight por ojambo (2025)
- Define, Generate, and Implement: An API-First Approach with OpenAPI Generator and FlightPHP por Daniel Schreiber (2025)
- Best PHP Micro Frameworks for 2024 por n0nag0n (2024)
- Creating a RESTful API with Flight Framework por n0nag0n (2024)
- Building a Simple Blog with Flight Part 2 por n0nag0n (2024)
- Building a Simple Blog with Flight Part 1 por n0nag0n (2024)
- 🚀 Build a Simple CRUD API in PHP with the Flight Framework por soheil-khaledabadi (2024)
- Building a PHP Web Application with the Flight Micro-framework por Arthur C. Codex (2023)
- Best PHP Frameworks for Web Development in 2024 por Ravikiran A S (2023)
- Top 12 PHP Frameworks: A Comprehensive Guide for 2023 por marketing kbk (2023)
- 5 PHP Frameworks You've (Probably) Never Heard of por n0nag0n (2022)
- 12 top PHP frameworks for web developers to consider in 2023 por Anna Monus (2022)
- The Best PHP Microframeworks on a Cloud Server por Shahzeb Ahmed (2021)
- PHP framework: Top 15 powerful ones for your web development por AHT Tech (2020)
- Easy PHP Routing with FlightPHP por Lucas Conceição (2019)
- Trying Out New PHP Framework (Flight) por Leon (2017)
- Setting up FlightPHP to work with Backbonejs por Timothy Tocci (2015)
Vídeos e Tutoriais
- Build a Flight PHP App with MVC & MariaDB in 10 Minutes! (Beginner Friendly) por ojamboshop (2025)
- Create a REST API for IoT Devices Using PHP & FlightPHP - ESP32 API por IoT Craft Hub (2024)
- PHP Flight Framework Simple Introductory Video por n0nag0n (2024)
- Set header HTTP code in Flightphp (3 Solutions!!) por Roel Van de Paar (2024)
- PHP Flight Framework Tutorial. Super easy API Project! por n0nag0n (2022)
- Aplicación web CRUD con php y mysql y bootstrap usando flight por Devlopteca - Oscar Uh (2021)
- DevOps & SysAdmins: Lighttpd rewrite rule for Flight PHP microframework por Roel Van de Paar (2021)
- Tutorial REST API Flight PHP #PART2 INSERT TABLE Info #Code (Tagalog) por Info Singkat Official (2020)
- Tutorial REST API Flight PHP #PART1 Info #Code (Tagalog) por Info Singkat Official (2020)
- How To Create JSON REST API IN PHP - Part 2 por Codewife (2018)
- How To Create JSON REST API IN PHP - Part 1 por Codewife (2018)
- Teste Micro Frameworks PHP - Flight PHP, Lumen, Slim 3 e Laravel por Codemarket (2016)
- Tutorial 1 Flight PHP - Instalación por absagg (2014)
- Tutorial 2 Flight PHP - Route parte 1 por absagg (2014)
Faltando Algo?
Estamos faltando algo que você escreveu ou gravou? Deixe-nos saber com uma issue ou pull request!
Examples
Precisa de um início rápido?
Você tem duas opções para começar com um novo projeto Flight:
- Full Skeleton Boilerplate: Um exemplo mais completo com controladores e visualizações.
- Single File Skeleton Boilerplate: Um único arquivo que inclui tudo o que você precisa para executar seu app em um único arquivo simples.
Exemplos contribuídos pela comunidade:
- flightravel: FlightPHP com diretórios Laravel, com ferramentas PHP + GH Actions
- fleact - Um kit inicial FlightPHP com integração ReactJS.
- flastro - Um kit inicial FlightPHP com integração Astro.
- velt - Velt é um template inicial rápido e fácil para Svelte com backend FlightPHP.
- vite-flightphp - FlightPHP e frontend moderno (Vite + Tailwind CSS) com hot reload.
Precisa de Alguma Inspiração?
Embora estes não sejam oficialmente patrocinados pela Equipe Flight, eles podem dar ideias sobre como estruturar seus próprios projetos construídos com Flight!
- ASC REST API Spell Checker - Uma API REST leve para verificação ortográfica em árabe construída com FlightPHP e biblioteca ArPHP. Esta API fornece capacidades de verificação ortográfica de texto em árabe, incluindo detecção de palavras mal escritas e sugestões de correção.
- Eventify - Eventify é um app de página única que conecta organizadores de eventos com participantes. Construído com PHP (FlightPHP), JavaScript e MySQL, ele apresenta autenticação JWT, gerenciamento de eventos e documentação de API RESTful usando OpenAPI.
- Ivox Car Rental - Ivox Car Rental é uma aplicação web de aluguel de carros de página única e amigável para mobile construída com PHP (FlightPHP), JavaScript e MySQL. Ela suporta registro de usuários, navegação e reserva de carros, enquanto administradores podem gerenciar carros, usuários e reservas. O app apresenta uma API REST, autenticação JWT e um design responsivo para uma experiência de aluguel moderna.
- Decay - Flight v3 com HTMX e SleekDB tudo sobre zumbis! (Demo)
- Flight Example Blog - Flight v3 com Middleware, Controllers, Active Record e Latte.
- Flight CRUD RESTful API - Projeto simples de API CRUD usando o framework Flight, que fornece uma estrutura básica para novos usuários configurarem rapidamente uma aplicação PHP com operações CRUD e conectividade de banco de dados. O projeto demonstra como usar Flight para desenvolvimento de API RESTful, tornando-o uma ferramenta de aprendizado ideal para iniciantes e um kit inicial útil para desenvolvedores mais experientes.
- Flight School Management System - Flight v3
- Paste Bin with Comments - Flight v3
- Basic Skeleton App
- Example Wiki
- The IT-Innovator PHP Framework Application
- LittleEducationalCMS (Spanish)
- Italian Yellow Pages API
- Generic Content Management System (with....very little documentation)
- A tiny php framework based on Flight and medoo.
- Example MVC Application
- Production ready Flight Boilerplate - Framework de autenticação pronto para produção que economiza semanas de desenvolvimento. Recursos de segurança de nível empresarial: 2FA/TOTP, integração LDAP, SSO Azure, limitação de taxa inteligente, fingerprinting de sessão, proteção contra força bruta, painel de análise de segurança, logging de auditoria abrangente e controle de acesso baseado em papéis granular.
Quer Compartilhar Seu Próprio Exemplo?
Se você tiver um projeto que deseja compartilhar, por favor, envie um pull request para adicioná-lo a esta lista!
Install/install
Instruções de Instalação
Existem alguns pré-requisitos básicos antes de poder instalar o Flight. Nomeadamente, você precisará de:
- Instalar PHP no seu sistema
- Instalar Composer para a melhor experiência de desenvolvimento.
Instalação Básica
Se você estiver usando o Composer, execute o seguinte comando:
composer require flightphp/core
Isso colocará apenas os arquivos principais do Flight no seu sistema. Você precisará definir a estrutura do projeto, layout, dependências, configurações, autoloading, etc. Este método garante que nenhuma outra dependência além do Flight seja instalada.
Você também pode baixar os arquivos diretamente e extraí-los para o seu diretório web.
A instalação básica é perfeita para aprendizado, micro APIs e experimentos de copiar e colar. Para um layout de aplicativo completo que humanos e ferramentas de codificação de IA possam seguir da mesma forma, use o esqueleto recomendado abaixo.
Instalação Recomendada
É altamente recomendável começar com o aplicativo flightphp/skeleton para qualquer novo projeto. A instalação é muito fácil.
composer create-project flightphp/skeleton my-project/
cd my-project/
composer start
# banco de dados de exemplo opcional + demonstração de posts
php runway migrate
Essa etapa configura a estrutura do projeto, o autoloading PSR-4 do Composer, a configuração e ferramentas como Tracy, Extensões do Tracy e Runway. Ela também inclui o AGENTS.md na raiz (e cópias com escopo em app/) para que os assistentes de IA compartilhem um layout com você — veja IA e experiência de desenvolvimento.
O que o esqueleto oferece
project-root/
├── AGENTS.md # Fonte de verdade para IA / agente
├── SECURITY.md # Expectativas de segurança
├── .env.example # Segredos / overlays de deploy (copiado para .env)
├── public/index.php # Apenas entrada web
├── app/
│ ├── config/ # bootstrap, rotas, serviços, config_sample.php
│ ├── Controller/ # App\Controller\* (pasta PascalCase!)
│ ├── Middleware/ # App\Middleware\*
│ ├── Model/ # App\Model\* (ActiveRecord)
│ ├── Utils/ # Config, Env, DatabaseFactory
│ ├── commands/ # Comandos CLI do Runway
│ ├── views/ # Templates Twig (*.twig)
│ ├── cache/
│ └── log/
├── migrations/ # Migrações SQL (.sql / .mysql.sql)
└── tests/ # PHPUnit
Namespaces seguem o caso das pastas. O Composer mapeia "App\\": "app/", então:
| Caminho no disco | Namespace |
|---|---|
app/Controller/HomeController.php |
App\Controller\HomeController |
app/Middleware/… |
App\Middleware\… |
app/Model/… |
App\Model\… |
app/Utils/… |
App\Utils\… |
No Linux, app/controller/ não é o mesmo que app/Controller/. O autoloading diferencia maiúsculas de minúsculas — corresponda às pastas PascalCase do esqueleto. Detalhes: Autoloading.
Padrões da stack (novos projetos): views Twig, SimplePdo + ActiveRecord, Dice com injeção de Engine (prefira não usar Flight:: dentro das classes do aplicativo), SQLite opcional após php runway migrate.
O create-project normalmente copia app/config/config_sample.php → config.php e .env.example → .env quando presentes. As rotas ficam em app/config/routes.php; serviços e DI (injeção de dependências) ficam em app/config/services.php.
Documentação ↔ esqueleto: Estes documentos ensinam as APIs do Flight (muitas vezes com exemplos curtos de
Flight::). O esqueleto define a estrutura do aplicativo. Ao adicionar código emapp/, siga a árvore do esqueleto; use a documentação para nomes de métodos, opções e plugins.
Configure seu Servidor Web
Servidor de Desenvolvimento Embutido do PHP
Esta é de longe a maneira mais simples de começar. Você pode usar o servidor embutido para executar seu aplicativo e até mesmo usar SQLite como banco de dados (desde que o sqlite3 esteja instalado no seu sistema) sem precisar de quase nada! Basta executar o seguinte comando assim que o PHP estiver instalado:
php -S localhost:8000
# ou com o aplicativo esqueleto
composer start
Em seguida, abra seu navegador e acesse http://localhost:8000.
Se você quiser definir o diretório raiz de documentos do seu projeto como um diretório diferente (Ex: seu projeto é ~/myproject, mas sua raiz de documentos é ~/myproject/public/), você pode executar o seguinte comando estando no diretório ~/myproject:
php -S localhost:8000 -t public/
# com o aplicativo esqueleto, isso já está configurado
composer start
Em seguida, abra seu navegador e acesse http://localhost:8000.
Apache
Certifique-se de que o Apache já esteja instalado no seu sistema. Se não estiver, pesquise no Google como instalar o Apache no seu sistema.
Para o Apache, edite seu arquivo .htaccess com o seguinte:
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php [QSA,L]
Nota: Se você precisar usar o Flight em um subdiretório, adicione a linha
RewriteBase /subdir/logo apósRewriteEngine On.
Nota: Se você quiser proteger todos os arquivos do servidor, como um arquivo de banco de dados ou env. Coloque isto no seu arquivo
.htaccess:
RewriteEngine On
RewriteRule ^(.*)$ index.php
Nginx
Certifique-se de que o Nginx já esteja instalado no seu sistema. Se não estiver, pesquise no Google como instalar o Nginx no seu sistema.
Para o Nginx, adicione o seguinte à sua declaração de servidor:
server {
location / {
try_files $uri $uri/ /index.php;
}
}
Crie seu arquivo index.php
Se você estiver fazendo uma instalação básica, precisará de algum código para começar.
<?php
// Se você estiver usando o Composer, requisite o autoloader.
require 'vendor/autoload.php';
// se você não estiver usando o Composer, carregue o framework diretamente
// require 'flight/Flight.php';
// Em seguida, defina uma rota e atribua uma função para lidar com a solicitação.
Flight::route('/', function () {
echo 'hello world!';
});
// Finalmente, inicie o framework.
Flight::start();
Com o aplicativo esqueleto, a entrada pública apenas inicializa o aplicativo. As rotas são registradas em app/config/routes.php (normalmente [App\Controller\…::class, 'method'] para que o Dice possa injetar dependências). Serviços, Twig, SimplePdo e o contêiner estão conectados em app/config/services.php. Essa estrutura é intencional para que ferramentas de IA e humanos editem os mesmos lugares todas as vezes.
Instalando PHP
Se você já tem php instalado no seu sistema, pode pular estas instruções e ir para a seção de download.
macOS
Instalando o PHP usando Homebrew
-
Instale o Homebrew (se ainda não estiver instalado):
- Abra o Terminal e execute:
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
- Abra o Terminal e execute:
-
Instale o PHP:
- Instale a versão mais recente:
brew install php - Para instalar uma versão específica, por exemplo, PHP 8.1:
brew tap shivammathur/php brew install shivammathur/php/php@8.1
- Instale a versão mais recente:
-
Alterne entre versões do PHP:
- Desvincule a versão atual e vincule a versão desejada:
brew unlink php brew link --overwrite --force php@8.1 - Verifique a versão instalada:
php -v
- Desvincule a versão atual e vincule a versão desejada:
Windows 10/11
Instalando o PHP manualmente
-
Baixe o PHP:
- Visite PHP para Windows e baixe a versão mais recente ou uma versão específica (ex.: 7.4, 8.0) como um arquivo zip não thread-safe.
-
Extraia o PHP:
- Extraia o arquivo zip baixado para
C:\php.
- Extraia o arquivo zip baixado para
-
Adicione o PHP ao PATH do sistema:
- Vá em Propriedades do Sistema > Variáveis de Ambiente.
- Em Variáveis do sistema, encontre Path e clique em Editar.
- Adicione o caminho
C:\php(ou onde você extraiu o PHP). - Clique em OK para fechar todas as janelas.
-
Configure o PHP:
- Copie
php.ini-developmentparaphp.ini. - Edite o
php.inipara configurar o PHP conforme necessário (ex.: definindoextension_dir, habilitando extensões).
- Copie
-
Verifique a instalação do PHP:
- Abra o Prompt de Comando e execute:
php -v
- Abra o Prompt de Comando e execute:
Instalando Múltiplas Versões do PHP
-
Repita os passos acima para cada versão, colocando cada uma em um diretório separado (ex.:
C:\php7,C:\php8). -
Alterne entre as versões ajustando a variável PATH do sistema para apontar para o diretório da versão desejada.
Ubuntu (20.04, 22.04, etc.)
Instalando o PHP usando apt
-
Atualize as listas de pacotes:
- Abra o Terminal e execute:
sudo apt update
- Abra o Terminal e execute:
-
Instale o PHP:
- Instale a versão mais recente do PHP:
sudo apt install php - Para instalar uma versão específica, por exemplo, PHP 8.1:
sudo apt install php8.1
- Instale a versão mais recente do PHP:
-
Instale módulos adicionais (opcional):
- Por exemplo, para instalar suporte ao MySQL:
sudo apt install php8.1-mysql
- Por exemplo, para instalar suporte ao MySQL:
-
Alterne entre versões do PHP:
- Use
update-alternatives:sudo update-alternatives --set php /usr/bin/php8.1
- Use
-
Verifique a versão instalada:
- Execute:
php -v
- Execute:
Rocky Linux
Instalando o PHP usando yum/dnf
-
Habilite o repositório EPEL:
- Abra o Terminal e execute:
sudo dnf install epel-release
- Abra o Terminal e execute:
-
Instale o repositório Remi:
- Execute:
sudo dnf install https://rpms.remirepo.net/enterprise/remi-release-8.rpm sudo dnf module reset php
- Execute:
-
Instale o PHP:
- Para instalar a versão padrão:
sudo dnf install php - Para instalar uma versão específica, por exemplo, PHP 7.4:
sudo dnf module install php:remi-7.4
- Para instalar a versão padrão:
-
Alterne entre versões do PHP:
- Use o comando de módulo
dnf:sudo dnf module reset php sudo dnf module enable php:remi-8.0 sudo dnf install php
- Use o comando de módulo
-
Verifique a versão instalada:
- Execute:
php -v
- Execute:
Notas Gerais
- Para ambientes de desenvolvimento, é importante configurar as configurações do PHP de acordo com os requisitos do seu projeto.
- Ao alternar versões do PHP, certifique-se de que todas as extensões PHP relevantes estejam instaladas para a versão específica que você pretende usar.
- Reinicie seu servidor web (Apache, Nginx, etc.) após alternar versões do PHP ou atualizar configurações para aplicar as alterações.
Guides
Guias
Flight PHP é projetado para ser simples, mas poderoso, e nossos guias ajudarão você a construir aplicações do mundo real passo a passo. Esses tutoriais práticos guiam você por projetos completos para demonstrar como Flight pode ser usado de forma eficaz.
Guias Oficiais
Construindo um Blog
Aprenda como criar uma aplicação de blog funcional com Flight PHP. Este guia o leva por:
- Configurando uma estrutura de projeto
- Trabalhando com modelos usando Latte
- Implementando rotas para posts
- Armazenando e recuperando dados
- Lidando com submissões de formulários
- Tratamento básico de erros
Este tutorial é perfeito para iniciantes que querem ver como todas as peças se encaixam em uma aplicação real.
Testes Unitários e Princípios SOLID
Este guia cobre os fundamentos dos testes unitários em aplicações Flight PHP. Ele inclui:
- Configurando PHPUnit
- Escrevendo código testável usando princípios SOLID
- Simulando dependências
- Armadilhas comuns a evitar
- Escalando seus testes à medida que sua aplicação cresce Este tutorial é ideal para desenvolvedores que buscam melhorar a qualidade e a mantibilidade do código.
Guias Não Oficiais
Embora esses guias não sejam mantidos oficialmente pela equipe do Flight, eles são recursos valiosos criados pela comunidade. Eles cobrem vários tópicos e casos de uso, fornecendo insights adicionais sobre o uso do Flight PHP.
Creating a RESTful API with Flight Framework
Este guia o leva pela criação de uma API RESTful usando o framework Flight PHP. Ele cobre os básicos de configurar uma API, definir rotas e retornar respostas em JSON.
Building a Simple Blog
Este guia o leva pela criação de um blog básico usando o framework Flight PHP. Na verdade, ele tem 2 partes: uma para cobrir os básicos e a outra para cobrir tópicos mais avançados e refinamentos para um blog pronto para produção.
- Building a Simple Blog with Flight - Part 1 - Começando com um blog simples.
- Building a Simple Blog with Flight - Part 2 - Refinando o blog para produção.
Building a Pokémon API in PHP: A Beginner's Guide
Este guia divertido o leva pela criação de uma simples API de Pokémon usando Flight PHP. Ele cobre os básicos de configurar uma API, definir rotas e retornar respostas em JSON.
Contribuindo
Tem uma ideia para um guia? Encontrou um erro? Nós欢迎 contribuições! Nossos guias são mantidos no repositório de documentação do FlightPHP.
Se você construiu algo interessante com Flight e quer compartilhar como um guia, por favor, envie um pull request. Compartilhar seu conhecimento ajuda a comunidade do Flight a crescer.
Procurando por Documentação da API?
Se você está procurando informações específicas sobre os recursos e métodos principais do Flight, consulte a seção Aprenda de nossa documentação.