Learn

Aprenda Sobre Flight

Flight é um framework rápido, simples e extensível para PHP. É bastante versátil e pode ser usado para construir qualquer tipo de aplicativo web. Foi criado com simplicidade em mente e é escrito de uma forma que é fácil de entender e usar.

🚀 IA & Experiência do Desenvolvedor com Flight

Flight não é apenas rápido e simples — também foi projetado para ajudar você a codificar de forma mais inteligente e fazer mais, especialmente com a ajuda de ferramentas de IA modernas. Seja usando assistentes de codificação com IA, automatizando tarefas repetitivas ou apenas querendo aumentar sua produtividade, o baixo impacto e a estrutura direta do Flight o tornam uma alegria de usar junto com os fluxos de trabalho de devex e IA mais recentes.

Curioso sobre como começar? Explore nosso guia de IA & DevEx para dicas, ferramentas e exemplos do mundo real sobre como trabalhar mais rápido e de forma mais inteligente com Flight!

Conceitos Importantes do Framework

Por Que um Framework?

Aqui vai um artigo curto sobre por que você deve usar um framework. É uma boa ideia entender os benefícios de usar um framework antes de começar a usá-lo.

Além disso, um tutorial excelente foi criado por @lubiana. Embora não vá em detalhes sobre o Flight especificamente, este guia ajudará você a entender alguns dos principais conceitos ao redor 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.

Tópicos Principais

IA & Experiência do Desenvolvedor

Aprenda como o Flight funciona com ferramentas de IA e fluxos de trabalho de desenvolvedor modernos para ajudar você a codificar mais rápido e de forma mais inteligente.

Autoloading

Aprenda como autoloadar suas próprias classes em seu aplicativo.

Routing

Aprenda como gerenciar rotas para seu aplicativo web. Isso também inclui agrupar rotas, parâmetros de rota e middleware.

Middleware

Aprenda como usar middleware para filtrar solicitações e respostas em seu aplicativo.

Requests

Aprenda como lidar com solicitações e respostas em seu aplicativo.

Responses

Aprenda como enviar respostas para seus usuários.

Events

Aprenda como usar o sistema de eventos para adicionar eventos personalizados ao seu aplicativo.

HTML Templates

Aprenda como usar o motor de visualização integrado para renderizar seus templates HTML.

Security

Aprenda como proteger seu aplicativo de ameaças de segurança comuns.

Configuration

Aprenda como configurar o framework para seu aplicativo.

Extending Flight

Aprenda como estender o framework adicionando seus próprios métodos e classes.

Events and Filtering

Aprenda como usar o sistema de eventos para adicionar hooks aos seus métodos e métodos internos do framework.

Dependency Injection Container

Aprenda como usar contêineres de injeção de dependência (DIC) para gerenciar as dependências do seu aplicativo.

Framework API

Aprenda sobre os métodos principais do framework.

Migrating to v3

A compatibilidade com versões anteriores foi mantida na maior parte, mas há algumas mudanças que você deve conhecer ao migrar do v2 para o v3.

Troubleshooting

Há alguns problemas comuns que você pode encontrar ao usar o Flight. Esta página ajudará você a solucionar esses problemas.

Learn/stopping

Parando

Você pode parar o framework a qualquer momento chamando o método halt:

Flight::halt();

Você também pode especificar um código de status HTTP opcional e uma mensagem:

Flight::halt(200, 'Volto em breve...');

Chamar halt irá descartar qualquer conteúdo de resposta até esse ponto. Se você quiser parar o framework e exibir a resposta atual, use o método stop:

Flight::stop();

Learn/errorhandling

Manipulação de Erros

Erros e Exceções

Todos os erros e exceções são interceptados pelo Flight e passados para o método error. O comportamento padrão é enviar uma resposta genérica de HTTP 500 Erro Interno do Servidor com algumas informações de erro.

Você pode substituir esse comportamento para suas próprias necessidades:

Flight::map('error', function (Throwable $error) {
  // Lidar com o erro
  echo $error->getTraceAsString();
});

Por padrão, os erros não são registrados no servidor web. Você pode habilitar isso alterando a configuração:

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

Não Encontrado

Quando um URL não pode ser encontrado, o Flight chama o método notFound. O comportamento padrão é enviar uma resposta de HTTP 404 Não Encontrado com uma mensagem simples.

Você pode substituir esse comportamento para suas próprias necessidades:

Flight::map('notFound', function () {
  // Lidar com não encontrado
});

Learn/flight_vs_laravel

Comparação Entre Flight e Laravel

O que é Laravel?

Laravel é um framework completo que possui todas as funcionalidades e uma incrível ecossistema focado no desenvolvedor, mas com um custo em termos de desempenho e complexidade. O objetivo do Laravel é fornecer ao desenvolvedor o mais alto nível de produtividade e tornar as tarefas comuns fáceis. Laravel é uma ótima escolha para desenvolvedores que desejam construir aplicações web empresariais completas. Isso vem com alguns compromissos, especificamente em termos de desempenho e complexidade. Aprender o básico do Laravel pode ser fácil, mas ganhar proficiência no framework pode levar algum tempo.

Também existem tantos módulos do Laravel que os desenvolvedores frequentemente sentem que a única maneira de solucionar problemas é através desses módulos, quando na verdade você poderia usar apenas outra biblioteca ou escrever seu próprio código.

Prós Comparados ao Flight

Contras Comparados ao Flight

Learn/migrating_to_v3

Migração para v3

A compatibilidade com versões anteriores foi em grande parte mantida, mas há algumas mudanças das quais você deve estar ciente ao fazer a migração da v2 para a v3.

Comportamento do Buffer de Saída (3.5.0)

O buffering de saída é o processo pelo qual 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 enviá-la ao cliente.

Em uma aplicação MVC, o Controlador é o "gerente" e ele gerencia o que a visualização faz. Ter saída gerada fora do controlador (ou no caso do Flight, às vezes em uma função anônima) quebra o padrão MVC. Essa mudança visa estar mais alinhada com o padrão MVC e tornar o framework mais previsível e fácil de usar.

Na v2, o buffering de saída era tratado de uma maneira em que não estava consistentemente fechando seu próprio buffer de saída, o que tornava os 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 dando um echo no conteúdo fora de callables e controladores (por exemplo, em um hook), provavelmente terá problemas. Dar echo no conteúdo em hooks e antes do framework realmente executar pode ter funcionado no passado, mas não funcionará mais para frente.

Onde você pode ter problemas

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

// apenas um exemplo
define('START_TIME', microtime(true));

function hello() {
    echo 'Olá Mundo';
}

Flight::map('hello', 'hello');
Flight::after('hello', function(){
    // isso na verdade está bem
    echo '<p>Esta frase de Olá Mundo foi trazida para você pela letra "H"</p>';
});

Flight::before('start', function(){
    // coisas como esta causarão um erro
    echo '<html><head><title>Minha Página</title></head><body>';
});

Flight::route('/', function(){
    // isso na verdade está ok
    echo 'Olá Mundo';

    // Isso também deve estar ok
    Flight::hello();
});

Flight::after('start', function(){
    // isso causará um erro
    echo '<div>Sua página carregou em '.(microtime(true) - START_TIME).' segundos</div></body></html>';
});

Ativando o Comportamento de Renderização v2

Ainda é possível manter o seu código antigo exatamente como está sem a necessidade de uma reescrita para fazê-lo funcionar com a v3? Sim, é possível! Você pode ativar o comportamento de renderização v2 definindo a opção de configuração flight.v2.output_buffering como true. Isso permitirá que você continue usando o antigo comportamento de renderização, mas é recomendado corrigi-lo para o futuro. 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>Minha Página</title></head><body>';
});

// mais código 

Mudanças no Dispatcher (3.7.0)

Se você estava chamando 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. Dispatcher foi convertido para ser mais orientado a objeto, de modo que Contêineres de Injeção de Dependência possam ser usados de forma mais simples. Se você precisar invocar um método semelhante 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() (3.10.0)

O comportamento padrão antes da versão 3.10.0 era limpar tanto os cabeçalhos quanto o corpo da resposta. Isso foi alterado para limpar apenas o corpo da resposta. Se você precisar limpar também os cabeçalhos, você pode usar Flight::response()->clear().

Learn/configuration

Configuração

Você pode personalizar determinados comportamentos do Flight definindo valores de configuração através do método set.

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

Configurações Disponíveis

A seguir está uma lista de todas as configurações disponíveis:

Configuração do Carregador

Existe adicionalmente uma outra configuração para o carregador. Isso permitirá que você carregue classes com _ no nome da classe.

// Ativar o carregamento de classe com underscores
// Por padrão é verdadeiro
Loader::$v2ClassLoading = false;

Variáveis

O Flight permite que você salve variáveis para que possam ser usadas em qualquer lugar de sua aplicação.

// Salve sua variável
Flight::set('id', 123);

// Em outro lugar de sua aplicação
$id = Flight::get('id');

Para ver se uma variável foi definida, você pode fazer:

if (Flight::has('id')) {
  // Faça algo
}

Você pode limpar uma variável fazendo:

// Limpa a variável id
Flight::clear('id');

// Limpa todas as variáveis
Flight::clear();

O Flight também utiliza variáveis para fins de configuração.

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

Manipulação de Erros

Erros e Exceções

Todos os erros e exceções são capturados pelo Flight e passados para o método error. O comportamento padrão é enviar uma resposta genérica de HTTP 500 Erro interno do servidor com algumas informações de erro.

Você pode substituir esse comportamento conforme suas necessidades:

Flight::map('error', function (Throwable $error) {
  // Manipular erro
  echo $error->getTraceAsString();
});

Por padrão, os erros não são registrados no servidor web. Você pode ativá-los alterando a configuração:

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

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 de HTTP 404 Não encontrado com uma mensagem simples.

Você pode substituir esse comportamento conforme suas necessidades:

Flight::map('notFound', function () {
  // Lidar com não encontrado
});

Learn/ai

IA e Experiência do Desenvolvedor com Flight

Flight é tudo sobre ajudar você a construir mais rápido, mais inteligente e com menos atrito — especialmente ao trabalhar com ferramentas alimentadas por IA e fluxos de trabalho modernos de desenvolvedor. Esta página cobre como Flight facilita superalimentar seus projetos com IA e como começar com os novos assistentes de IA integrados ao framework e ao projeto de esqueleto.


IA-Pronta por Padrão: O Projeto de Esqueleto

O starter oficial flightphp/skeleton agora vem com instruções e configuração para assistentes de codificação de IA populares:

Essas ferramentas são pré-configuradas com instruções específicas do projeto, para que você e sua equipe possam obter a ajuda mais relevante e consciente do contexto ao codificar. Isso significa:

Por que isso importa?

Quando suas ferramentas de IA conhecem a intenção e as convenções do seu projeto, elas podem ajudar a estruturar recursos, refatorar código e evitar erros comuns — tornando você (e sua equipe) mais produtivo desde o primeiro dia.


Novos Comandos de IA no Núcleo do Flight

v3.16.0+

O núcleo do Flight agora inclui dois comandos CLI poderosos para ajudar a configurar e direcionar seu projeto com IA:

1. ai:init — Conecte-se ao Seu Provedor de LLM Favorito

Este comando o orienta na configuração de credenciais para um provedor de LLM (Modelo de Linguagem Grande), como OpenAI, Grok ou Anthropic (Claude).

Exemplo:

php runway ai:init

Você será solicitado a selecionar seu provedor, inserir sua chave de API e escolher um modelo. Isso facilita conectar seu projeto aos serviços de IA mais recentes — sem configuração manual necessária.

2. ai:generate-instructions — Instruções de Codificação com IA Consciente do Projeto

Este comando ajuda a criar ou atualizar instruções específicas do projeto para assistentes de codificação de IA. Ele faz algumas perguntas simples sobre seu projeto (como para o que ele serve, qual banco de dados você usa, tamanho da equipe etc.) e, em seguida, usa seu provedor de LLM para gerar instruções personalizadas.

Se você já tiver instruções, ele as atualizará para refletir as respostas que você fornecer. Essas instruções são escritas automaticamente em:

Exemplo:

php runway ai:generate-instructions

Por que isso é útil?

Com instruções atualizadas e específicas do projeto, suas ferramentas de IA podem:

  • Fornecer sugestões de código melhores
  • Entender as necessidades únicas do seu projeto
  • Ajudar a embarcar novos colaboradores mais rapidamente
  • Reduzir atrito e confusão à medida que seu projeto evolui

Não Apenas para Construir Aplicativos de IA

Embora você possa usar Flight para construir recursos alimentados por IA (como chatbots, APIs inteligentes ou integrações), o verdadeiro poder está em como Flight ajuda você a trabalhar melhor com ferramentas de IA como desenvolvedor. É sobre:


Saiba Mais e Comece

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:

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:

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

A segurança é um assunto importante quando se trata de aplicativos web. Você quer ter certeza de que seu aplicativo é seguro e que os dados dos seus usuários estão salvos. Flight oferece uma série de recursos para ajudá-lo a proteger seus aplicativos web.

Cabeçalhos

Os cabeçalhos HTTP são uma das maneiras mais fáceis de proteger seus aplicativos web. Você pode usar cabeçalhos para prevenir clickjacking, XSS e outros ataques. Existem várias maneiras de adicionar esses cabeçalhos ao seu aplicativo.

Dois ótimos sites para verificar a segurança dos seus cabeçalhos são securityheaders.com e observatory.mozilla.org.

Adicionar Manualmente

Você pode adicionar esses cabeçalhos manualmente usando o método header no objeto Flight\Response.

// Defina o cabeçalho X-Frame-Options para prevenir clickjacking
Flight::response()->header('X-Frame-Options', 'SAMEORIGIN');

// Defina o cabeçalho Content-Security-Policy para prevenir XSS
// Nota: este cabeçalho pode ficar muito complexo, então você vai querer
// consultar exemplos na internet para o seu aplicativo
Flight::response()->header("Content-Security-Policy", "default-src 'self'");

// Defina o cabeçalho X-XSS-Protection para prevenir XSS
Flight::response()->header('X-XSS-Protection', '1; mode=block');

// Defina o cabeçalho X-Content-Type-Options para prevenir sniffing MIME
Flight::response()->header('X-Content-Type-Options', 'nosniff');

// Defina o cabeçalho Referrer-Policy para controlar quanta informação de referenciador é enviada
Flight::response()->header('Referrer-Policy', 'no-referrer-when-downgrade');

// Defina o cabeçalho Strict-Transport-Security para forçar HTTPS
Flight::response()->header('Strict-Transport-Security', 'max-age=31536000; includeSubDomains; preload');

// Defina o cabeçalho Permissions-Policy para controlar quais recursos e APIs podem ser usados
Flight::response()->header('Permissions-Policy', 'geolocation=()');

Esses podem ser adicionados no topo dos seus arquivos bootstrap.php ou index.php.

Adicionar como um Filtro

Você também pode adicioná-los em um filtro/gatilho 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. Esta é uma boa maneira de manter seu código limpo e organizado.

// app/middleware/SecurityHeadersMiddleware.php

namespace app\middleware;

class SecurityHeadersMiddleware
{
    public function before(array $params): void
    {
        Flight::response()->header('X-Frame-Options', 'SAMEORIGIN');
        Flight::response()->header("Content-Security-Policy", "default-src 'self'");
        Flight::response()->header('X-XSS-Protection', '1; mode=block');
        Flight::response()->header('X-Content-Type-Options', 'nosniff');
        Flight::response()->header('Referrer-Policy', 'no-referrer-when-downgrade');
        Flight::response()->header('Strict-Transport-Security', 'max-age=31536000; includeSubDomains; preload');
        Flight::response()->header('Permissions-Policy', 'geolocation=()');
    }
}

// index.php ou onde quer que você tenha suas rotas
// FYI, este grupo de string vazia atua como um middleware global para
// todas as rotas. Claro que você poderia fazer a mesma coisa e adicionar
// isso apenas a rotas específicas.
Flight::group('', function(Router $router) {
    $router->get('/users', [ 'UserController', 'getUsers' ]);
    // mais rotas
}, [ new SecurityHeadersMiddleware() ]);

Cross Site Request Forgery (CSRF)

O Cross Site Request Forgery (CSRF) é um tipo de ataque onde um site malicioso pode fazer o navegador de um usuário enviar uma solicitação para seu site. Isso pode ser usado para realizar ações em seu site sem o conhecimento do usuário. O Flight não fornece um mecanismo de proteção CSRF embutido, mas você pode facilmente implementar o seu usando middleware.

Configuração

Primeiro, você precisa gerar um token CSRF e armazená-lo na sessão do usuário. Você pode então usar esse token em seus formulários e verificá-lo quando o formulário for enviado.

// Gere um token CSRF e armazene-o na sessão do usuário
// (supondo que você tenha criado um objeto de sessão e o anexado ao Flight)
// consulte a documentação da sessão para mais informações
Flight::register('session', \Ghostff\Session\Session::class);

// Você só precisa gerar um único token por sessão (para que funcione 
// em várias abas e solicitações para o mesmo usuário)
if(Flight::session()->get('csrf_token') === null) {
    Flight::session()->set('csrf_token', bin2hex(random_bytes(32)) );
}
<!-- Use o token CSRF em seu formulário -->
<form method="post">
    <input type="hidden" name="csrf_token" value="<?= Flight::session()->get('csrf_token') ?>">
    <!-- outros campos do formulário -->
</form>

Usando Latte

Você também pode definir uma função personalizada para exibir o token CSRF em seus templates Latte.

// Defina uma função personalizada para exibir o token CSRF
// Nota: A View foi configurada com Latte como o mecanismo de visualização
Flight::view()->addFunction('csrf', function() {
    $csrfToken = Flight::session()->get('csrf_token');
    return new \Latte\Runtime\Html('<input type="hidden" name="csrf_token" value="' . $csrfToken . '">');
});

E agora, em 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>

Curto e simples, certo?

Verificar o Token CSRF

Você pode verificar o token CSRF usando filtros de evento:

// Este middleware verifica se a solicitação é uma solicitação POST e, se for, verifica se o token CSRF é válido
Flight::before('start', function() {
    if(Flight::request()->method == 'POST') {

        // capture o token csrf dos valores do formulário
        $token = Flight::request()->data->csrf_token;
        if($token !== Flight::session()->get('csrf_token')) {
            Flight::halt(403, 'Token CSRF inválido');
            // ou para uma resposta JSON
            Flight::jsonHalt(['error' => 'Token CSRF inválido'], 403);
        }
    }
});

Ou você pode usar uma classe de middleware:

// app/middleware/CsrfMiddleware.php

namespace app\middleware;

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

// index.php ou onde quer que você tenha suas rotas
Flight::group('', function(Router $router) {
    $router->get('/users', [ 'UserController', 'getUsers' ]);
    // mais rotas
}, [ new CsrfMiddleware() ]);

Cross Site Scripting (XSS)

O Cross Site Scripting (XSS) é um tipo de ataque onde um site malicioso pode injetar código em seu site. A maioria dessas oportunidades vem de valores de formulário que seus usuários finais preencherão. Você nunca deve confiar na saída de seus usuários! Sempre assuma que todos eles são os melhores hackers do mundo. Eles podem injetar JavaScript ou HTML malicioso em sua página. Este código pode ser usado para roubar informações de seus usuários ou realizar ações em seu site. Usando a classe de visualização do Flight, você pode facilmente escapar da saída para prevenir ataques XSS.

// Vamos supor que o usuário é esperto e tenta usar isto como seu nome
$name = '<script>alert("XSS")</script>';

// Isso vai escapar a saída
Flight::view()->set('name', $name);
// Isso vai exibir: &lt;script&gt;alert(&quot;XSS&quot;)&lt;/script&gt;

// Se você usar algo como Latte registrado como sua classe de visualização, isso também escapará automaticamente.
Flight::view()->render('template', ['name' => $name]);

Injeção de SQL

A injeção de SQL é um tipo de ataque onde um usuário malicioso pode injetar código SQL em seu banco de dados. Isso pode ser usado para roubar informações de seu banco de dados ou realizar ações em seu banco de dados. Novamente, você deve nunca confiar na entrada de seus usuários! Sempre assuma que eles estão em busca de sangue. Você pode usar instruções preparadas em seus objetos PDO para prevenir a injeção de SQL.

// Supondo que você tenha Flight::db() registrado como seu objeto PDO
$statement = Flight::db()->prepare('SELECT * FROM users WHERE username = :username');
$statement->execute([':username' => $username]);
$users = $statement->fetchAll();

// Se você usar a classe PdoWrapper, isso pode ser facilmente feito em uma linha
$users = Flight::db()->fetchAll('SELECT * FROM users WHERE username = :username', [ 'username' => $username ]);

// Você pode fazer a mesma coisa com um objeto PDO com ? espaços reservados
$statement = Flight::db()->fetchAll('SELECT * FROM users WHERE username = ?', [ $username ]);

// Apenas prometa que você nunca NUNCA fará algo como isso...
$users = Flight::db()->fetchAll("SELECT * FROM users WHERE username = '{$username}' LIMIT 5");
// porque e se $username = "' OR 1=1; -- "; 
// Depois que a consulta é construída, ela 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 muito comum de injeção de SQL que retornará todos os usuários.

CORS

O Cross-Origin Resource Sharing (CORS) é um mecanismo que permite que muitos recursos (por exemplo, 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 tem funcionalidade embutida, mas isso pode ser facilmente tratado com um gancho para ser executado antes que o método Flight::start() seja chamado.

// app/utils/CorsUtil.php

namespace app\utils;

class CorsUtil
{
    public function set(array $params): void
    {
        $request = Flight::request();
        $response = Flight::response();
        if ($request->getVar('HTTP_ORIGIN') !== '') {
            $this->allowOrigins();
            $response->header('Access-Control-Allow-Credentials', 'true');
            $response->header('Access-Control-Max-Age', '86400');
        }

        if ($request->method === 'OPTIONS') {
            if ($request->getVar('HTTP_ACCESS_CONTROL_REQUEST_METHOD') !== '') {
                $response->header(
                    'Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, PATCH, OPTIONS, HEAD'
                );
            }
            if ($request->getVar('HTTP_ACCESS_CONTROL_REQUEST_HEADERS') !== '') {
                $response->header(
                    "Access-Control-Allow-Headers",
                    $request->getVar('HTTP_ACCESS_CONTROL_REQUEST_HEADERS')
                );
            }

            $response->status(200);
            $response->send();
            exit;
        }
    }

    private function allowOrigins(): void
    {
        // personalize seus hosts permitidos aqui.
        $allowed = [
            'capacitor://localhost',
            'ionic://localhost',
            'http://localhost',
            'http://localhost:4200',
            'http://localhost:8080',
            'http://localhost:8100',
        ];

        $request = Flight::request();

        if (in_array($request->getVar('HTTP_ORIGIN'), $allowed, true) === true) {
            $response = Flight::response();
            $response->header("Access-Control-Allow-Origin", $request->getVar('HTTP_ORIGIN'));
        }
    }
}

// index.php ou onde quer que você tenha suas rotas
$CorsUtil = new CorsUtil();

// Isso precisa ser executado antes que o start seja executado.
Flight::before('start', [ $CorsUtil, 'setupCors' ]);

Tratamento de Erros

Oculte detalhes de erro sensíveis na produção para evitar vazamento de informações para atacantes.

// No seu bootstrap.php ou index.php

// no flightphp/skeleton, isso está em app/config/config.php
$environment = ENVIRONMENT;
if ($environment === 'production') {
    ini_set('display_errors', 0); // Desabilitar a exibição de erros
    ini_set('log_errors', 1);     // Registrar 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, 'Acesso negado');

Sanitização de Entradas

Nunca confie na entrada do usuário. Sanitizá-la antes de processar para evitar que dados maliciosos sejam inseridos.


// Vamos supor uma solicitação $_POST com $_POST['input'] e $_POST['email']

// Sanitizar uma entrada de string
$clean_input = filter_var(Flight::request()->data->input, FILTER_SANITIZE_STRING);
// Sanitizar um email
$clean_email = filter_var(Flight::request()->data->email, FILTER_SANITIZE_EMAIL);

Hashing de Senhas

Armazene senhas de forma segura e verifique-as com segurança usando as funções embutidas do PHP.

$password = Flight::request()->data->password;
// Hash uma senha ao armazená-la (por exemplo, durante o registro)
$hashed_password = password_hash($password, PASSWORD_DEFAULT);

// Verifique uma senha (por exemplo, durante o login)
if (password_verify($password, $stored_hash)) {
    // A senha corresponde
}

Limitação de Taxa

Proteja-se contra ataques de força bruta limitando as taxas de solicitação com um cache.

// Supondo que você tenha flightphp/cache instalado e registrado
// Usando flightphp/cache em um middleware
Flight::before('start', function() {
    $cache = Flight::cache();
    $ip = Flight::request()->ip;
    $key = "rate_limit_{$ip}";
    $attempts = (int) $cache->retrieve($key);

    if ($attempts >= 10) {
        Flight::halt(429, 'Muitas solicitações');
    }

    $cache->set($key, $attempts + 1, 60); // Redefinir após 60 segundos
});

Conclusão

A segurança é um assunto importante e é fundamental garantir que seus aplicativos web sejam seguros. O Flight oferece uma série de recursos para ajudá-lo a proteger seus aplicativos web, mas é importante estar sempre alerta e garantir que você está fazendo tudo que pode para manter os dados dos seus usuários seguros. Sempre assuma o pior e nunca confie na entrada dos seus usuários. Sempre escape a saída e use instruções preparadas para evitar a injeção de SQL. Sempre use middleware para proteger suas rotas contra ataques CSRF e CORS. Se você fizer tudo isso, estará no caminho certo para construir aplicativos web seguros.

Learn/overriding

Substituição

O Flight permite que você substitua sua funcionalidade padrão para atender às suas próprias necessidades, sem ter que modificar nenhum código.

Por exemplo, quando o Flight não consegue corresponder a uma URL a uma rota, ele invoca o método notFound, que envia uma resposta genérica de HTTP 404. Você pode substituir esse comportamento usando o método map:

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

O Flight também permite que você substitua componentes principais do framework. Por exemplo, você pode substituir a classe do Roteador padrão pela sua própria classe personalizada:

// Registre sua classe personalizada
Flight::register('router', MyRouter::class);

// Quando o Flight carrega a instância do Roteador, ele carregará sua classe
$myrouter = Flight::router();

Métodos do framework como map e register, no entanto, não podem ser substituídos. Você receberá um erro se tentar fazê-lo.

Learn/routing

Roteamento

Nota: Quer entender mais sobre roteamento? Confira a página "why a framework?" para uma explicação mais detalhada.

O roteamento básico no Flight é feito ao combinar um padrão de URL com uma função de callback ou um array de uma classe e método.

Flight::route('/', function(){
    echo 'hello world!';
});

As rotas são combinadas na ordem em que são definidas. A primeira rota que combinar com uma solicitação será invocada.

Callbacks/Funções

O callback pode ser qualquer objeto que seja chamável. Então, você pode usar uma função regular:

function hello() {
    echo 'hello world!';
}

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

Classes

Você também pode usar um método estático de uma classe:

class Greeting {
    public static function hello() {
        echo 'hello world!';
    }
}

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

Ou criando um objeto primeiro e depois chamando o método:

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

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

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

Flight::route('/', [ $greeting, 'hello' ]);
// Você também pode fazer isso sem criar o objeto primeiro
// Nota: Nenhum argumento será injetado no construtor
Flight::route('/', [ 'Greeting', 'hello' ]);
// Além disso, você pode usar esta sintaxe mais curta
Flight::route('/', 'Greeting->hello');
// ou
Flight::route('/', Greeting::class.'->hello');

Injeção de Dependência via DIC (Container de Injeção de Dependência)

Se você quiser usar injeção de dependência via um container (PSR-11, PHP-DI, Dice, etc.), o único tipo de rotas onde isso está disponível é ou criando diretamente o objeto você mesmo e usando o container para criar seu objeto, ou você pode usar strings para definir a classe e o método a chamar. Você pode ir para a página Injeção de Dependência para mais informações.

Aqui está um exemplo rápido:

use flight\database\PdoWrapper;

// Greeting.php
class Greeting
{
    protected PdoWrapper $pdoWrapper;
    public function __construct(PdoWrapper $pdoWrapper) {
        $this->pdoWrapper = $pdoWrapper;
    }

    public function hello(int $id) {
        // faça algo com $this->pdoWrapper
        $name = $this->pdoWrapper->fetchField("SELECT name FROM users WHERE id = ?", [ $id ]);
        echo "Hello, world! My name is {$name}!";
    }
}

// index.php

// Configure o container com os parâmetros que você precisa
// Veja a página de Injeção de Dependência para mais informações sobre PSR-11
$dice = new \Dice\Dice();

// Não esqueça de reatribuir a variável com '$dice = '!!!!!
$dice = $dice->addRule('flight\database\PdoWrapper', [
    'shared' => true,
    'constructParams' => [ 
        'mysql:host=localhost;dbname=test', 
        'root',
        'password'
    ]
]);

// Registre o manipulador do container
Flight::registerContainerHandler(function($class, $params) use ($dice) {
    return $dice->create($class, $params);
});

// Rotas como de costume
Flight::route('/hello/@id', [ 'Greeting', 'hello' ]);
// ou
Flight::route('/hello/@id', 'Greeting->hello');
// ou
Flight::route('/hello/@id', 'Greeting::hello');

Flight::start();

Roteamento por Método

Por padrão, os padrões de rota são combinados com todos os métodos de solicitaçã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 isso é um método para obter variáveis, não para criar uma rota.
// Flight::post('/', function() { /* code */ });
// Flight::patch('/', function() { /* code */ });
// Flight::put('/', function() { /* code */ });
// Flight::delete('/', function() { /* code */ });

Você também pode mapear vários métodos para um único callback usando um delimitador |:

Flight::route('GET|POST /', function () {
  echo 'I received either a GET or a POST request.';
});

Além disso, você pode obter o objeto Router, que tem alguns métodos auxiliares para você usar:

$router = Flight::router();

// mapeia todos os métodos
$router->map('/', function() {
    echo 'hello world!';
});

// solicitação GET
$router->get('/users', function() {
    echo 'users';
});
// $router->post();
// $router->put();
// $router->delete();
// $router->patch();

Expressões Regulares

Você pode usar expressões regulares em suas rotas:

Flight::route('/user/[0-9]+', function () {
  // Isso irá combinar com /user/1234
});

Embora este método esteja disponível, é recomendado usar parâmetros nomeados ou parâmetros nomeados com expressões regulares, pois eles 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 callback. Isso é mais para a legibilidade da rota do que qualquer outra coisa. Por favor, veja a seção abaixo sobre ressalva importante.

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 irá combinar com /bob/123
  // Mas não irá combinar com /bob/12345
});

Nota: Combinar grupos de regex () com parâmetros posicionais não é suportado. :'(

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 callback é o que determina o que é passado para ela. Então, se você inverter a ordem dos parâmetros na função de callback, as variáveis também serão invertidas. Aqui está um exemplo:

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

E se você acessar a URL seguinte: /bob/123, a saída seria hello, 123 (bob)!. Por favor, tenha cuidado ao configurar suas rotas e funções de callback.

Parâmetros Opcionais

Você pode especificar parâmetros nomeados que são opcionais para combinação ao envolver segmentos em parênteses.

Flight::route(
  '/blog(/@year(/@month(/@day)))',
  function(?string $year, ?string $month, ?string $day) {
    // Isso irá combinar com as seguintes URLs:
    // /blog/2012/12/10
    // /blog/2012/12
    // /blog/2012
    // /blog
  }
);

Quaisquer parâmetros opcionais que não forem combinados serão passados como NULL.

Coringas

A combinação é feita apenas em segmentos individuais de URL. Se você quiser combinar vários segmentos, pode usar o curinga *.

Flight::route('/blog/*', function () {
  // Isso irá combinar com /blog/2000/02/01
});

Para rotear todas as solicitações para um único callback, você pode fazer:

Flight::route('*', function () {
  // Faça algo
});

Passagem

Você pode passar a execução para a próxima rota que combina retornando true da sua função de callback.

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 () {
  // Isso será chamado
});

Alias de Rotas

Você pode atribuir um alias a uma rota, para que a URL possa ser gerada dinamicamente mais tarde no seu código (como em um template, por exemplo).

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

// mais tarde no código em algum lugar
Flight::getUrl('user_view', [ 'id' => 5 ]); // irá retornar '/users/5'

Isso é especialmente útil se sua URL acontecer de mudar. No exemplo acima, digamos que users foi movido para /admin/users/@id em vez disso. Com aliasing no lugar, você não precisa mudar em nenhum lugar onde você referencia o alias, porque o alias agora retornará /admin/users/5 como no exemplo acima.

O alias de rota ainda funciona em grupos também:

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

// mais tarde no código em algum lugar
Flight::getUrl('user_view', [ 'id' => 5 ]); // irá retornar '/users/5'

Informações de Rota

Se você quiser inspecionar as informações da rota que combina, há 2 maneiras de fazer isso. Você pode usar a propriedade executedRoute ou pode solicitar que o objeto de rota seja passado para seu callback passando true como o terceiro parâmetro no método de rota. O objeto de rota sempre será o último parâmetro passado para sua função de callback.

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

  // Array de parâmetros nomeados
  $route->params;

  // Expressão regular de combinação
  $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 o que middleware é atribuído a isso
  $route->middleware;

  // Mostra o alias atribuído a esta rota
  $route->alias;
}, true);

Ou se você quiser inspecionar a última rota executada, você pode fazer:

Flight::route('/', function() {
  $route = Flight::router()->executedRoute;
  // Faça algo com $route
  // Array de métodos HTTP combinados
  $route->methods;

  // Array de parâmetros nomeados
  $route->params;

  // Expressão regular de combinação
  $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 o que middleware é atribuído a isso
  $route->middleware;

  // Mostra o alias atribuído a esta rota
  $route->alias;
});

Nota: A propriedade executedRoute só será definida após uma rota ter sido executada. Se você tentar acessá-la antes que uma rota tenha sido executada, ela será NULL. Você também pode usar executedRoute em middleware!

Agrupamento de Rotas

Pode haver momentos em que você deseja agrupar rotas relacionadas (como /api/v1). Você pode fazer isso usando o método group:

Flight::group('/api/v1', function () {
  Flight::route('/users', function () {
    // Combina com /api/v1/users
  });

  Flight::route('/posts', function () {
    // Combina com /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 do objeto abaixo
    Flight::route('GET /users', function () {
      // Combina com GET /api/v1/users
    });

    Flight::post('/posts', function () {
      // Combina com POST /api/v1/posts
    });

    Flight::put('/posts/1', function () {
      // Combina com PUT /api/v1/posts
    });
  });
  Flight::group('/v2', function () {

    // Flight::get() obtém variáveis, não define uma rota! Veja o contexto do objeto abaixo
    Flight::route('GET /users', function () {
      // Combina com GET /api/v2/users
    });
  });
});

Agrupamento com Contexto de Objeto

Você ainda pode usar agrupamento de rotas com o objeto Engine da seguinte forma:

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

  // use a variável $router
  $router->get('/users', function () {
    // Combina com GET /api/v1/users
  });

  $router->post('/posts', function () {
    // Combina com POST /api/v1/posts
  });
});

Agrupamento com Middleware

Você também pode atribuir middleware a um grupo de rotas:

Flight::group('/api/v1', function () {
  Flight::route('/users', function () {
    // Combina com /api/v1/users
  });
}, [ MyAuthMiddleware::class ]); // ou [ new MyAuthMiddleware() ] se você quiser usar uma instância

Veja mais detalhes na página group middleware.

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 ',
      'create' => 'GET /create',
      'store' => 'POST ',
      'show' => 'GET /@id',
      'edit' => 'GET /@id/edit',
      'update' => 'PUT /@id',
      'destroy' => 'DELETE /@id'
]

E seu controlador ficará assim:

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 runway executando php runway routes.

Personalizando Rotas de Recursos

Há algumas opções para configurar as rotas de recursos.

Base de Alias

Você pode configurar o aliasBase. Por padrão, o alias é a última parte da URL especificada. Por exemplo, /users/ resultaria em um aliasBase de users. Quando essas rotas são criadas, os aliases são users.index, users.create, etc. Se você quiser mudar o alias, defina o aliasBase para o valor desejado.

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

Only e Except

Você também pode especificar quais rotas você quer criar usando as opções only e except.

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

Essas são basicamente opções de lista branca e lista negra, para que você possa especificar quais rotas você quer 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 ] ]);

Streaming

Agora você pode transmitir respostas para o cliente usando o método streamWithHeaders(). Isso é útil para enviar arquivos grandes, processos de longa duração ou gerar respostas grandes. Transmitir uma rota é tratado de forma um pouco diferente de uma rota regular.

Nota: Respostas de streaming só estão disponíveis se você tiver flight.v2.output_buffering definido como false.

Stream com Cabeçalhos Manuais

Você pode transmitir uma resposta para o cliente usando o método stream() em uma rota. Se você fizer isso, você deve definir todos os métodos manualmente antes de enviar qualquer coisa para o cliente. Isso é feito com a função header() do PHP ou o método Flight::response()->setRealHeader().

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

    // obviamente você sanitizaria o caminho e o que seja.
    $fileNameSafe = basename($filename);

    // Se você tiver cabeçalhos adicionais para definir aqui após a rota ter sido executada
    // você deve defini-los antes que qualquer coisa seja ecoada.
    // Eles devem ser uma chamada bruta para a função header() ou 
    // uma chamada para Flight::response()->setRealHeader()
    header('Content-Disposition: attachment; filename="'.$fileNameSafe.'"');
    // ou
    Flight::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 você quiser
    header('Content-Length: '.filesize($filePath));

    // Transmita o arquivo para o cliente enquanto ele é lido
    readfile($filePath);

// Esta é a linha mágica aqui
})->stream();

Stream com Cabeçalhos

Você também pode usar o método streamWithHeaders() para definir os cabeçalhos antes de começar a transmitir.

Flight::route('/stream-users', function() {

    // você pode adicionar quaisquer cabeçalhos adicionais que quiser aqui
    // você só deve usar header() ou Flight::response()->setRealHeader()

    // no entanto, como você puxa seus dados, apenas como um 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 para o cliente
        ob_flush();
    }
    echo '}';

// Esta é como você definirá os cabeçalhos antes de começar a transmitir.
})->streamWithHeaders([
    'Content-Type' => 'application/json',
    'Content-Disposition' => 'attachment; filename="users.json"',
    // código de status opcional, padrão para 200
    'status' => 200
]);

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

Contras comparados ao Flight

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

Variáveis

O Flight permite que você salve variáveis para que possam ser usadas em qualquer lugar de sua aplicação.

// Salve sua variável
Flight::set('id', 123);

// Em outro lugar de sua aplicação
$id = Flight::get('id');

Para verificar se uma variável foi definida, você pode fazer:

if (Flight::has('id')) {
  // Faça algo
}

Você pode limpar uma variável fazendo:

// Limpa a variável id
Flight::clear('id');

// Limpa todas as variáveis
Flight::clear();

O Flight também usa variáveis para fins de configuração.

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

Learn/dependency_injection_container

Contentor de Injeção de Dependência

Introdução

O Contentor de Injeção de Dependência (CID) é uma ferramenta poderosa que permite gerenciar as dependências de sua aplicação. É um conceito-chave nos frameworks PHP modernos e é usado para gerenciar a instanciação e configuração de objetos. Alguns exemplos de bibliotecas CID são: Dice, Pimple, PHP-DI e league/container.

Um CID é uma forma sofisticada de dizer que permite criar e gerenciar suas classes em um local centralizado. Isso é útil quando você precisa passar o mesmo objeto para múltiplas classes (como seus controladores). Um exemplo simples pode ajudar a tornar isso mais claro.

Exemplo Básico

A maneira antiga de fazer as coisas poderia parecer assim:


require 'vendor/autoload.php';

// classe para gerenciar usuários no banco de dados
class UserController {

    protected PDO $pdo;

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

    public function view(int $id) {
        $stmt = $this->pdo->prepare('SELECT * FROM users WHERE id = :id');
        $stmt->execute(['id' => $id]);

        print_r($stmt->fetch());
    }
}

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

Flight::start();

Você pode ver a partir do código acima que estamos criando um novo objeto PDO e passando-o para nossa classe UserController. Isso é bom para uma aplicação pequena, mas à medida que a aplicação cresce, você descobrirá que está criando o mesmo objeto PDO em múltiplos lugares. É aqui que um CID se torna útil.

Aqui está o mesmo exemplo usando um CID (usando Dice):


require 'vendor/autoload.php';

// mesma classe que acima. Nada mudou
class UserController {

    protected PDO $pdo;

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

    public function view(int $id) {
        $stmt = $this->pdo->prepare('SELECT * FROM users WHERE id = :id');
        $stmt->execute(['id' => $id]);

        print_r($stmt->fetch());
    }
}

// criar um novo contentor
$container = new \Dice\Dice;
// não se esqueça de reatribuí-lo a si mesmo como abaixo!
$container = $container->addRule('PDO', [
    // shared significa que o mesmo objeto será retornado toda vez
    'shared' => true,
    'constructParams' => ['mysql:host=localhost;dbname=test', 'user', 'pass' ]
]);

// Isso registra o manipulador do contentor para que o Flight saiba usá-lo.
Flight::registerContainerHandler(function($class, $params) use ($container) {
    return $container->create($class, $params);
});

// agora podemos usar o contentor para criar nosso UserController
Flight::route('/user/@id', [ 'UserController', 'view' ]);
// ou alternativamente você pode definir a rota assim
Flight::route('/user/@id', 'UserController->view');
// ou
Flight::route('/user/@id', 'UserController::view');

Flight::start();

Aposto que você pode estar pensando que houve muito código extra adicionado ao exemplo. A magia acontece quando você tem outro controlador que precisa do objeto PDO.


// Se todos os seus controladores têm um construtor que precisa de um objeto PDO
// cada uma das rotas abaixo terá automaticamente ele injetado!!!
Flight::route('/empresa/@id', 'CompanyController->view');
Flight::route('/organizacao/@id', 'OrganizationController->view');
Flight::route('/categoria/@id', 'CategoryController->view');
Flight::route('/configuracoes', 'SettingsController->view');

O benefício adicional de utilizar um CID é que os testes unitários se tornam muito mais fáceis. Você pode criar um objeto falso e passá-lo para sua classe. Isso é um grande benefício ao escrever testes para sua aplicação!

PSR-11

O Flight também pode usar qualquer contentor compatível com o PSR-11. Isso significa que você pode usar qualquer contentor que implemente a interface PSR-11. Aqui está um exemplo usando o contentor PSR-11 da League:


require 'vendor/autoload.php';

// mesma classe UserController que acima

$container = new \League\Container\Container();
$container->add(UserController::class)->addArgument(PdoWrapper::class);
$container->add(PdoWrapper::class)
    ->addArgument('mysql:host=localhost;dbname=test')
    ->addArgument('user')
    ->addArgument('pass');
Flight::registerContainerHandler($container);

Flight::route('/user', [ 'UserController', 'view' ]);

Flight::start();

Embora isso possa ser um pouco mais verboso do que o exemplo anterior com Dice, ainda faz o trabalho com os mesmos benefícios!

Manipulador CID Personalizado

Você também pode criar seu próprio manipulador CID. Isso é útil se você tiver um contentor personalizado que deseja usar que não seja PSR-11 (Dice). Veja o exemplo básico de como fazer isso.

Além disso, existem alguns padrões úteis que facilitarão sua vida ao usar o Flight.

Instância do Motor

Se você estiver usando a instância Engine em seus controladores/funções intermediárias, aqui está como você configuraria:


// Em algum lugar do seu arquivo de inicialização
$engine = Flight::app();

$container = new \Dice\Dice;
$container = $container->addRule('*', [
    'substitutions' => [
        // Aqui é onde você passa a instância
        Engine::class => $engine
    ]
]);

$engine->registerContainerHandler(function($class, $params) use ($container) {
    return $container->create($class, $params);
});

// Agora você pode usar a instância do Engine em seus controladores/funções intermediárias

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

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

Adicionando Outras Classes

Se você tem outras classes que deseja adicionar ao contentor, com Dice é fácil, pois elas serão automaticamente resolvidas pelo contentor. Aqui está um exemplo:


$container = new \Dice\Dice;
// Se você não precisa injetar nada em sua classe
// você não precisa definir nada!
Flight::registerContainerHandler(function($class, $params) use ($container) {
    return $container->create($class, $params);
});

class MinhaClassePersonalizada {
    public function parseCoisa() {
        return 'coisa';
    }
}

class UserController {

    protected MyCustomClass $MyCustomClass;

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

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

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

Learn/middleware

Middleware de Rota

Flight suporta middleware de rota e grupo de rota. Middleware é uma função que é executada antes (ou depois) do callback da rota. Esta é 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.

Middleware Básica

Aqui vai um exemplo básico:

// Se você fornecer apenas uma função anônima, ela será executada antes do callback da rota. 
// não há funções de middleware "after" exceto para classes (veja abaixo)
Flight::route('/path', function() { echo ' Here I am!'; })->addMiddleware(function() {
    echo 'Middleware first!';
});

Flight::start();

// Isso vai exibir "Middleware first! Here I am!"

Há algumas notas muito importantes sobre middleware que você deve saber antes de usá-las:

Classes de Middleware

Middleware também pode 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 vai exibir "Middleware first! Here I am! Middleware last!"

Tratamento de Erros de Middleware

Digamos que você tenha um middleware de autenticação e deseja redirecionar o usuário para uma página de login se ele não estiver autenticado. Você tem algumas opções à sua disposição:

  1. Você pode retornar false da função de middleware e Flight vai automaticamente retornar um erro 403 Forbidden, mas sem personalização.
  2. Você pode redirecionar o usuário para uma página de login usando Flight::redirect().
  3. Você pode criar um erro personalizado dentro do middleware e parar a execução da rota.

Exemplo Básico

Aqui vai um exemplo simples de return false;:

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

        // como é true, tudo continua normalmente
    }
}

Exemplo de Redirecionamento

Aqui vai um exemplo de redirecionar o usuário para uma página de login:

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

Exemplo de Erro Personalizado

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()->headers['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.']);
        }
    }
}

Agrupando Middleware

Você pode adicionar um grupo de rotas, 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 Autenticação para verificar a chave da API no cabeçalho.


// adicionado no final do método group
Flight::group('/api', function() {

    // Esta rota "vazia" vai realmente corresponder a /api
    Flight::route('', function() { echo 'api'; }, false, 'api');
    // Isso vai corresponder a /api/users
    Flight::route('/users', function() { echo 'users'; }, false, 'users');
    // Isso vai 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 group
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

Learn/filtering

Filtragem

O Flight permite que você filtre métodos antes e depois de serem chamados. Não há ganchos predefinidos que você precise memorizar. Você pode filtrar qualquer um dos métodos padrão do framework, bem como quaisquer métodos personalizados que você tenha mapeado.

Uma função de filtro parece com isso:

function (array &$params, string &$output): bool {
  // Código do filtro
}

Usando as variáveis passadas, você pode manipular os parâmetros de entrada e/ou a saída.

Você pode ter um filtro sendo executado antes de um método fazendo:

Flight::before('start', function (array &$params, string &$output): bool {
  // Faça algo
});

Você pode ter um filtro sendo executado após um método fazendo:

Flight::after('start', function (array &$params, string &$output): bool {
  // Faça algo
});

Você pode adicionar quantos filtros quiser a qualquer método. Eles serão chamados na ordem em que são declarados.

Aqui está um exemplo do processo de filtragem:

// Mapeie um método personalizado
Flight::map('hello', function (string $name) {
  return "Olá, $name!";
});

// Adicione um filtro antes
Flight::before('hello', function (array &$params, string &$output): bool {
  // Manipule o parâmetro
  $params[0] = 'Fred';
  return true;
});

// Adicione um filtro após
Flight::after('hello', function (array &$params, string &$output): bool {
  // Manipule a saída
  $output .= " Tenha um bom dia!";
  return true;
});

// Invoque o método personalizado
echo Flight::hello('Bob');

Isso deve exibir:

Olá Fred! Tenha um bom dia!

Se você definiu vários filtros, você pode interromper a cadeia retornando false em qualquer uma de suas funções de filtro:

Flight::before('start', function (array &$params, string &$output): bool {
  echo 'um';
  return true;
});

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

  // Isso irá encerrar a cadeia
  return false;
});

// Isso não será chamado
Flight::before('start', function (array &$params, string &$output): bool {
  echo 'três';
  return true;
});

Observação, métodos principais como map e register não podem ser filtrados porque são chamados diretamente e não invocados dinamicamente.

Learn/requests

Pedidos

Flight encapsula o pedido HTTP em um único objeto, que pode ser acessado da seguinte forma:

$request = Flight::request();

Casos de Uso Típicos

Quando você está trabalhando com um pedido em uma aplicação web, geralmente você deseja extrair um cabeçalho, ou um parâmetro $_GET ou $_POST, ou talvez até o corpo bruto do pedido. Flight fornece uma interface simples para fazer todas essas coisas.

Aqui está um exemplo para obter um parâmetro da string de consulta:

Flight::route('/search', function(){
    $keyword = Flight::request()->query['keyword'];
    echo "You are searching for: $keyword";
    // consultar um banco de dados ou algo mais com o $keyword
});

Aqui está um exemplo de talvez um formulário com método POST:

Flight::route('POST /submit', function(){
    $name = Flight::request()->data['name'];
    $email = Flight::request()->data['email'];
    echo "You submitted: $name, $email";
    // salvar em um banco de dados ou algo mais com o $name e $email
});

Propriedades do Objeto de Pedido

O objeto de pedido fornece as seguintes propriedades:

Você pode acessar as propriedades query, data, cookies e files como arrays ou objetos.

Então, para obter um parâmetro da string de consulta, você pode fazer:

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

Ou você pode fazer:

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

Corpo Bruto do Pedido

Para obter o corpo bruto do pedido HTTP, por exemplo ao lidar com pedidos PUT, você pode fazer:

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

Entrada JSON

Se você enviar um pedido com o tipo application/json e os dados {"id": 123}, ele estará disponível na propriedade data:

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

$_GET

Você pode acessar o array $_GET via a propriedade query:

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

$_POST

Você pode acessar o array $_POST via a propriedade data:

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

$_COOKIE

Você pode acessar o array $_COOKIE via a propriedade cookies:

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

$_SERVER

Há um atalho disponível para acessar o array $_SERVER via o método getVar():

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

Acessando Arquivos Enviados via $_FILES

Você pode acessar arquivos enviados via a propriedade files:

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

Processamento de Envios de Arquivos (v3.12.0)

Você pode processar envios de arquivos usando o framework com alguns métodos auxiliares. Basicamente, isso se resume a puxar os dados do arquivo do pedido 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 vários 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 envios de arquivos. Sempre valide o tipo de extensões que você permitirá ser enviadas, mas você também deve validar os "bytes mágicos" do arquivo para garantir que ele seja realmente o tipo de arquivo que o usuário alega que é. Há articles and libraries disponíveis para ajudar com isso.

Cabeçalhos do Pedido

Você pode acessar cabeçalhos do pedido 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();

Corpo do Pedido

Você pode acessar o corpo bruto do pedido usando o método getBody():

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

Método do Pedido

Você pode acessar o método do pedido usando a propriedade method ou o método getMethod():

$method = Flight::request()->method; // na verdade, chama 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.

URLs do Pedido

Há alguns métodos auxiliares para juntar partes de uma URL para sua conveniência.

URL Completa

Você pode acessar a URL completa do pedido 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():

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

Análise de Consulta

Você pode passar uma URL para o método parseQuery() para analisar a string de consulta em um array associativo:

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

Learn/frameworkmethods

# Métodos do Framework

Flight é projetado para ser fácil de usar e entender. O seguinte é o conjunto completo
de métodos para o framework. Consiste em métodos principais, que são métodos estáticos regulares,
e métodos extensíveis, que são métodos mapeados que podem ser filtrados
ou substituídos.

## Métodos Principais

```php
Flight::map(string $name, callable $callback, bool $pass_route = false) // Cria um método de framework personalizado.
Flight::register(string $name, string $class, array $params = [], ?callable $callback = null) // Registra uma classe a um método de framework.
Flight::before(string $name, callable $callback) // Adiciona um filtro antes de um método de framework.
Flight::after(string $name, callable $callback) // Adiciona um filtro após um método de framework.
Flight::path(string $path) // Adiciona um caminho para carregar classes automaticamente.
Flight::get(string $key) // Obtém uma variável.
Flight::set(string $key, mixed $value) // Define uma variável.
Flight::has(string $key) // Verifica se uma variável está definida.
Flight::clear(array|string $key = []) // Limpa uma variável.
Flight::init() // Inicializa o framework com suas configurações padrão.
Flight::app() // Obtém a instância do objeto de aplicativo

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) // Mapeia um padrão de URL para um retorno de chamada.
Flight::group(string $pattern, callable $callback) // Cria agrupamento para urls, o padrão deve ser uma string.
Flight::redirect(string $url, int $code) // Redireciona para outra URL.
Flight::render(string $file, array $data, ?string $key = null) // Renderiza um arquivo de modelo.
Flight::error(Throwable $error) // Envia uma resposta HTTP 500.
Flight::notFound() // Envia uma resposta HTTP 404.
Flight::etag(string $id, string $type = 'string') // Realiza o cache do HTTP ETag.
Flight::lastModified(int $time) // Realiza o cache do HTTP de última modificação.
Flight::json(mixed $data, int $code = 200, bool $encode = true, string $charset = 'utf8', int $option) // Envia uma resposta JSON.
Flight::jsonp(mixed $data, string $param = 'jsonp', int $code = 200, bool $encode = true, string $charset = 'utf8', int $option) // Envia uma resposta JSONP.

Quaisquer métodos personalizados adicionados com map e register também podem ser filtrados.

Learn/api

Métodos da API do Framework

Flight foi projetado para ser fácil de usar e entender. O seguinte é o conjunto completo de métodos para o framework. Ele consiste em métodos principais, que são métodos estáticos regulares, e métodos extensíveis, que são métodos mapeados que podem ser filtrados ou substituídos.

Métodos Principais

Esses métodos são essenciais para o framework e não podem ser substituídos.

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) // Cancela o registro de uma classe a 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 autoloading 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 mecanismo 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 as configurações padrão.
Flight::app() // Obtém a instância do objeto da 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 a um callback.
Flight::post(string $pattern, callable $callback, bool $pass_route = false, string $alias = '') // Mapeia um padrão de URL de requisição POST a um callback.
Flight::put(string $pattern, callable $callback, bool $pass_route = false, string $alias = '') // Mapeia um padrão de URL de requisição PUT a um callback.
Flight::patch(string $pattern, callable $callback, bool $pass_route = false, string $alias = '') // Mapeia um padrão de URL de requisição PATCH a um callback.
Flight::delete(string $pattern, callable $callback, bool $pass_route = false, string $alias = '') // Mapeia um padrão de URL de requisição DELETE a um callback.
Flight::group(string $pattern, callable $callback) // Cria agrupamentos para URLs, o padrão deve ser uma string.
Flight::getUrl(string $name, array $params = []) // Gera uma URL com base em um alias de rota.
Flight::redirect(string $url, int $code) // Redireciona para outra URL.
Flight::download(string $filePath) // Faz o download de 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 caching HTTP de ETag.
Flight::lastModified(int $time) // Realiza caching HTTP do último modificado.
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.

Qualquer método personalizado adicionado com map e register também pode ser filtrado. Para exemplos de como mapear esses métodos, consulte o guia Extending Flight.

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:

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:

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:

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

Caching HTTP

Voo fornece suporte embutido para o caching de nível HTTP. Se a condição de caching for atendida, Voo retornará uma resposta 304 Not Modified HTTP. Da próxima vez que o cliente solicitar o mesmo recurso, eles serão convidados a usar sua versão em cache localmente.

Última Modificação

Você pode usar o método lastModified e passar um carimbo de data/hora UNIX para definir a data e hora em que a página foi modificada pela última vez. O cliente continuará a usar seu cache até que o valor da última modificação seja alterado.

Flight::route('/noticias', function () {
  Flight::lastModified(1234567890);
  echo 'Este conteúdo será armazenado em cache.';
});

ETag

O caching ETag é semelhante ao Última Modificação, exceto que você pode especificar qualquer ID que desejar para o recurso:

Flight::route('/noticias', function () {
  Flight::etag('meu-id-único');
  echo 'Este conteúdo será armazenado em cache.';
});

Tenha em mente que chamar lastModified ou etag definirá e verificará o valor do cache. Se o valor do cache for o mesmo entre as solicitações, Voo enviará imediatamente uma resposta HTTP 304 e interromperá o processamento.

Learn/responses

Respostas

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. Às vezes, você pode acessar o objeto Response diretamente, mas na maioria das vezes, você usará a instância do Flight para enviar uma resposta.

Enviando uma Resposta Básica

Flight usa ob_start() para bufferizar a saída. Isso significa que você pode usar echo ou print para enviar uma resposta ao usuário e o Flight capturará e 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 às vezes é útil quando você precisa
    Flight::response()->write("Hello, World!");

    // se você quiser recuperar o corpo que você definiu neste ponto
    // você pode fazer assim
    $body = Flight::response()->getBody();
});

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, você pode usar o método status sem argumentos:

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

Definindo um Corpo de Resposta

Você pode definir o corpo da resposta usando o método write, no entanto, se você usar echo ou print, isso será capturado e enviado como o corpo da resposta via bufferização de saída.

Flight::route('/', function() {
    Flight::response()->write("Hello, World!");
});

// o mesmo que

Flight::route('/', function() {
    echo "Hello, World!";
});

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

Executando uma Callback no Corpo da Resposta

Você pode executar uma 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á o gzip de todas as respostas para qualquer rota
Flight::response()->addResponseBodyCallback(function($body) {
    return gzencode($body, 9);
});

Você pode adicionar várias callbacks e elas serão executadas na ordem em que foram adicionadas. Como isso pode aceitar qualquer callable, ele pode aceitar um array de classe [ $class, 'method' ], um closure $strReplace = function($body) { str_replace('hi', 'there', $body); };, ou um nome de função 'minify' se você tiver uma função para minimizar seu código HTML, por exemplo.

Nota: As callbacks de rota não funcionarão se você estiver usando a opção de configuração flight.v2.output_buffering.

Callback para Rota Específica

Se você quiser que isso se aplique apenas a uma rota específica, você pode adicionar a callback na rota em si:

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

    // Isso fará o gzip apenas da resposta para esta rota
    Flight::response()->addResponseBodyCallback(function($body) {
        return gzencode($body, 9);
    });
});

Opção de Middleware

Você também pode usar middleware para aplicar a callback a todas as rotas via middleware:

// MinifyMiddleware.php
class MinifyMiddleware {
    public function before() {
        // Aplicar a callback aqui no objeto response().
        Flight::response()->addResponseBodyCallback(function($body) {
            return $this->minify($body);
        });
    }

    protected function minify(string $body): string {
        // minimizar o corpo de alguma forma
        return $body;
    }
}

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

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 simples
Flight::route('/', function() {
    Flight::response()->header('Content-Type', 'text/plain');
    // ou
    Flight::response()->setHeader('Content-Type', 'text/plain');
    echo "Hello, World!";
});

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::json(['id' => 123]);

Nota: Por padrão, Flight enviará um cabeçalho Content-Type: application/json com a resposta. Ele também usará as constantes JSON_THROW_ON_ERROR e JSON_UNESCAPED_SLASHES ao 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 habilitar a impressão bonita:

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

Se você estiver alterando opções passadas para Flight::json() e quiser uma sintaxe mais simples, você pode remapear o método JSON:

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

// E agora pode ser usado assim
Flight::json(['id' => 123], 200, JSON_PRETTY_PRINT);

JSON e Parar 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 algum tipo de autorização e, se o usuário não estiver autorizado, pode enviar uma resposta JSON imediatamente, limpar o conteúdo do corpo existente e parar a execução.

Flight::route('/users', function() {
    $authorized = someAuthorizationCheck();
    // Verifique se o usuário está autorizado
    if($authorized === false) {
        Flight::jsonHalt(['error' => 'Unauthorized'], 401);
    }

    // Continue com o resto da rota
});

Antes da v3.10.0, você teria que fazer algo assim:

Flight::route('/users', function() {
    $authorized = someAuthorizationCheck();
    // Verifique se o usuário está autorizado
    if($authorized === false) {
        Flight::halt(401, json_encode(['error' => 'Unauthorized']));
    }

    // Continue com o resto da rota
});

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 será padronizado para jsonp.

Redirecionar para outra URL

Você pode redirecionar a solicitação atual usando o método redirect() e passando uma nova URL:

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

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', 401);

Parando

Você pode parar o framework em qualquer ponto chamando o método halt:

Flight::halt();

Você também pode especificar um código de status HTTP opcional e uma mensagem:

Flight::halt(200, 'Be right back...');

Chamar halt descartará qualquer conteúdo de resposta até aquele ponto. Se você quiser parar o framework e emitir a resposta atual, use o método stop:

Flight::stop($httpStatusCode = null);

Nota: Flight::stop() tem alguns comportamentos estranhos, como emitir a resposta, mas continuar executando seu script. Você pode usar exit ou return após chamar Flight::stop() para impedir a execução adicional, mas é geralmente recomendado usar Flight::halt().

Limpando Dados de Resposta

Você pode limpar o corpo e os cabeçalhos da resposta 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 como 200.

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

Limpando Apenas o Corpo da Resposta

Se você quiser limpar apenas o corpo da resposta, você pode usar o método clearBody():

// 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 incentivados a usar sua versão local em cache.

Cache no Nível de Rota

Se você quiser cachear toda a sua resposta, você pode usar o método cache() e passar o tempo para cachear.

// Isso cacheará a resposta por 5 minutos
Flight::route('/news', function () {
  Flight::response()->cache(time() + 300);
  echo 'This content will be cached.';
});

// Como alternativa, você pode usar uma string que você 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 quiser para o recurso:

Flight::route('/news', function () {
  Flight::etag('my-unique-id');
  echo 'This content will be cached.';
});

Lembre-se de 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 baixar um arquivo. Você pode usar o método download e passar o caminho.

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

Learn/frameworkinstance

Instância do Framework

Em vez de executar o Flight como uma classe estática global, você pode executá-lo opcionalmente como uma instância de objeto.

require 'flight/autoload.php';

$app = Flight::app();

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

$app->start();

Portanto, em vez de chamar o método estático, você chamaria o método de instância com o mesmo nome no objeto Engine.

Learn/redirects

Redirecionamentos

Você pode redirecionar a solicitação atual usando o método redirect e passando uma nova URL:

Flight::redirect('/novo/local');

Por padrão, o Flight envia um código de status HTTP 303. Você pode opcionalmente definir um código personalizado:

Flight::redirect('/novo/local', 401);

Learn/events

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

O Flight PHP introduz um sistema de eventos leve e intuitivo que permite registrar e disparar eventos personalizados em seu aplicativo. Com a adição de Flight::onEvent() e Flight::triggerEvent(), você pode agora se conectar a momentos chave do ciclo de vida do seu app ou definir seus próprios eventos para tornar seu código mais modular e extensível. Esses métodos fazem parte dos métodos mapeáveis do Flight, o que significa que você pode sobrescrever seu comportamento para atender às suas necessidades.

Este guia cobre tudo o que você precisa saber para começar com eventos, incluindo por que eles são valiosos, como usá-los e exemplos práticos para ajudar iniciantes a entenderem seu poder.

Por que Usar Eventos?

Os eventos permitem separar diferentes partes do seu aplicativo 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 um app de blog:

Sem eventos, você enfiaria tudo isso em uma única função. Com eventos, você pode dividir: uma parte salva o comentário, outra dispara um evento como 'comment.posted', e ouvintes separados lidam com o email e o registro. Isso mantém seu código mais limpo e permite adicionar ou remover recursos (como notificações) sem tocar na lógica principal.

Usos Comuns

Registrando Ouvintes de Eventos

Para ouvir um evento, use Flight::onEvent(). Esse método permite definir o que deve acontecer quando um evento ocorre.

Sintaxe

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

Como Funciona

Você "se inscreve" em um evento informando ao Flight o que fazer quando ele acontece. O callback pode aceitar argumentos passados do disparo do evento.

O sistema de eventos do 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 aquele 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 "Welcome back, $username!";
});

Aqui, quando o evento 'user.login' é disparado, ele cumprimenta o usuário pelo nome.

Pontos Chave

Disparando Eventos

Para fazer um evento acontecer, use Flight::triggerEvent(). Isso informa ao Flight para executar todos os ouvintes registrados para aquele evento, passando qualquer dado que você fornecer.

Sintaxe

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

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 resultará na saída: Welcome back, alice!.

Pontos Chave

Registrando Ouvintes de Eventos

...

Parando Ouvintes Adicionais: Se um ouvinte retornar false, nenhum ouvinte adicional para aquele 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 desejam 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) {
    // Registrar cada registro de evento
    error_log("New event listener added for: $event");
    // Chamar 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?

Onde Colocar Seus Eventos

Como iniciante, você pode se perguntar: onde eu registro todos esses eventos no meu app? A simplicidade do Flight significa que não há 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 seu app cresce. Aqui estão algumas opções práticas e melhores práticas, adaptadas à natureza leve do Flight:

Opção 1: No Seu Principal index.php

Para apps pequenos 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 lugar, o que é bom quando a simplicidade é a prioridade.

require 'vendor/autoload.php';

// Registrar eventos
Flight::onEvent('user.login', function ($username) {
    error_log("$username logged in at " . date('Y-m-d H:i:s'));
});

// Definir rotas
Flight::route('/login', function () {
    $username = 'bob';
    Flight::triggerEvent('user.login', $username);
    echo "Logged in!";
});

Flight::start();

Opção 2: Um Arquivo Separado events.php

Para um app um pouco maior, considere mover os registros de eventos para um arquivo dedicado como app/config/events.php. Inclua esse arquivo no seu index.php antes das rotas. Isso imita como as rotas são frequentemente organizadas em app/config/routes.php em projetos do 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();

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 a uma parte do seu app.

Flight::route('/signup', function () {
    // Registrar 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!";
});

Melhor Prática para o Flight

Dica: Agrupe por Propósito

Em events.php, agrupe eventos relacionados (ex.: todos os eventos de usuário 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) {
    unset($_SESSION['pages'][$pageId]);
});

Essa estrutura escala bem e permanece amigável para iniciantes.

Exemplos para Iniciantes

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: Registrar 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: Dispará-lo no seu app
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 (ex.: enviar um email de boas-vindas) mais tarde sem alterar a rota.

Exemplo 2: Notificando Sobre Novos Usuários

// Ouvinte para novos registros
Flight::onEvent('user.registered', function ($email, $name) {
    // Simule o envio de um email
    echo "Email sent to $email: Welcome, $name!";
});

// Dispará-lo quando alguém se registra
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 registro se concentra em criar o usuário, enquanto o evento lida com notificações. Você poderia adicionar mais ouvintes (ex.: registrar o registro) mais tarde.

Exemplo 3: Limpando um Cache

// Ouvinte para limpar um cache
Flight::onEvent('page.updated', function ($pageId) {
    unset($_SESSION['pages'][$pageId]); // Limpa o cache da sessão, se aplicável
    echo "Cache cleared for page $pageId.";
});

// Dispará-lo 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 preocupa com o caching — ele apenas sinaliza a atualização. Outras partes do app podem reagir conforme necessário.

Melhores Práticas

O sistema de eventos no Flight PHP, com Flight::onEvent() e Flight::triggerEvent(), oferece uma forma simples, mas poderosa, de construir aplicativos flexíveis. Ao permitir que diferentes partes do seu app se comuniquem por meio 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 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 do seu app!

Eventos Integrados

O 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

Learn/views

Vistas

Flight fornece alguma funcionalidade básica de modelagem por padrão. Para exibir uma vista chame o método render com o nome do arquivo de modelo e dados de modelo opcionais:

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

Os dados do modelo que você passa são automaticamente injetados no modelo e podem ser referenciados como uma variável local. Os arquivos de modelo são simplesmente arquivos PHP. Se o conteúdo do arquivo de modelo hello.php for:

Olá, <?= $name ?>!

A saída seria:

Olá, Bob!

Você também pode definir manualmente variáveis de visualização usando o método set:

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

A variável name agora está disponível em todas as suas visualizações. Então você pode simplesmente fazer:

Flight::render('hello');

Observe que ao especificar o nome do modelo no método render, você pode omitir a extensão .php.

Por padrão, o Flight procurará um diretório views para arquivos de modelo. Você pode definir um caminho alternativo para seus modelos configurando o seguinte:

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

Layouts

É comum que sites tenham um único arquivo de modelo de layout com conteúdo alternante. Para renderizar conteúdo a ser usado em um layout, você pode passar um parâmetro opcional para o método render.

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

Sua visualização então terá variáveis salvas chamadas headerContent e bodyContent. Você pode então renderizar seu layout fazendo:

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

Se os arquivos de modelo forem assim:

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>Página Inicial</title>
  </head>
  <body>
    <h1>Olá</h1>
    <div>Mundo</div>
  </body>
</html>

Vistas Personalizadas

O Flight permite que você substitua a engine de visualização padrão simplesmente registrando sua própria classe de visualização. Veja como você usaria o Smarty template engine para suas visualizações:

// Carregar biblioteca do Smarty
require './Smarty/libs/Smarty.class.php';

// Registrar Smarty como a classe de visualização
// Também passar uma função de retorno de chamada para configurar o Smarty ao carregar
Flight::register('view', Smarty::class, [], function (Smarty $smarty) {
  $smarty->setTemplateDir('./templates/');
  $smarty->setCompileDir('./templates_c/');
  $smarty->setConfigDir('./config/');
  $smarty->setCacheDir('./cache/');
});

// Atribuir dados do modelo
Flight::view()->assign('name', 'Bob');

// Exibir o modelo
Flight::view()->display('hello.tpl');

Para completude, você também deve substituir o método de renderização padrão do Flight:

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

Learn/templates

Visões e Modelos HTML

O Flight fornece algumas funcionalidades básicas de modelagem por padrão.

O Flight permite que você substitua o mecanismo de visualização padrão simplesmente registrando sua própria classe de visualização. Role para baixo para ver exemplos de como usar Smarty, Latte, Blade e mais!

Mecanismo de Visualização Integrado

Para exibir um modelo de visualização, chame o método render com o nome do arquivo de modelo e dados de modelo opcionais:

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

Os dados do modelo que você passa são automaticamente injetados no modelo e podem ser referenciados como uma variável local. Os arquivos de modelo são simplesmente arquivos PHP. Se o conteúdo do arquivo de modelo hello.php for:

Hello, <?= $name ?>!

A saída seria:

Hello, Bob!

Você também pode definir variáveis de visualização manualmente 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 modelo no método render, você pode deixar de fora a extensão .php.

Por padrão, o Flight irá procurar um diretório views para arquivos de modelo. Você pode definir um caminho alternativo para seus modelos configurando o seguinte:

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

Layouts

É comum que websites tenham um único arquivo de modelo de layout com conteúdo intercambiável. Para renderizar conteúdo a ser usado em um layout, você pode passar um parâmetro opcional para o método render.

Flight::render('header', ['heading' => '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 modelo forem assim:

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

Aqui está como você usaria o Smarty mecanismo de modelo para suas visualizações:

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

// Registrar Smarty como a classe de visualização
// Também passe 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/');
});

// Atribuir dados do modelo
Flight::view()->assign('name', 'Bob');

// Exibir o modelo
Flight::view()->display('hello.tpl');

Para completar, você também deve sobrescrever o método render padrão do Flight:

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

Latte

Aqui está como você usaria o Latte mecanismo de modelo para suas visualizações:

// Registrar Latte como a classe de visualização
// Também passe uma função de retorno para configurar o Latte ao carregar
Flight::register('view', Latte\Engine::class, [], function (Latte\Engine $latte) {
  // Aqui é onde o Latte irá armazenar seus modelos para acelerar as coisas
    // Uma coisa interessante sobre o Latte é que ele atualiza automaticamente seu
    // cache quando você faz alterações em seus modelos!
    $latte->setTempDirectory(__DIR__ . '/../cache/');

    // Diga ao Latte onde estará o diretório raiz para suas visualizações.
    $latte->setLoader(new \Latte\Loaders\FileLoader(__DIR__ . '/../views/'));
});

// E finalize para que você possa usar Flight::render() corretamente
Flight::map('render', function(string $template, array $data): void {
  // Isso é como $latte_engine->render($template, $data);
  echo Flight::view()->render($template, $data);
});

Blade

Aqui está como você usaria o Blade mecanismo de modelo para suas visualizações:

Primeiro, você precisa instalar a biblioteca BladeOne via Composer:

composer require eftec/bladeone

Em seguida, você pode configurar o BladeOne como a classe de visualização no Flight:

<?php
// Carregar biblioteca BladeOne
use eftec\bladeone\BladeOne;

// Registrar BladeOne como a classe de visualização
// Também passe 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);
});

// Atribuir dados do modelo
Flight::view()->share('name', 'Bob');

// Exibir o modelo
echo Flight::view()->run('hello', []);

Para completar, você também deve sobrescrever o método render 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 modelo hello.blade.php pode ser assim:

<?php
Hello, {{ $name }}!

A saída seria:

Hello, Bob!

Seguindo esses passos, você pode integrar o mecanismo de modelo Blade com o Flight e usá-lo para renderizar suas visualizações.

Learn/flight_vs_fat_free

Voo vs Sem Gordura

O que é Fat-Free?

Sem Gordura (afetuosamente conhecido como SG) é um microframework PHP poderoso, mas fácil de usar, projetado para ajudá-lo a construir aplicativos web dinâmicos e robustos - rapidamente!

Voo se compara com Sem Gordura de muitas maneiras e provavelmente é o parente mais próximo em termos de recursos e simplicidade. Sem Gordura tem muitos recursos que Voo não tem, mas também tem muitos recursos que Voo tem. Sem Gordura está começando a mostrar sua idade e não é tão popular quanto já foi.

As atualizações estão se tornando menos frequentes e a comunidade não é mais tão ativa quanto já foi. O código é simples o suficiente, mas às vezes a falta de disciplina sintática pode tornar difícil de ler e entender. Ele funciona para o PHP 8.3, mas o código em si ainda parece que pertence ao PHP 5.3.

Prós em comparação com Voo

Contras em comparação com Voo

Learn/extending

Estendendo

Flight foi projetado para ser um framework extensível. O framework vem com um conjunto de métodos e componentes padrão, mas permite que você mapeie seus próprios métodos, registre suas próprias classes ou até mesmo substitua classes e métodos existentes.

Se você está procurando um DIC (Container de Injeção de Dependência), vá para a página do Container de Injeção de Dependência.

Mapeando Métodos

Para mapear seu próprio método simples personalizado, você usa a função map:

// Mapeie seu método
Flight::map('hello', function (string $name) {
  echo "olá $name!";
});

// Chame seu método personalizado
Flight::hello('Bob');

Embora seja possível criar métodos personalizados simples, é recomendável apenas criar funções padrão em PHP. Isso possui autocompletar nos IDEs e é mais fácil de ler. O equivalente do código acima seria:

function hello(string $name) {
  echo "olá $name!";
}

hello('Bob');

Isso é mais utilizado quando você precisa passar variáveis para seu método para obter um valor esperado. Usar o método register() como abaixo é mais para passar configurações e, em seguida, chamar sua classe pré-configurada.

Registrando Classes

Para registrar sua própria classe e configurá-la, você usa a função register:

// Registre sua classe
Flight::register('user', User::class);

// Obtenha uma instância de sua classe
$user = Flight::user();

O método de registro também permite que você passe parâmetros para o construtor da sua classe. Assim, quando você carregar sua classe personalizada, ela virá pré-inicializada. Você pode definir os parâmetros do construtor passando um array adicional. Aqui está um exemplo de como carregar 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 de 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 mais tarde em seu código, basta chamar o mesmo método novamente
class SomeController {
  public function __construct() {
    $this->db = Flight::db();
  }
}

Se você passar um parâmetro de callback adicional, ele será executado imediatamente após a construção da classe. Isso permite que você execute quaisquer procedimentos de preparação para 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, cada vez que você carrega sua classe, receberá uma instância compartilhada. Para obter uma nova instância de uma classe, basta passar false como parâmetro:

// Instância compartilhada da classe
$shared = Flight::db();

// Nova instância da classe
$new = Flight::db(false);

Lembre-se de que métodos mapeados têm precedência sobre classes registradas. Se você declarar ambos usando o mesmo nome, apenas o método mapeado será invocado.

Registro de Logs

Flight não possui um sistema de logs embutido, no entanto, é muito fácil utilizar uma biblioteca de logging com o Flight. Aqui está um exemplo usando a biblioteca Monolog:

// index.php ou bootstrap.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('Esta é uma mensagem de aviso');

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 da Aplicação) usando os métodos before e after:

// Em seu arquivo bootstrap

Flight::before('start', function() {
    Flight::set('start_time', microtime(true));
});

Flight::after('start', function() {
    $end = microtime(true);
    $start = Flight::get('start_time');
    Flight::log()->info('Requisição '.Flight::request()->url.' levou ' . round($end - $start, 4) . ' segundos');

    // Você também poderia adicionar seus cabeçalhos de requisição ou resposta
    // para registrá-los também (tenha cuidado, pois isso seria uma 
    // grande quantidade de dados se você tiver muitas requisições)
    Flight::log()->info('Cabeçalhos da Requisição: ' . json_encode(Flight::request()->headers));
    Flight::log()->info('Cabeçalhos da Resposta: ' . json_encode(Flight::response()->headers));
});

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 visualizar todos os métodos que pode substituir aqui.

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() {
  // Exiba a página personalizada de 404
  include 'errors/404.html';
});

Flight também permite que você substitua componentes centrais do framework. Por exemplo, você pode substituir a classe Router padrão pela sua própria classe personalizada:

// Registre sua classe personalizada
Flight::register('router', MyRouter::class);

// Quando o Flight carrega a instância do Router, ele carregará sua classe
$myrouter = Flight::router();

No entanto, métodos do framework como map e register não podem ser substituídos. Você receberá um erro se tentar fazer isso.

Learn/json

# JSON

Flight fornece suporte para o envio de respostas JSON e JSONP. Para enviar uma resposta JSON, você
passa alguns dados a serem codificados em JSON:

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

Para solicitações JSONP, você pode passar opcionalmente o nome do parâmetro de consulta que está usando para definir sua função de retorno de chamada:

Flight::jsonp(['id' => 123], 'q');

Portanto, 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 será padrão para jsonp.

Learn/flight_vs_slim

Comparação entre Flight e Slim

O que é o Slim?

Slim é um microframework PHP que ajuda você a escrever rapidamente aplicações web e APIs simples, porém poderosas.

Muita da inspiração para algumas das funcionalidades da versão 3 do Flight na verdade veio do Slim. Agrupar rotas e executar middleware em uma ordem específica são duas funcionalidades que foram inspiradas pelo Slim. O Slim v3 foi lançado voltado para a simplicidade, mas houve críticas mistas em relação ao v4.

Prós em comparação com o Flight

Contras em comparação com o Flight

Learn/autoloading

Carregamento Automático

O carregamento automático é um conceito em PHP onde você especifica um diretório ou diretórios para carregar classes. Isso é muito mais benéfico do que usar require ou include para carregar classes. Também é um requisito para usar pacotes do Composer.

Por padrão, qualquer classe Flight é carregada automaticamente graças ao Composer. No entanto, se você deseja carregar suas próprias classes, pode usar o método Flight::path() para especificar um diretório para carregar classes.

Exemplo Básico

Vamos assumir que temos uma estrutura de diretórios como a seguinte:

# Caminho de Exemplo
/home/user/project/my-flight-project/
├── app
│   ├── cache
│   ├── config
│   ├── controllers - contém os controladores para este projeto
│   ├── translations
│   ├── UTILS - contém classes apenas para esta aplicação (tudo em maiúsculas de propósito para um exemplo posterior)
│   └── views
└── public
    └── css
    └── js
    └── index.php

Você pode especificar cada diretório a ser carregado da seguinte maneira:


/**
 * public/index.php
 */

// Adicionar um caminho ao carregador automático
Flight::path(__DIR__.'/../app/controllers/');
Flight::path(__DIR__.'/../app/utils/');

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

// sem necessidade de namespace

// Todas as classes carregadas automaticamente são recomendadas a serem em Pascal Case (cada palavra capitalizada, sem espaços)
// A partir da versão 3.7.2, você pode usar Pascal_Snake_Case para os nomes de suas classes executando Loader::setV2ClassLoading(false);
class MyController {

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

Namespaces

Se você tiver namespaces, na verdade se torna muito fácil implementar isso. Você deve usar o método Flight::path() para especificar o diretório raiz (não o diretório do documento ou pasta public/) de sua aplicação.


/**
 * public/index.php
 */

// Adicionar um caminho ao carregador automático
Flight::path(__DIR__.'/../');

Agora, veja como seu controlador poderia ser. Observe o exemplo abaixo, mas preste atenção aos comentários para informações importantes.

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

// namespaces são necessários
// os namespaces são iguais à estrutura de diretórios
// os namespaces devem seguir o mesmo padrão de caso que a estrutura de diretórios
// os namespaces e diretórios não podem ter nenhum sublinhado (a menos que Loader::setV2ClassLoading(false) seja definido)
namespace app\controllers;

// Todas as classes carregadas automaticamente são recomendadas a serem em Pascal Case (cada palavra capitalizada, sem espaços)
// A partir da versão 3.7.2, você pode usar Pascal_Snake_Case para os nomes de suas classes executando Loader::setV2ClassLoading(false);
class MyController {

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

E se você quiser carregar automaticamente uma classe em seu diretório de utils, você faria basicamente a mesma coisa:


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

// o namespace deve corresponder à estrutura de diretórios e caso (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) {
        // fazer algo
    }
}

Sublinhados em Nomes de Classe

A partir da versão 3.7.2, você pode usar Pascal_Snake_Case para os nomes de suas classes executando Loader::setV2ClassLoading(false);. Isso permitirá que você use sublinhados em seus nomes de classe. Isso não é recomendado, mas está disponível para aqueles que precisam.


/**
 * public/index.php
 */

// Adicionar um caminho ao carregador automático
Flight::path(__DIR__.'/../app/controllers/');
Flight::path(__DIR__.'/../app/utils/');
Loader::setV2ClassLoading(false);

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

// sem necessidade de namespace

class My_Controller {

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

Learn/troubleshooting

# Solução de problemas

Esta página irá ajudá-lo a resolver problemas comuns que você pode encontrar ao usar o Flight.

## Problemas Comuns

### 404 Não Encontrado ou Comportamento de Rota Inesperado

Se você estiver vendo um erro 404 Não Encontrado (mas jura pela sua vida que está realmente lá e não é um erro de digitação) isso poderia 
realmente ser um problema com você retornando um valor no ponto final da sua rota em vez de apenas ecoá-lo. A razão para isso é intencional, 
mas pode pegar alguns desenvolvedores de surpresa.

```php

Flight::route('/hello', function(){
    // Isso pode causar um erro 404 Não Encontrado
    return 'Olá Mundo';
});

// O que você provavelmente quer
Flight::route('/hello', function(){
    echo 'Olá Mundo';
});

A razão para isso é por causa de um mecanismo especial incorporado 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 de Roteamento.

Classe Não Encontrada (autoload não funcionando)

Podem haver algumas razões para isso não estar funcionando. Abaixo estão alguns exemplos, mas certifique-se também de verificar a seção de autoload.

Nome do 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 chamado MyClass.php. Se você tem uma classe chamada MyClass e o arquivo é chamado myclass.php então o autoload não será capaz de encontrá-lo.

Namespace Incorreto

Se estiver usando namespaces, então o namespace deve corresponder à estrutura de diretórios.

// código

// se o seu MyController estiver no diretório app/controllers e estiver em um namespace
// isso não funcionará
Flight::route('/hello', 'MyController->hello');

// você precisará escolher uma dessas opções
Flight::route('/hello', 'app\controllers\MyController->hello');
// ou se você tiver um comando use no topo

use app\controllers\MyController;

Flight::route('/hello', [ MyController::class, 'hello' ]);
// também pode ser escrito
Flight::route('/hello', MyController::class.'->hello');
// também...
Flight::route('/hello', [ 'app\controllers\MyController', 'hello' ]);

path() não definido

No aplicativo skeleton, isso é definido dentro do arquivo config.php, mas para que suas classes sejam encontradas, você precisa garantir que o método path() esteja definido (provavelmente na raiz do seu diretório) antes de tentar usá-lo.


// Adicione um caminho ao autoload
Flight::path(__DIR__.'/../');

Install

Instalação

Baixe os arquivos.

Se estiver a usar Composer, pode executar o seguinte comando:

composer require flightphp/core

OU então pode baixar os arquivos diretamente e extrair para o seu diretório web.

Configure o seu servidor web.

Apache

Para o Apache, edite o seu ficheiro .htaccess com o seguinte:

RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php [QSA,L]

Nota: Se necessitar de utilizar o Flight numa subpasta, adicione a linha RewriteBase /subdir/ logo após RewriteEngine On.

Nota: Se quiser proteger todos os ficheiros do servidor, como um ficheiro de bd ou env. Coloque isto no seu ficheiro .htaccess:

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

Nginx

Para o Nginx, adicione o seguinte à declaração do seu servidor:

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

Crie o seu ficheiro index.php.

<?php

// Se estiver a usar o Composer, requer o autoloader.
require 'vendor/autoload.php';
// se não estiver a usar o Composer, carregue o framework diretamente
// require 'flight/Flight.php';

// Depois defina uma rota e atribua uma função para lidar com o pedido.
Flight::route('/', function () {
  echo 'hello world!';
});

// Por fim, inicie o framework.
Flight::start();

Guides/unit_testing

Testes Unitários no Flight PHP com PHPUnit

Este guia introduz testes unitários no Flight PHP usando PHPUnit, direcionado a iniciantes que querem entender por que os testes unitários importam e como aplicá-los na prática. Nós nos concentraremos em testar comportamentos — garantindo que sua aplicação faça o que você espera, como enviar um email ou salvar um registro — em vez de cálculos triviais. Começaremos com um manipulador de rota simples [/learn/routing] e progrediremos para um controlador mais complexo [/learn/routing], incorporando injeção de dependência (DI) e simulando serviços de terceiros.

Por que Testar Unitariamente?

Testes unitários garantem que seu código se comporte conforme o esperado, capturando bugs antes que eles cheguem à produção. É especialmente valioso no Flight, onde roteamento leve e flexibilidade podem levar a interações complexas. Para desenvolvedores solo ou equipes, testes unitários atuam como uma rede de segurança, documentando o comportamento esperado e prevenindo regressões ao revisitar 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 (ex.: testando x * y = z), nós nos concentraremos em comportamentos do mundo real, como validar entrada, salvar dados ou disparar ações como emails. Nosso objetivo é tornar os testes acessíveis e significativos.

Princípios Gerais de Orientação

  1. Teste Comportamento, Não Implementação: Foque em resultados (ex.: “email enviado” ou “registro salvo”) em vez de detalhes internos. Isso torna os testes robustos contra refatorações.
  2. Pare de usar Flight::: Os métodos estáticos do Flight são extremamente convenientes, mas tornam os testes difíceis. Você deve se acostumar a usar a variável $app de $app = Flight::app();. $app tem todos os mesmos métodos que Flight::. Você ainda poderá usar $app->route() ou $this->app->json() no seu controlador etc. Você também deve usar o roteador real do Flight com $router = $app->router() e então usar $router->get(), $router->post(), $router->group() etc. Veja Routing.
  3. Mantenha Testes Rápidos: Testes rápidos incentivam execuções frequentes. Evite operações lentas como chamadas de banco de dados em testes unitários. Se você tiver um teste lento, é um sinal de que está escrevendo um teste de integração, não unitário. Testes de integração envolvem bancos de dados reais, chamadas HTTP reais, envio de emails reais etc. Eles têm seu lugar, mas são lentos e podem ser instáveis, significando que às vezes falham por razões desconhecidas.
  4. Use Nomes Descritivos: Nomes de testes devem descrever claramente o comportamento sendo testado. Isso melhora a legibilidade e a manutenção.
  5. Evite Globals Como a Peste: Minimize o uso de $app->set() e $app->get(), pois eles atuam como estado global, exigindo simulações em cada teste. Prefira DI ou um contêiner DI (veja Dependency Injection Container). Até mesmo usar o método $app->map() é tecnicamente um "global" e deve ser evitado em favor da DI. Use uma biblioteca de sessão como flightphp/session para que você possa simular o objeto de sessão nos seus testes. Não chame $_SESSION diretamente no seu código, pois isso injeta uma variável global no seu código, tornando-o difícil de testar.
  6. Use Injeção de Dependência: Injete dependências (ex.: PDO, mailers) em controladores para isolar a lógica e simplificar simulações. Se você tiver uma classe com muitas dependências, considere refatorá-la em classes menores que tenham uma única responsabilidade seguindo os princípios SOLID.
  7. Simule Serviços de Terceiros: Simule bancos de dados, clientes HTTP (cURL) ou serviços de email para evitar chamadas externas. Teste uma ou duas camadas de profundidade, mas permita que sua lógica principal execute. Por exemplo, se sua aplicação envia uma mensagem de texto, você NÃO quer realmente enviar uma mensagem de texto toda vez que executar seus testes, pois isso acumulará custos (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.
  8. Almeje Alta Cobertura, Não Perfeição: 100% de cobertura de linhas é bom, mas não significa que tudo no seu código esteja testado da maneira correta (vá em frente e pesquise cobertura de ramo/caminho no PHPUnit). Priorize comportamentos críticos (ex.: registro de usuário, respostas de API e captura de respostas falhas).
  9. Use Controladores para Rotas: Nas definições de rotas, use controladores e não closures. O flight\Engine $app é injetado em todo controlador via o construtor por padrão. Nos testes, use $app = new Flight\Engine() para instanciar o Flight dentro de um teste, injetá-lo no seu controlador e chamar métodos diretamente (ex.: $controller->register()). Veja Extending Flight e Routing.
  10. Escolha um estilo de simulação e mantenha-se nele: O PHPUnit suporta vários estilos de simulação (ex.: prophecy, mocks internos), ou você pode usar classes anônimas que têm seus próprios benefícios como autocompletar, quebrar se você alterar a definição do método etc. Apenas seja consistente em seus testes. Veja PHPUnit Mock Objects.
  11. Use visibilidade protected para métodos/propriedades que você quer 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 PHPUnit no seu projeto Flight PHP usando o Composer para testes fáceis. Veja o guia de introdução ao PHPUnit para mais detalhes.

  1. No diretório do seu projeto, execute:

    composer require --dev phpunit/phpunit

    Isso instala a versão mais recente do PHPUnit como uma dependência de desenvolvimento.

  2. Crie um diretório tests na raiz do seu projeto para arquivos de teste.

  3. Adicione um script de teste ao composer.json para conveniência:

    // outro conteúdo do composer.json
    "scripts": {
       "test": "phpunit --configuration phpunit.xml"
    }
  4. Crie um arquivo phpunit.xml na 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 [/learn/routing] que valida o email de entrada de um usuário. Nós testaremos seu comportamento: retornando uma mensagem de sucesso para emails válidos e um erro para inválidos. Para validação de email, usamos filter_var.

// index.php
$app->route('POST /register', [ UserController::class, 'register' ]);

// UserController.php
class UserController {
    protected $app;

    public function __construct(flight\Engine $app) {
        $this->app = $app;
    }

    public function register() {
        $email = $this->app->request()->data->email;
        $responseArray = [];
        if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
            $responseArray = ['status' => 'error', 'message' => 'Invalid email'];  // Email inválido
        } else {
            $responseArray = ['status' => 'success', 'message' => 'Valid email'];  // Email válido
        }

        $this->app->json($responseArray);
    }
}

Para testar isso, crie um arquivo de teste. Veja Unit Testing and SOLID Principles para mais sobre estruturar testes:

// tests/UserControllerTest.php
use PHPUnit\Framework\TestCase;
use Flight;
use flight\Engine;

class UserControllerTest extends TestCase {

    public function testValidEmailReturnsSuccess() {
        $app = new Engine();  // Simula o objeto 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('Valid email', $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('Invalid email', $output['message']);
    }
}

Pontos Chave:

Execute composer test para verificar se a rota se comporta conforme o esperado. Para mais sobre requests e responses no Flight, veja os docs relevantes.

Usando Injeção de Dependência para Controladores Testáveis

Para cenários mais complexos, use injeção de dependência (DI) para tornar controladores testáveis. Evite globals do Flight (ex.: Flight::set(), Flight::map(), Flight::register()) pois eles atuam como estado global, exigindo simulações para cada teste. Em vez disso, use o contêiner DI do Flight, DICE, PHP-DI ou DI manual.

Vamos usar flight\database\PdoWrapper em vez de PDO cru. Essa wrapper é muito mais fácil de simular e testar unitariamente!

Aqui está um controlador que salva um usuário em um banco de dados e envia um email de boas-vindas:

use flight\database\PdoWrapper;

class UserController {
    protected $app;
    protected $db;
    protected $mailer;

    public function __construct(Engine $app, PdoWrapper $db, MailerInterface $mailer) {
        $this->app = $app;
        $this->db = $db;
        $this->mailer = $mailer;
    }

    public function register() {
        $email = $this->app->request()->data->email;
        if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
            // adicionando o return aqui ajuda nos testes unitários para parar a execução
            return $this->app->jsonHalt(['status' => 'error', 'message' => 'Invalid email']);
        }

        $this->db->runQuery('INSERT INTO users (email) VALUES (?)', [$email]);
        $this->mailer->sendWelcome($email);

        return $this->app->json(['status' => 'success', 'message' => 'User registered']);
    }
}

Pontos Chave:

Testando o Controlador com Simulações

Agora, vamos testar o comportamento do UserController: validando emails, salvando no banco de dados e enviando emails. Nós simularemos o banco de dados e o mailer para isolar o controlador.

// tests/UserControllerDICTest.php
use PHPUnit\Framework\TestCase;

class UserControllerDICTest extends TestCase {
    public function testValidEmailSavesAndSendsEmail() {

        // Às vezes misturar estilos de simulação é necessário
        // Aqui usamos o mock interno do PHPUnit para PDOStatement
        $statementMock = $this->createMock(PDOStatement::class);
        $statementMock->method('execute')->willReturn(true);
        // Usando uma classe anônima para simular PdoWrapper
        $mockDb = new class($statementMock) extends PdoWrapper {
            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 isso para alterar o mock 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('User registered', $result['message']);
        $this->assertEquals('test@example.com', $mockMailer->sentEmail);
    }

    public function testInvalidEmailSkipsSaveAndEmail() {
         $mockDb = new class() extends PdoWrapper {
            // Um construtor vazio ignora o construtor pai
            public function __construct() {}
            public function runQuery(string $sql, array $params = []): PDOStatement {
                throw new Exception('Should not be called');  // Não deve ser chamado
            }
        };
        $mockMailer = new class implements MailerInterface {
            public $sentEmail = null;
            public function sendWelcome($email): bool {
                throw new Exception('Should not be called');  // Não deve ser chamado
            }
        };
        $app = new Engine();
        $app->request()->data->email = 'invalid-email';

        // Precisa mapear jsonHalt para evitar saída
        $app->map('jsonHalt', function($data) use ($app) {
            $app->json($data, 400);
        });
        $controller = new UserControllerDIC($app, $mockDb, $mockMailer);
        $controller->register();
        $response = $app->response()->getBody();
        $result = json_decode($response, true);
        $this->assertEquals('error', $result['status']);
        $this->assertEquals('Invalid email', $result['message']);
    }
}

Pontos Chave:

Simulando Demais

Cuidado para não simular demais do seu código. Deixe-me dar um exemplo abaixo sobre por que isso pode ser ruim usando nosso UserController. Nós vamos mudar essa verificação para um método chamado isEmailValid (usando filter_var) e as outras novas adições para um método separado chamado registerUser.

use flight\database\PdoWrapper;
use flight\Engine;

// UserControllerDICV2.php
class UserControllerDICV2 {
    protected $app;
    protected $db;
    protected $mailer;

    public function __construct(Engine $app, PdoWrapper $db, MailerInterface $mailer) {
        $this->app = $app;
        $this->db = $db;
        $this->mailer = $mailer;
    }

    public function register() {
        $email = $this->app->request()->data->email;
        if (!$this->isEmailValid($email)) {
            // adicionando o return aqui ajuda nos testes unitários para parar a execução
            return $this->app->jsonHalt(['status' => 'error', 'message' => 'Invalid email']);
        }

        $this->registerUser($email);

        $this->app->json(['status' => 'success', 'message' => 'User registered']);
    }

    protected function isEmailValid($email) {
        return filter_var($email, FILTER_VALIDATE_EMAIL) !== false;
    }

    protected function registerUser($email) {
        $this->db->runQuery('INSERT INTO users (email) VALUES (?)', [$email]);
        $this->mailer->sendWelcome($email);
    }
}

E agora o teste unitário com simulação excessiva que não testa nada de fato:

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 porque é "fácil"
        $controller = new class($app) extends UserControllerDICV2 {
            protected $app;
            // Bypass as dependências no construtor
            public function __construct($app) {
                $this->app = $app;
            }

            // Vamos forçar isso a ser válido.
            protected function isEmailValid($email) {
                return true;  // Sempre retorna true, contornando a validação real
            }

            // Bypass as chamadas reais de DB 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('User registered', $result['message']);
    }
}

Uhu! Temos testes unitários e eles estão passando! Mas espere, o que acontece 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 um domínio específico
        $validDomain = strpos($email, '@example.com') !== false; 
        return $validEmail && $validDomain;
    }
}

Se eu executar meus testes acima, eles ainda passam! Mas porque eu não estava testando o comportamento (deixando parte do código executar), eu tenho potencialmente um bug esperando para acontecer na produção. O teste deve ser modificado para contabilizar o novo comportamento, e também o oposto 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 mais guias, veja Unit Testing and SOLID Principles e Troubleshooting.

Armadilhas Comuns

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 capturam regressões, salvando-o de re-aprender sua aplicação. Para devs solo, teste caminhos críticos (ex.: cadastro de usuário, processamento de pagamento). Para equipes, testes garantem comportamento consistente através de contribuições. Veja Why 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 o orienta na criação de um blog básico usando o framework Flight PHP. Você configurará um projeto, definirá rotas, gerenciará postagens com JSON e as renderizará com o motor de templates Latte—todos mostrando a simplicidade e flexibilidade do Flight. Ao final, você terá um blog funcional com uma página inicial, páginas de postagens individuais e um formulário de criação.

Pré-requisitos

Passo 1: Configure Seu Projeto

Comece criando um novo diretório de projeto e instalando o Flight via Composer.

  1. Criar um Diretório:

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

    composer require flightphp/core
  3. Criar um Diretório Público: O Flight utiliza um único ponto de entrada (index.php). Crie uma pasta public/ para ele:

    mkdir public
  4. index.php Básico: Crie public/index.php com uma rota simples de “hello world”:

    <?php
    require '../vendor/autoload.php';
    
    Flight::route('/', function () {
       echo 'Olá, Flight!';
    });
    
    Flight::start();
  5. Executar o Servidor Interno: Teste sua configuração com o servidor de desenvolvimento do PHP:

    php -S localhost:8000 -t public/

    Acesse http://localhost:8000 para ver “Olá, Flight!”.

Passo 2: Organize a Estrutura do Seu Projeto

Para uma configuração limpa, estruture seu projeto da seguinte forma:

flight-blog/
├── app/
│   ├── config/
│   └── views/
├── data/
├── public/
│   └── index.php
├── vendor/
└── composer.json

Passo 3: Instalar e Configurar o Latte

O Latte é um motor de templates leve que se integra bem com o Flight.

  1. Instalar o Latte:

    composer require latte/latte
  2. Configurar o Latte no Flight: Atualize public/index.php para registrar o Latte como o motor 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' => 'Meu Blog']);
    });
    
    Flight::start();
  3. Criar um Template de Layout: Em app/views/layout.latte:

    <!DOCTYPE html>
    <html>
    <head>
    <title>{$title}</title>
    </head>
    <body>
    <header>
        <h1>Meu Blog</h1>
        <nav>
            <a href="/">Home</a> | 
            <a href="/create">Criar uma Postagem</a>
        </nav>
    </header>
    <main>
        {block content}{/block}
    </main>
    <footer>
        <p>&copy; {date('Y')} Blog Flight</p>
    </footer>
    </body>
    </html>
  4. Criar um Template de Página 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ê saiu dele e visite http://localhost:8000 para ver a página renderizada.

  5. Criar um Arquivo de Dados:

    Use um arquivo JSON para simular um banco de dados para simplicidade.

    Em data/posts.json:

    [
       {
           "slug": "primeira-postagem",
           "title": "Minha Primeira Postagem",
           "content": "Esta é minha primeira postagem no blog com Flight PHP!"
       }
    ]

Passo 4: Definir Rotas

Separe suas rotas em um arquivo de configuração para melhor organização.

  1. Criar routes.php: Em app/config/routes.php:

    <?php
    Flight::route('/', function () {
       Flight::view()->render('home.latte', ['title' => 'Meu 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' => 'Criar uma Postagem']);
    });
  2. Atualizar 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: Armazenar e Recuperar Postagens do Blog

Adicione os métodos para carregar e salvar postagens.

  1. Adicionar um Método de Postagens: Em index.php, adicione um método para carregar postagens:

    Flight::map('posts', function () {
       $file = __DIR__ . '/../data/posts.json';
       return json_decode(file_get_contents($file), true);
    });
  2. Atualizar Rotas: Modifique app/config/routes.php para usar postagens:

    <?php
    Flight::route('/', function () {
       $posts = Flight::posts();
       Flight::view()->render('home.latte', [
           'title' => 'Meu 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' => 'Criar uma Postagem']);
    });

Passo 6: Criar Templates

Atualize seus templates para exibir postagens.

  1. Página de Postagem (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: Adicionar Criação de Postagens

Manipule a submissão de formulário para adicionar novas postagens.

  1. Criar 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">Título:</label>
                <input type="text" name="title" id="title" required>
            </div>
            <div class="form-group">
                <label for="content">Conteúdo:</label>
                <textarea name="content" id="content" required></textarea>
            </div>
            <button type="submit">Salvar Postagem</button>
        </form>
    {/block}
  2. Adicionar 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('/');
    });
  3. Teste:

    • Acesse http://localhost:8000/create.
    • Envie uma nova postagem (por exemplo, “Segunda Postagem” com algum conteúdo).
    • Verifique a página inicial para vê-la listada.

Passo 8: Melhorar com Tratamento de Erros

Sobreponha o método notFound para uma melhor experiência de 404.

Em index.php:

Flight::map('notFound', function () {
    Flight::view()->render('404.latte', ['title' => 'Página Não Encontrada']);
});

Crie app/views/404.latte:

{extends 'layout.latte'}

{block content}
    <h2>404 - {$title}</h2>
    <p>Desculpe, essa página não existe!</p>
{/block}

Próximos Passos

Conclusão

Você construiu um blog simples com o Flight PHP! Este guia demonstra recursos principais como roteamento, templating com Latte e manipulação de envios de formulários—tudo isso mantendo as coisas leves. Explore a documentação do Flight para recursos mais avançados para levar 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

O que é Flight?

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 experimentando as mais recentes ferramentas alimentadas por IA, o baixo consumo de recursos e o design direto do Flight o tornam uma escolha perfeita.

Por que escolher Flight?

Início Rápido

Primeiro, instale-o com Composer:

composer require flightphp/core

Ou você pode baixar um zip do repositório aqui. Em seguida, você terá um arquivo básico index.php 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();

Pronto! Você tem um aplicativo básico do Flight. Agora, você pode executar este arquivo com php -S localhost:8000 e visitar http://localhost:8000 no seu navegador para ver a saída.

Simples o suficiente, não?
Saiba mais sobre Flight na documentação!

É rápido?

Absolutamente! Flight é um dos frameworks PHP mais rápidos por aí. Seu núcleo leve significa menos overhead e mais velocidade—perfeito para aplicativos tradicionais e projetos modernos impulsionados por IA. Você pode ver todos os benchmarks em TechEmpower

Veja o benchmark abaixo com alguns outros frameworks PHP populares.

Framework Plaintext Reqs/sec JSON Reqs/sec
Flight 190,421 182,491
Yii 145,749 131,434
Fat-Free 139,238 133,952
Slim 89,588 87,348
Phalcon 95,911 87,675
Symfony 65,053 63,237
Lumen 40,572 39,700
Laravel 26,657 26,901
CodeIgniter 20,628 19,901

Aplicativo Esqueleto/Modelo

Há um exemplo de aplicativo para ajudar você a começar com Flight. Confira flightphp/skeleton para um projeto pronto para uso, ou visite a página de exemplos para inspiração. Quer ver como a IA se encaixa? Explore exemplos impulsionados por IA.

Comunidade

Estamos no Matrix Chat

Matrix

E no Discord

Contribuindo

Existem duas maneiras de contribuir para Flight:

  1. Contribua para o framework principal visitando o repositório principal.
  2. Ajude a melhorar os documentos! Este site de documentação é 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 em torno de IA e novas tecnologias!

Requisitos

Flight requer PHP 7.4 ou superior.

Nota: PHP 7.4 é suportado porque, no momento da escrita (2024), o PHP 7.4 é a versão padrão para algumas distribuições Linux LTS. Forçar uma mudança para PHP >8 causaria muitos problemas para esses usuários. O framework também suporta PHP >8.

Licença

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 de armazenamento em cache em arquivo PHP leve, simples e autônoma

Vantagens

Este site de documentação está usando esta biblioteca para armazenar em cache 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 definida no seu arquivo de bootstrap ou em outro lugar no seu aplicativo
    $cache->setDevMode(ENVIRONMENT === 'development');
});

Então você pode usá-lo no seu código assim:


// Obter instância de cache
$cache = Flight::cache();
$data = $cache->refreshIfExpired('simple-cache-test', function () {
    return date("H:i:s"); // retornar dados a serem armazenados em cache
}, 10); // 10 segundos

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

Documentação

Visite https://github.com/flightphp/cache para a documentação completa e certifique-se de ver a pasta exemplos.

Awesome-plugins/permissions

FlightPHP/Permissões

Este é um módulo de permissões que pode ser usado em seus projetos se você tiver vários papéis em seu aplicativo e cada papel tiver funcionalidades um pouco diferentes. Este módulo permite que você defina permissões para cada papel e depois verifique se o usuário atual tem permissão para acessar uma determinada página ou realizar uma determinada ação.

Clique aqui para acessar o repositório no GitHub.

Instalação

Execute composer require flightphp/permissions e pronto!

Uso

Primeiro, você precisa configurar suas permissões e, em seguida, informar ao seu aplicativo o que as permissões significam. Por fim, você verificará suas permissões com $Permissions->has(), ->can() ou is(). has() e can() têm a mesma funcionalidade, mas têm nomes diferentes para tornar seu código mais legível.

Exemplo Básico

Vamos supor que você tenha um recurso em seu aplicativo que verifica se um usuário está conectado. 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 diz qual é o papel atual da pessoa
// provavelmente você tem algo que puxa o papel atual
// de uma variável de sessão que define isso
// depois que alguém faz o login, caso contrário eles terão um papel 'convidado' ou 'público'.
$current_role = 'admin';

// configurar permissões
$permission = new \flight\Permission($current_role);
$permission->defineRule('conectado', function($current_role) {
    return $current_role !== 'convidado';
});

// Você provavelmente vai querer persistir este objeto em algum lugar do Flight
Flight::set('permissão', $permissão);

Então em um controlador em algum lugar, você pode ter algo assim.

<?php

// algum controlador
class AlgunsController {
    public function algumaAção() {
        $permissão = Flight::get('permissão');
        if ($permissão->has('conectado')) {
            // faça algo
        } else {
            // faça algo diferente
        }
    }
}

Você também pode usar isso para rastrear se têm permissão para fazer algo em seu aplicativo. Por exemplo, se você tiver uma forma dos usuários interagirem com postagens em seu software, você pode verificar se eles têm permissão para realizar certas ações.

$current_role = 'admin';

// configurar permissões
$permission = new \flight\Permission($current_role);
$permission->defineRule('postagem', function($current_role) {
    if($current_role === 'admin') {
        $permissões = ['criar', 'ler', 'atualizar', 'apagar'];
    } else if($current_role === 'editor') {
        $permissões = ['criar', 'ler', 'atualizar'];
    } else if($current_role === 'autor') {
        $permissões = ['criar', 'ler'];
    } else if($current_role === 'contribuidor') {
        $permissões = ['criar'];
    } else {
        $permissões = [];
    }
    return $permissões;
});
Flight::set('permissão', $permissão);

Então em um controlador em algum lugar...

class ControladorDePostagem {
    public function criar() {
        $permissão = Flight::get('permissão');
        if ($permissão->can('postagem.criar')) {
            // faça algo
        } else {
            // faça algo diferente
        }
    }
}

Injetando dependências

Você pode injetar dependências no fechamento que define as permissões. Isso é útil se você tiver algum tipo de alternância, id ou qualquer outro ponto de dados que deseja verificar. O mesmo funciona para chamadas de Tipo Classe->Método, exceto que você define os argumentos no método.

Fechamentos

$Permission->defineRule('pedido', function(string $current_role, MyDependency $MyDependency = null) {
    // ... código
});

// em seu arquivo de controlador
public function criarPedido() {
    $MyDependency = Flight::myDependency();
    $permissão = Flight::get('permissão');
    if ($permissão->can('pedido.criar', $MyDependency)) {
        // faça algo
    } else {
        // faça algo diferente
    }
}

Classes

namespace MyApp;

class Permissões {

    public function pedido(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 de inicialização
$Permissões = new \flight\Permission($current_role);
$Permissões->defineRule('pedido', 'MyApp\Permissões->pedido');

// myapp/Permissões.php
namespace MyApp;

class Permissões {

    public function pedido(string $current_role, int $id_usuario) {
        // Pressupondo que você configurou isso antecipadamente
        /** @var \flight\database\PdoWrapper $db */
        $db = Flight::db();
        $permissões_permitidas = [ 'ler' ]; // todos podem visualizar um pedido
        if($current_role === 'gerente') {
            $permissões_permitidas[] = 'criar'; // gerentes podem criar pedidos
        }
        $alguma_alternancia_especial_do_db = $db->fetchField('SELECT alguma_alternancia_especial FROM ajustes WHERE id = ?', [ $id_usuario ]);
        if($alguma_alternancia_especial_do_db) {
            $permissões_permitidas[] = 'atualizar'; // se o usuário tiver uma alternância especial, ele pode atualizar pedidos
        }
        if($current_role === 'admin') {
            $permissões_permitidas[] = 'apagar'; // administradores podem excluir pedidos
        }
        return $permissões_permitidas;
    }
}

A parte legal é que também há um atalho que você pode usar (que também pode ser cacheado!!!) onde você apenas diz à classe de permissões para mapear todos os métodos de uma classe em permissões. Portanto, se você tiver um método chamado pedido() e um método chamado empresa(), esses serão mapeados automaticamente para que você possa simplesmente executar $Permissões->has('pedido.ler') ou `$Permissões->has('empresa.ler') e isso funcionará. Definir isso é muito difícil, então fique comigo aqui. Você só precisa fazer o seguinte:

Crie a classe de permissões que deseja agrupar.

class MinhasPermissões {
    public function pedido(string $current_role, int $id_pedido = 0): array {
        // código para determinar permissões
        return $array_de_permissoes;
    }

    public function empresa(string $current_role, int $id_empresa): array {
        // código para determinar permissões
        return $array_de_permissoes;
    }
}

Em seguida, torne as permissões descobríveis usando esta biblioteca.

$Permissões = new \flight\Permission($current_role);
$Permissões->defineRulesFromClassMethods(MyApp\Permissões::class);
Flight::set('permissões', $Permissões);

Finalmente, chame a permissão em sua base de código para verificar se o usuário tem permissão para realizar uma determinada permissão.

class AlgunsControlador {
    public function criarPedido() {
        if(Flight::get('permissões')->can('pedido.criar') === false) {
            die('Você não pode criar um pedido. Desculpe!');
        }
    }
}

Cache

Para habilitar o cache, consulte a simples biblioteca wruczak/phpfilecache. Um exemplo de habilitação está abaixo.


// este $app pode fazer parte do seu código, ou
// você pode simplesmente passar null e ele irá
// 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;

$Permissões = new \flight\Permission($current_role, $app, $Cache);
$Permissões->defineRulesFromClassMethods(MyApp\Permissões::class, 3600); // 3600 é quantos segundos para armazenar em cache. Deixe isso de fora para não usar o cache

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:

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

Plugins Incríveis

O Flight é incrivelmente extensível. Existem vários plugins que podem ser usados para adicionar funcionalidades à sua aplicação Flight. Alguns são oficialmente suportados pela Equipe do Flight e outros são bibliotecas micro/lite para ajudá-lo a começar.

Caching

O armazenamento em cache é uma ótima maneira de acelerar a sua aplicação. Existem diversas bibliotecas de cache que podem ser usadas com o Flight.

Debugging

Depurar é crucial quando você está desenvolvendo no seu ambiente local. Existem alguns plugins que podem aprimorar a sua experiência de depuração.

Bancos de Dados

Os bancos de dados são a base da maioria das aplicações. É assim que você armazena e recupera dados. Algumas bibliotecas de banco de dados são simplesmente wrappers para escrever consultas e outras são ORMs completos.

Sessão

As sessões não são realmente úteis para APIs, mas para desenvolver uma aplicação web, as sessões podem ser cruciais para manter o estado e as informações de login.

Templating

A criação de modelos é essencial para qualquer aplicação web com uma interface de usuário. Existem várias engines de templates que podem ser usadas com o Flight.

Contribuindo

Tem um plugin que gostaria de compartilhar? Envie uma solicitação pull para adicioná-lo à lista!

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?

Instalação

  1. Carregue a pasta flight-integration no diretório /wp-content/plugins/.
  2. Ative o plugin no admin do WordPress (menu Plugins).
  3. Vá para Configurações > Flight Framework para configurar o plugin.
  4. Defina o caminho do vendor para a sua instalação do Flight (ou use Composer para instalar o Flight).
  5. Configure o caminho da pasta do seu app e crie a estrutura de pastas (o plugin pode ajudar com isso!).
  6. 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/pdo_wrapper

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:

Os Scripts SQL

Os scripts são divididos em três conjuntos de scripts:

O diretório dos scripts é:

 <diretório raiz>
     |
     +-- base.sql
     |
     +-- /migrations
              |
              +-- /up
                   |
                   +-- 00001.sql
                   +-- 00002.sql
              +-- /down
                   |
                   +-- 00000.sql
                   +-- 00001.sql

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:

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 é

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

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

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:

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:

Todos os métodos, exceto get() e id(), retornam a instância Session para encadeamento.

Por Que Usar Este Plugin?

Detalhes Técnicos

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

Pista

A Pista é uma aplicação CLI que ajuda a gerenciar suas aplicações Flight. Ela pode gerar controladores, exibir todas as rotas e mais. É baseada na excelente biblioteca adhocore/php-cli.

Clique aqui para visualizar o código.

Instalação

Instale com o composer.

composer require flightphp/runway

Configuração Básica

Na primeira execução da Pista, ela guiará você por um processo de configuração e criará um arquivo de configuração .runway.json na raiz do seu projeto. Este arquivo conterá algumas configurações necessárias para que a Pista funcione corretamente.

Utilização

A Pista possui vários comandos que você pode usar para gerenciar sua aplicação Flight. Existem duas maneiras fáceis de usar a Pista.

  1. Se estiver usando o projeto esqueleto, você pode executar php runway [comando] a partir da raiz do seu projeto.
  2. Se estiver usando a Pista como um pacote instalado via composer, você pode executar vendor/bin/runway [comando] a partir da raiz do seu projeto.

Para qualquer comando, você pode inserir a flag --help para obter mais informações sobre como utilizar o comando.

php runway routes --help

Aqui estão alguns exemplos:

Gerar um Controlador

Com base na configuração em seu arquivo .runway.json, a localização padrão gerará um controlador para você no diretório app/controllers/.

php runway make:controller MeuControlador

Gerar um Modelo Active Record

Com base na configuração em seu arquivo .runway.json, a localização padrão gerará um controlador para você no diretório app/records/.

php runway make:record usuários

Se por acaso você tiver a tabela usuários com o seguinte esquema: id, nome, email, criado_em, atualizado_em, um arquivo semelhante ao seguinte será criado no arquivo app/records/RegistroUsuario.php:

<?php

declare(strict_types=1);

namespace app\records;

/**
 * Classe Active Record para a tabela de usuários.
 * @link https://docs.flightphp.com/awesome-plugins/active-record
 * 
 * @property int $id
 * @property string $nome
 * @property string $email
 * @property string $criado_em
 * @property string $atualizado_em
 * // você também pode adicionar relacionamentos aqui uma vez que os definir no array $relações
 * @property RegistroEmpresa $empresa Exemplo de um relacionamento
 */
class RegistroUsuario extends \flight\ActiveRecord
{
    /**
     * @var array $relações Define os relacionamentos para o modelo
     *   https://docs.flightphp.com/awesome-plugins/active-record#relationships
     */
    protected array $relações = [];

    /**
     * Construtor
     * @param mixed $conexãoBancoDados A conexão com o banco de dados
     */
    public function __construct($conexãoBancoDados)
    {
        parent::__construct($conexãoBancoDados, 'usuários');
    }
}

Exibir Todas as Rotas

Isso exibirá todas as rotas atualmente registradas com o Flight.

php runway routes

Se desejar visualizar apenas rotas específicas, você pode inserir uma flag para filtrar as rotas.

# Exibir apenas rotas GET
php runway routes --get

# Exibir apenas rotas POST
php runway routes --post

# etc.

Personalizando a Pista

Se você está criando um pacote para o Flight ou deseja 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 o seu projeto/pacote.

Para criar um comando, você simplesmente estende a classe AbstractBaseCommand e implementa, no mínimo, um método __construct e um método execute.

<?php

declare(strict_types=1);

namespace flight\commands;

class ComandoExemplo extends AbstractBaseCommand
{
    /**
     * Construtor
     *
     * @param array<string,mixed> $config Configuração JSON de .runway-config.json
     */
    public function __construct(array $config)
    {
        parent::__construct('make:example', 'Criar um exemplo para a documentação', $config);
        $this->argument('<gif-engracado>', 'O nome do gif engraçado');
    }

    /**
     * Executa a função
     *
     * @return void
     */
    public function execute(string $controlador)
    {
        $io = $this->app()->io();

        $io->info('Criando exemplo...');

        // Faça algo aqui

        $io->ok('Exemplo criado!');
    }
}

Consulte a Documentação do adhocore/php-cli para mais informações sobre como criar seus próprios comandos personalizados em sua aplicação Flight!

Awesome-plugins/tracy_extensions

Tracy Flight Painel de Extensões

Isto é um conjunto de extensões para tornar o trabalho com Flight um pouco mais rico.

Este é o Painel

Flight Bar

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

Flight Data Flight Database Flight Request

Clique here para ver o código.

Instalação

Execute composer require flightphp/tracy-extensions --dev e você estará pronto!

Configuração

Há pouca configuração necessária para começar. Você precisará iniciar o depurador Tracy antes de usar isto https://tracy.nette.org/en/guide:

<?php

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

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

Debugger::enable();
// Você pode precisar especificar seu ambiente com Debugger::enable(Debugger::DEVELOPMENT)

// se você usar conexões de banco de dados no seu app, há um
// wrapper PDO obrigatório para usar SOMENTE EM DESENVOLVIMENTO (não em produção, por favor!)
// Ele tem os mesmos parâmetros que 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, ela capturará o tempo, a consulta e os parâmetros

// Isso conecta os pontos
if(Debugger::$showBar === true) {
    // Isso precisa ser falso ou Tracy não pode renderizar :(
    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), você pode passar um array de dados de sessão para Tracy e ele automaticamente os exibirá. Você passa isso com a chave session_data no segundo parâmetro do construtor de 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 falso ou Tracy não pode renderizar :(
    Flight::set('flight.content_length', false);
    new TracyExtensionLoader(Flight::app(), [ 'session_data' => Flight::session()->getAll() ]);
}

// rotas e outras coisas...

Flight::start();

Latte

Se você tiver Latte instalado no seu projeto, você pode usar o painel Latte para analisar seus templates. Você pode passar a instância Latte para o construtor de TracyExtensionLoader com a chave latte no segundo parâmetro.


use Latte\Engine;

require 'vendor/autoload.php';

$app = Flight::app();

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

    // aqui é onde você adiciona o Painel Latte ao Tracy
    $latte->addExtension(new Latte\Bridges\Tracy\TracyExtension);
});

if(Debugger::$showBar === true) {
    // Isso precisa ser falso ou Tracy não pode renderizar :(
    Flight::set('flight.content_length', false);
    new TracyExtensionLoader(Flight::app());
}

Awesome-plugins/apm

Documentação do FlightPHP APM

Bem-vindo ao FlightPHP APM—seu treinador pessoal de desempenho do aplicativo! Este guia é o seu roteiro para configurar, usar e dominar o Monitoramento de Desempenho de Aplicações (APM) com FlightPHP. Seja caçando solicitações lentas ou apenas se empolgando com gráficos de latência, cobrimos tudo. Vamos tornar seu aplicativo mais rápido, seus usuários mais felizes e suas sessões de depuração uma brisa!

FlightPHP APM

Por que o APM Importa

Imagine isso: seu aplicativo é um restaurante movimentado. Sem uma forma de rastrear quanto tempo as ordens levam ou onde a cozinha está travando, você está adivinhando por que os clientes estão saindo irritados. O APM é seu subchef—ele observa cada etapa, desde solicitações de entrada até consultas de banco de dados, e marca qualquer coisa que esteja desacelerando você. Páginas lentas perdem usuários (estudos dizem que 53% abandonam se um site leva mais de 3 segundos para carregar!), e o APM ajuda a capturar esses problemas antes que eles machuquem. É uma paz de espírito proativa—menos momentos de “por que isso está quebrado?”, mais vitórias de “olha como isso roda bem!”.

Instalação

Comece com o Composer:

composer require flightphp/apm

Você precisará:

Bancos de Dados Suportados

O FlightPHP APM atualmente suporta os seguintes bancos de dados para armazenar métricas:

Você pode escolher o 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 (por exemplo, pdo_sqlite ou pdo_mysql).

Começando

Aqui está o seu passo a passo para o APM incrível:

1. Registrar o APM

Adicione isso ao seu index.php ou um arquivo services.php para começar a rastrear:

use flight\apm\logger\LoggerFactory;
use flight\Apm;

$ApmLogger = LoggerFactory::create(__DIR__ . '/../../.runway-config.json');
$Apm = new Apm($ApmLogger);
$Apm->bindEventsToFlightInstance($app);

// Se você estiver adicionando uma conexão de banco de dados
// Deve ser PdoWrapper ou PdoQueryCapture das Extensões Tracy
$pdo = new PdoWrapper('mysql:host=localhost;dbname=example', 'user', 'pass', null, true); // <-- True necessário para habilitar o rastreamento no APM.
$Apm->addPdoConnection($pdo);

O que está acontecendo aqui?

Dica Profissional: Amostragem Se seu aplicativo estiver movimentado, registrar toda solicitação pode sobrecarregar as coisas. Use uma taxa de amostragem (0.0 a 1.0):

$Apm = new Apm($ApmLogger, 0.1); // Registra 10% das solicitações

Isso mantém o desempenho ágil enquanto ainda fornece dados sólidos.

2. Configure-o

Execute isso para criar seu .runway-config.json:

php vendor/bin/runway apm:init

O que isso faz?

Esse processo também perguntará se você deseja executar as migrações para essa configuração. Se você estiver 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 processa-as em um destino estruturado para o painel. Mantém as coisas organizadas!

3. Processar 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?

Mantenha-o Executando Para aplicativos ao vivo, você desejará processamento contínuo. Aqui estão suas opções:

Por que se incomodar? Sem o worker, seu painel está vazio. É a ponte entre logs brutos e insights acionáveis.

4. Iniciar o Painel

Veja os sinais vitais do seu aplicativo:

php vendor/bin/runway apm:dashboard

O que isso é?

Personalize-o:

php vendor/bin/runway apm:dashboard --host 0.0.0.0 --port 8080 --php-path=/usr/local/bin/php

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, pois provavelmente há firewalls e outras medidas de segurança no lugar. Aqui estão algumas opções:

Quer um painel diferente?

Você pode criar seu próprio painel se quiser! Olhe no diretório vendor/flightphp/apm/src/apm/presenter para ideias sobre como apresentar os dados para o seu próprio painel!

Recursos do Painel

O painel é sua sede do APM—aqui está o que você verá:

Extras:

Exemplo: Uma solicitação para /users pode mostrar:

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 isso aparece? Nos detalhes da solicitação do painel sob “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 para baixo!

Monitoramento de Banco de Dados

Rastreie consultas PDO assim:

use flight\database\PdoWrapper;

$pdo = new PdoWrapper('sqlite:/path/to/db.sqlite', null, null, null, true); // <-- True necessário para habilitar o rastreamento no APM.
$Apm->addPdoConnection($pdo);

O que Você Obtém:

Atenção:

Exemplo de Saída:

Opções do Worker

Ajuste o worker ao seu gosto:

Exemplo:

php vendor/bin/runway apm:worker --daemon --batch_size 100 --timeout 3600

Executa por uma hora, processando 100 métricas de cada vez.

ID de Solicitação no Aplicativo

Cada solicitação tem um ID de solicitação único para rastreamento. Você pode usar esse ID no seu aplicativo para correlacionar logs e métricas. Por exemplo, você pode adicionar o ID de solicitação a uma página de erro:

Flight::map('error', function($message) {
    // Obtenha o ID de solicitação do cabeçalho de resposta X-Flight-Request-Id
    $requestId = Flight::response()->getHeader('X-Flight-Request-Id');

    // Além disso, você poderia buscá-lo da variável Flight
    // Esse método não funcionará bem em plataformas swoole ou outras assíncronas.
    // $requestId = Flight::get('apm.request_id');

    echo "Error: $message (Request ID: $requestId)";
});

Atualizando

Se você estiver 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 banco de dados do seu APM for grande, essas migrações podem levar algum tempo para executar. Você pode querer executar esse comando durante horas de pico baixo.

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 mais antigos que 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 mais antigos que 7 dias do banco de dados.

Solução de Problemas

Preso? Tente esses:

Awesome-plugins/tracy

Tracy

Tracy é um incrível manipulador de erros que pode ser usado com Flight. Possui vários painéis que podem ajudá-lo a depurar sua aplicação. É também muito fácil de estender e adicionar seus próprios painéis. A equipe do Flight criou alguns painéis especificamente para projetos do Flight com o plugin flightphp/tracy-extensions.

Instalação

Instale com o compositor. E na verdade, você vai querer instalar isso sem a versão de desenvolvimento, já que o Tracy vem com um componente de tratamento de erros de produção.

composer require tracy/tracy

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


require 'vendor/autoload.php';

use Tracy\Debugger;

// Habilitar Tracy
Debugger::enable();
// Debugger::enable(Debugger::DEVELOPMENT) // às vezes você precisa ser explícito (também Debugger::PRODUCTION)
// Debugger::enable('23.75.345.200'); // você também pode fornecer um array de endereços IP

// Aqui é onde os erros e exceções serão registrados. Certifique-se de que este diretório exista e seja gravável.
Debugger::$logDirectory = __DIR__ . '/../log/';
Debugger::$strictMode = true; // exibir todos os erros
// Debugger::$strictMode = E_ALL & ~E_DEPRECATED & ~E_USER_DEPRECATED; // todos os erros exceto avisos obsoletos
if (Debugger::$showBar) {
    $app->set('flight.content_length', false); // se a barra do Debugger estiver visível, então o comprimento do conteúdo não pode ser definido pelo Flight

    // Isso é específico para a Extensão do Tracy para o Flight se você incluiu isso
    // caso contrário, comente isso.
    new TracyExtensionLoader($app);
}

Dicas Úteis

Ao depurar seu código, existem algumas funções muito úteis para exibir dados para você.

Awesome-plugins/active_record

Flight Active Record

Um registro ativo é o mapeamento de uma entidade de banco de dados para um objeto PHP. Falando de forma simples, se você tiver uma tabela de usuários em seu banco de dados, você pode "traduzir" uma linha dessa tabela para uma classe User e um objeto $user em 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 recomendável 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 configurá-la assim
        parent::__construct($database_connection, 'users');
        // ou assim
        parent::__construct($database_connection, null, [ 'table' => 'users']);
    }
}

Agora assista 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('uma senha legal');
$user->insert();
// ou $user->save();

echo $user->id; // 1

$user->name = 'Joseph Mamma';
$user->password = password_hash('uma nova senha legal!!!');
$user->insert();
// não pode usar $user->save() aqui ou ele achará que é uma atualização!

echo $user->id; // 2

E foi assim tão fácil adicionar um novo usuário! Agora que há uma linha de usuário no banco de dados, como você a extrai?

$user->find(1); // encontra id = 1 no banco de dados e o retorna.
echo $user->name; // 'Bobby Tables'

E se você quiser encontrar todos os usuários?

$users = $user->findAll();

E quanto a uma certa condição?

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

Veja quanto é divertido isso? Vamos instalá-lo e começar!

Instalação

Basta instalar com o Composer

composer require flightphp/active-record 

Uso

Isso pode ser utilizado como uma biblioteca autônoma ou com o Framework PHP Flight. Totalmente a seu critério.

Autônomo

Basta garantir que você passe 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 configurar 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 Framework PHP Flight, pode registrar a classe ActiveRecord como um serviço, mas honestamente, você não precisa.

Flight::register('user', 'User', [ $pdo_connection ]);

// então você pode usá-la assim em um controlador, uma função, etc.

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

Métodos runway

runway é uma ferramenta CLI para o Flight que possui um comando personalizado para esta biblioteca.

# Uso
php runway make:record nome_da_tabela_do_banco_de_dados [nome_da_classe]

# 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 os relacionamentos para o modelo
     *   https://docs.flightphp.com/awesome-plugins/active-record#relationships
     */
    protected array $relations = [
        // 'nome_da_relação' => [ self::HAS_MANY, 'ClasseRelacionada', 'chave_estrangeira' ],
    ];

    /**
     * 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 atribui ao objeto atual. Se você passar um $id de algum tipo, ele realizará uma busca na chave primária com esse valor. Se nada for passado, ele apenas encontrará o primeiro registro na tabela.

Além disso, você pode passar outros métodos auxiliares para consultar sua tabela.

// encontrar um registro com algumas condições previamente
$user->notNull('password')->orderBy('id DESC')->find();

// encontrar um registro por um id específico
$id = 123;
$user->find($id);

findAll(): array<int,ActiveRecord>

Encontra todos os registros na tabela que você especificar.

$user->findAll();

isHydrated(): boolean (v0.4.0)

Retorna true se o registro atual tiver sido 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), pode definir o valor da chave primária antes de inserir de duas maneiras.

$user = new User($pdo_connection, [ 'primaryKey' => 'uuid' ]);
$user->uuid = 'algum-uuid';
$user->name = 'demo';
$user->password = md5('demo');
$user->insert(); // ou $user->save();

ou você pode fazer com que a chave primária seja gerada automaticamente para você por meio 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 chave primária assim 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 o rowid e o banco de dados a gerará para você, mas não persistirá porque esse campo pode não existir em sua tabela. Por isso, é recomendável usar o evento para gerenciar 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, ele irá inserir.

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

Nota: Se você tiver relacionamentos definidos na classe, ele salvará recursivamente essas relações também, se foram definidas, instanciadas e possuem dados sujos para atualizar. (v0.4.0 e superior)

delete(): boolean

Exclui o registro atual do banco de dados.

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

Você também pode excluir vários registros executando uma pesquisa previamente.

$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é esse ponto.

$user->email = 'test@example.com'; // agora o e-mail é considerado "sujo" pois foi alterado.
$user->update();
// agora não há dados sujos porque foram atualizados e persistidos no banco de dados.

$user->password = password_hash('nova_senha'); // agora isso é sujo
$user->dirty(); // passando nada limpará todas as entradas sujas.
$user->update(); // nada será atualizado porque nada foi capturado como sujo.

$user->dirty([ 'name' => 'algo', 'password' => password_hash('uma senha diferente') ]);
$user->update(); // tanto nome quanto senha são atualizados.

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

Este é um alias para o método dirty(). É um pouco mais claro o que você está fazendo.

$user->copyFrom([ 'name' => 'algo', 'password' => password_hash('uma senha diferente') ]);
$user->update(); // tanto nome quanto senha são atualizados.

isDirty(): boolean (v0.4.0)

Retorna true se o registro atual tiver sido 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 é muito bom para usar em tipos de comportamento de loop. Se você passar true, isso 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 folha limpa
    $user_company->user_id = $user->id;
    $user_company->company_id = $some_company_id;
    $user_company->insert();
}

getBuiltSql(): string (v0.4.1)

Depois de executar um método find(), findAll(), insert(), update(), ou save(), você pode obter o SQL que foi construído e usá-lo para fins de depuração.

Métodos de Consulta SQL

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

Você pode selecionar apenas algumas das colunas em uma tabela, se desejar (é mais eficiente em tabelas realmente 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 a 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 (não pode definir parâmetros 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 que são conhecidos como injeções de SQL. Existem muitos artigos online, por favor, pesquise "sql injection attacks php" e você encontrará muitos artigos sobre esse assunto. A maneira adequada de lidar com isso com esta biblioteca é, em vez desse método where(), você faria algo mais parecido com $user->eq('id', $id)->eq('name', $name)->find(); Se você absolutamente precisa fazer isso, a biblioteca PDO possui $pdo->quote($var) para escapar 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)

Classifique 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 como em 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 o método startWrap() e endWrap() ou preenchendo o 3º parâmetro da condição após o campo e o valor.

// Método 1
$user->eq('id', 1)->startWrap()->eq('name', 'demo')->or()->eq('name', 'test')->endWrap('OR')->find();
// Isso será avaliado para `id = 1 AND (name = 'demo' OR name = 'test')`

// Método 2
$user->eq('id', 1)->eq('name', 'demo', 'OR')->find();
// Isso será avaliado para `id = 1 OR name = 'demo'`

Relacionamentos

Você pode definir vários tipos de relacionamentos usando esta biblioteca. Você pode definir relacionamentos um->muitos e um->um entre tabelas. Isso requer uma configuração extra na classe previamente.

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 será referenciada

        // 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 se junta apenas à chave primária do modelo "outro"

        // opcional
        [ 'eq' => [ 'client_id', 5 ], 'select' => 'COUNT(*) as count', 'limit' 5 ], // condições adicionais que você deseja 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 essa relação de volta para si mesma 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 muito facilmente!

$user = new User($pdo_connection);

// encontrar o usuário mais recente.
$user->notNull('id')->orderBy('id desc')->find();

// obter contatos usando a relação:
foreach($user->contacts as $contact) {
    echo $contact->id;
}

// ou podemos fazer o caminho inverso.
$contact = new Contact();

// encontrar um contato
$contact->find();

// obter usuário usando a relação:
echo $contact->user->name; // este é o nome do usuário

Bem legal, nã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 apenas anexar ao objeto que então seria passado para, digamos, um modelo.

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 faz referência a ele como uma propriedade normal do objeto.

echo $user->page_view_count;

Eventos

Mais uma super incrível característica sobre esta biblioteca é sobre eventos. Eventos são acionados em determinados momentos com base em certos métodos que você chama. Eles são muito úteis para configurar dados automaticamente para você.

onConstruct(ActiveRecord $ActiveRecord, array &config)

Isso é realmente útil se você precisar definir uma conexão padrão ou algo assim.

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

//
//
//

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

    protected function onConstruct(self $self, array &$config) { // não se esqueça da 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 só é útil se você precisar de uma manipulação de consulta toda 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 do seu agrado
        $self->gte('id', 0); 
    } 
}

afterFind(ActiveRecord $ActiveRecord)

Esse é provavelmente mais útil se você sempre precisar executar alguma lógica toda vez que este registro for buscado. Você precisa descriptografar algo? Você precisa executar uma consulta de contagem personalizada a cada vez (não performático, mas tudo bem)?

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 só útil se você precisar de uma manipulação de consulta toda 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 do seu agrado
        $self->gte('id', 0); 
    } 
}

afterFindAll(array<int,ActiveRecord> $results)

Semelhante ao afterFind(), mas você pode fazê-lo com 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 no afterFind()
        }
    } 
}

beforeInsert(ActiveRecord $ActiveRecord)

Realmente útil se você precisar definir alguns valores padrão toda 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 sensatos
        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 mudar dados após serem inseridos?

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 qualquer outra coisa....
    } 
}

beforeUpdate(ActiveRecord $ActiveRecord)

Realmente útil se você precisar definir alguns valores padrão cada vez que uma atualização ocorrer.

class User extends flight\ActiveRecord {

    public function __construct($database_connection)
    {
        parent::__construct($database_connection, 'users');
    }

    protected function beforeInsert(self $self) {
        // defina alguns padrões sensatos
        if(!$self->updated_date) {
            $self->updated_date = gmdate('Y-m-d');
        }
    } 
}

afterUpdate(ActiveRecord $ActiveRecord)

Talvez você tenha um caso de uso para mudar dados após serem atualizados?

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 qualquer outra coisa....
    } 
}

beforeSave(ActiveRecord $ActiveRecord)/afterSave(ActiveRecord $ActiveRecord)

Isso é útil se você quiser que eventos aconteçam tanto quando inserções quanto atualizações ocorrerem. Vou poupar você da longa explicação, mas tenho certeza de que você pode imaginar 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 'Ele foi um soldado valente... :cry-face:';
    } 
}

Gerenciamento de Conexão de Banco de Dados

Quando você estiver usando esta biblioteca, pode definir a conexão com o banco de dados de algumas maneiras diferentes. Você pode definir a conexão no construtor, pode defini-la via uma variável de configuração $config['connection'] ou 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ê deseja evitar configurar sempre uma $database_connection toda vez que chamar um registro ativo, existem 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, nenhum argumento é necessário!
$user = new User();

Nota: Se você planeja fazer testes unitários, fazer assim 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 complicado.

Se você precisar atualizar a conexão do 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, pode redefinir a conexão com $your_record->setDatabaseConnection($pdo_connection).

Contribuindo

Por favor, contribua. :D

Configuração

Quando você contribuir, certifique-se de executar composer test-coverage para manter 100% de cobertura de testes (isso não é verdadeiro teste unitário, mais como teste de integração).

Além disso, certifique-se de executar composer beautify e composer phpcs para corrigir quaisquer erros de linting.

Licença

MIT

Awesome-plugins/latte

Latte

Latte é um mecanismo de modelo 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.


use Latte\Engine as LatteEngine;

require 'vendor/autoload.php';

$app = Flight::app();

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

    // Aqui é onde o Latte irá armazenar em cache seus modelos para acelerar as coisas
    // Uma coisa legal sobre o Latte é que ele atualiza automaticamente seu
    // cache quando você faz alterações nos seus modelos!
    $latte->setTempDirectory(__DIR__ . '/../cache/');

    // Diga ao Latte onde o diretório raiz para suas visualizações estará
    // $app->get('flight.views.path') é definido no arquivo config.php
    //   Você também pode fazer algo como `__DIR__ . '/../views/'`
    $latte->setLoader(new \Latte\Loaders\FileLoader($app->get('flight.views.path')));
});

Exemplo de Layout Simples

Aqui está um exemplo simples de um arquivo de layout. Este é o arquivo que será usado para envolver todas as suas outras visualizações.

<!-- app/views/layout.latte -->
<!doctype html>
<html lang="en">
    <head>
        <title>{$title ? $title . ' - '}My App</title>
        <link rel="stylesheet" href="style.css">
    </head>
    <body>
        <header>
            <nav>
                <!-- seus elementos de navegação aqui -->
            </nav>
        </header>
        <div id="content">
            <!-- Aqui está a mágica -->
            {block content}{/block}
        </div>
        <div id="footer">
            &copy; Direitos autorais
        </div>
    </body>
</html>

E agora temos seu arquivo que será renderizado dentro desse bloco de conteúdo:

<!-- app/views/home.latte -->
<!-- Isso diz ao Latte que este arquivo está "dentro" do arquivo layout.latte -->
{extends layout.latte}

<!-- Este é o conteúdo que será renderizado dentro do layout dentro do bloco de conteúdo -->
{block content}
    <h1>Página Inicial</h1>
    <p>Bem-vindo ao meu aplicativo!</p>
{/block}

Então, quando você for renderizar isso dentro da sua função ou controlador, você faria algo assim:

// rota simples
Flight::route('/', function () {
    Flight::latte()->render('home.latte', [
        'title' => 'Página Inicial'
    ]);
});

// ou se estiver usando um controlador
Flight::route('/', [HomeController::class, 'index']);

// HomeController.php
class HomeController
{
    public function index()
    {
        Flight::latte()->render('home.latte', [
            'title' => 'Página Inicial'
        ]);
    }
}

Veja a Documentação do Latte para mais informações sobre como usar o Latte em todo o seu potencial!

Awesome-plugins/awesome_plugins

Plugins Incríveis

Flight é extremamente extensível. Existem vários plugins que podem ser usados para adicionar funcionalidade à 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.

Documentação da API

A documentação da API é crucial para qualquer API. Ela ajuda os desenvolvedores a entenderem como interagir com sua API e o que esperar em troca. Existem algumas ferramentas disponíveis para ajudar a gerar documentação da API para seus projetos Flight.

Monitoramento de Desempenho de Aplicações (APM)

Monitoramento de Desempenho de Aplicações (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ários ferramentas de APM que podem ser usadas com Flight.

Autorização/Permissões

Autorização e Permissões são cruciais para qualquer aplicação que exija controles para definir quem pode acessar o quê.

Caching

Caching é uma ótima forma de acelerar sua aplicação. Existem vários bibliotecas de caching que podem ser usadas com Flight.

CLI

Aplicações CLI são uma ótima forma de interagir com sua aplicação. Você pode usá-las para gerar controladores, exibir todas as rotas e muito mais.

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 muito mais.

Debugging

Debugging é crucial quando você está desenvolvendo em seu ambiente local. Existem alguns plugins que podem elevar sua experiência de debugging.

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 outras são ORMs completos.

Criptografia

Criptografia é crucial para qualquer aplicação que armazena dados sensíveis. Criptografar e descriptografar os dados não é terrivelmente difícil, mas armazenar corretamente a chave de criptografia can be difficult. O mais importante é nunca armazenar sua chave de criptografia em um diretório público ou commitá-la em seu repositório de código.

Fila de Tarefas

Filas de tarefas 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.

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 o estado e informações de login.

Templating

Templating é o núcleo de qualquer aplicação web com uma interface de usuário. Existem vários engines de templating que podem ser usados com Flight.

Integração com WordPress

Quer usar Flight no seu projeto WordPress? Há um plugin prático para isso!

Contribuindo

Tem um plugin que você 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 disponíveis na internet sobre Flight. Veja abaixo diferentes recursos que você pode usar para aprender mais sobre Flight.

Artigos e Escritos

Vídeos e Tutoriais

Examples

Precisa de um início rápido?

Você tem duas opções para começar um novo projeto Flight:

Exemplos contribuídos pela comunidade:

Precisa de Inspiração?

Embora esses não sejam oficialmente patrocinados pela equipe Flight, eles podem lhe dar ideias sobre como estruturar seus próprios projetos que são construídos com o Flight!

Quer Compartilhar Seu Próprio Exemplo?

Se você tem um projeto que gostaria de compartilhar, por favor envie um pull request para adicioná-lo a esta lista!

Install/install

Instalação

Baixar os arquivos

Certifique-se de ter o PHP instalado em seu sistema. Caso contrário, clique aqui para obter instruções sobre como instalá-lo em seu sistema.

Se estiver usando Composer, você pode executar o seguinte comando:

composer require flightphp/core

OU você pode baixar os arquivos diretamente e extrair para o diretório da web.

Configurar seu Servidor Web

Servidor de Desenvolvimento PHP Integrado

Esta é de longe a maneira mais simples de começar. Você pode usar o servidor integrado para executar sua aplicação e até mesmo usar SQLite para um banco de dados (desde que o sqlite3 esteja instalado em seu sistema) e não exigir muito! Basta executar o seguinte comando uma vez que o PHP estiver instalado:

php -S localhost:8000

Em seguida, abra seu navegador e vá para http://localhost:8000.

Se quiser definir o diretório raiz do seu projeto como um diretório diferente (Por ex: seu projeto é ~/myproject, mas sua raiz do documento é ~/myproject/public/), você pode executar o seguinte comando uma vez que estiver no diretório ~/myproject:

php -S localhost:8000 -t public/

Então, abra seu navegador e vá para http://localhost:8000.

Apache

Certifique-se de que o Apache já esteja instalado em seu sistema. Caso contrário, pesquise como instalar o Apache em 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 precisar usar o flight em um subdiretório, adicione a linha RewriteBase /subdir/ logo após RewriteEngine On.

Nota: Se desejar proteger todos os arquivos do servidor, como um arquivo db ou env. Coloque isso em seu arquivo .htaccess:

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

Nginx

Certifique-se de que o Nginx já esteja instalado em seu sistema. Caso contrário, pesquise como instalar o Nginx em seu sistema.

Para o Nginx, adicione o seguinte à declaração do seu servidor:

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

Crie seu arquivo index.php

<?php

// Se estiver usando o Composer, requer o autoloader.
require 'vendor/autoload.php';
// se 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 'olá mundo!';
});

// Por fim, inicie o framework.
Flight::start();

Instalando o PHP

Se você já tem o php instalado em seu sistema, vá em frente e pule estas instruções e vá para a seção de download

Claro! Aqui estão as instruções para instalar o PHP no macOS, Windows 10/11, Ubuntu e Rocky Linux. Também incluirei detalhes sobre como instalar diferentes versões do PHP.

macOS

Instalando o PHP usando o Homebrew

  1. Instalar o Homebrew (caso ainda não esteja instalado):

    • Abra o Terminal e execute:
      /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
  2. Instalar o PHP:

    • Instalar 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
  3. Alternar 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

Windows 10/11

Instalando o PHP manualmente

  1. Baixar o PHP:

    • Visite PHP for Windows e baixe a versão mais recente ou uma versão específica (por ex., 7.4, 8.0) como um arquivo zip não seguro para threads.
  2. Extrair o PHP:

    • Extraia o arquivo zip baixado para C:\php.
  3. Adicionar o PHP ao PATH do sistema:

    • Vá para 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.
  4. Configurar o PHP:

    • Copie php.ini-development para php.ini.
    • Edite php.ini para configurar o PHP conforme necessário (por ex., definindo extension_dir, habilitando extensões).
  5. Verificar a instalação do PHP:

    • Abra o Prompt de Comando e execute:
      php -v

Instalando Múltiplas Versões do PHP

  1. Repita os passos acima para cada versão, colocando cada uma em um diretório separado (por ex., C:\php7, C:\php8).

  2. Alternar 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

  1. Atualizar listas de pacotes:

    • Abra o Terminal e execute:
      sudo apt update
  2. Instalar o PHP:

    • Instalar 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
  3. Instalar módulos adicionais (opcional):

    • Por exemplo, para instalar suporte ao MySQL:
      sudo apt install php8.1-mysql
  4. Alternar entre as versões do PHP:

    • Usar update-alternatives:
      sudo update-alternatives --set php /usr/bin/php8.1
  5. Verificar a versão instalada:

    • Execute:
      php -v

Rocky Linux

Instalando o PHP usando yum/dnf

  1. Ativar o repositório EPEL:

    • Abra o Terminal e execute:
      sudo dnf install epel-release
  2. Instalar o repositório do Remi:

    • Execute:
      sudo dnf install https://rpms.remirepo.net/enterprise/remi-release-8.rpm
      sudo dnf module reset php
  3. Instalar 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
  4. Alternar entre as versões do PHP:

    • Usar o comando de módulo dnf:
      sudo dnf module reset php
      sudo dnf module enable php:remi-8.0
      sudo dnf install php
  5. Verificar a versão instalada:

    • Execute:
      php -v

Notas Gerais

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:

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:

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