Qualidade de código
PHPStan no level 7 — e por que isso é um ponto forte
A maioria dos projetos Laravel para no level 5 ou 6. O kit roda no 7, com zero erros e sem baseline: não há @phpstan-ignore espalhado, não há phpstan-baseline.neon escondendo dívida.
O que o level 7 pega e o 6 não pega, na prática:
- Nulo não checado.
Filament::getCurrentPanel()devolve?Panel;auth()->user()devolve?User. No level 6 você chama método neles e passa. No 7, precisa provar que existe. - Tipo largo do vendor entrando no seu código.
session()émixed,env()ébool|string, os getters do Shield são?array. O 7 obriga a estreitar na fronteira, uma vez, em vez de torcer para o valor ser o esperado em cada uso. list<T>vsarray<int,T>.filter()emap()preservam chave. Um array com buracos entregue onde se esperava lista é bug que só aparece nojson_encode— vira objeto em vez de array, e o front quebra.
Subir de 6 para 7 expôs 29 erros reais no kit, e um deles era bug latente de verdade: um Convite|null com método chamado direto. Todos corrigidos na origem — nenhum silenciado.
⚠️ Ponto de atenção ao implementar no seu projeto
O level 7 vale para o código que você escrever também.
composer testrodaphpstan analysee reprova o build inteiro.O que mais aparece quando alguém começa a escrever no kit:
Você escreve O que o PHPStan cobra auth()->user()->idprove que há usuário: auth()->user()?->id, ou umifantesFilament::getTenant()->nome?Model— useinstanceof Tenantcomo guarda->filter()->all()num@return list<string>array_values()no fimenv('ALGUMA_COISA')direto numstr_*(string) env(...), ouconfig()com default tipadométodo sem tipo de retorno declare o tipo; o kit exige em tudo Não resolva com
@phpstan-ignorenem baseline. O kit tem exatamente duas exceções emphpstan.neon: uma para um macro de vendor resolvido em runtime (simpleLightbox()), outra para a anotação insatisfazível decustomMyProfilePage()do filament-breezy — cada uma com o motivo, as alternativas testadas e descartadas, e o teste que cobre o ponto de verdade. Esse é o padrão: se precisar de exceção, ela vem com a justificativa e com o teste que a substitui.Se quiser afrouxar no seu projeto, é uma linha em
phpstan.neon. Mas saiba o que está trocando: os 29 erros acima eram todos reais.
FilaCheck: o lint que só entende de Filament
composer filament:check roda o laraveldaily/filacheck — 17 regras que o Pint e o PHPStan não têm como ter: método depreciado da API do Filament, namespace errado de action, chamada que mudou entre versões. Ele entra no composer test junto com o pint e o phpstan, então a CI reprova o mesmo que a sua máquina.
Ao ser adotado, ele encontrou 7 problemas preexistentes no próprio kit — seis métodos de teste depreciados e um ImageColumn::size() — todos corrigidos.
Rector: upgrade de major, não lint
O kit tem quatro ferramentas de qualidade, em quatro eixos — e só três estão no gate:
| Ferramenta | Eixo | Ao achar problema | Roda |
|---|---|---|---|
| Pint | estilo | corrige | sempre (gate) |
| PHPStan + larastan | tipos | reporta | sempre (gate), level 7 |
| FilaCheck | API do Filament | reporta | sempre (gate) |
| Rector | reescrita de código | muda semântica | sob demanda |
composer refactor:preview e composer refactor:apply não estão no composer test — e isso é deliberado.
Para que o Rector serve aqui: upgrade de major. Laravel 13 → 14, PHP 8.4 → 8.5. O rector.php da raiz nasce sem nenhum set ligado, e traz, num bloco de comentário, qual set ligar em cada caso. O fluxo é: descomentar o set → composer refactor:preview → ler o diff inteiro → composer refactor:apply → composer test → desligar o set de novo.
Por que ele fica fora do gate — foi medido, não opinado. Com os sets de qualidade do Laravel ligados, o Rector reescreveria 103 arquivos deste projeto. Os três maiores motivos:
| Regra | Arquivos | O que propõe |
|---|---|---|
EloquentMagicMethodToQueryBuilderRector | 35 | User::find() → User::query()->find() |
AddClosureVoidReturnTypeWhereNoReturnRector | 26 | : void em closure |
AppToResolveRector | 21 | app() → resolve() |
São opinião de estilo, não correção. Num kit cujo produto é o código-exemplo legível, User::find() e app() são o idioma que o ecossistema lê sem parar.
E há um caso que fecha a questão. CarbonToDateFacadeRector propõe, no InfraPanelProvider:
- Carbon::now()->subDays(...)
+ Date::now()->subDays(...)
E isso quebra, por três fatos verificáveis:
now()éDate::now()—Illuminate/Foundation/helpers.php:623- O kit faz
Date::use(CarbonImmutable::class)—KitServiceProvider.php:57 FilamentExceptionsPlugin::modelPruneInterval()exigeCarbonmutável
O PHPStan level 7 já reportou exatamente esse erro quando o código usava now(). O Carbon::now() explícito é a correção — e o Rector a desfaria.
Ferramenta de qualidade que reverte a correção de outra não é gate, é disputa — e o build passaria a depender de qual das duas rodou por último.
tests/Kit/QualidadeDeCodigoTest.php fixa isso: falha se o Rector entrar no composer test, ou se um set de qualidade for ligado.
Upgrade de Filament é outra ferramenta. Não existe regra de Filament no driftingly/rector-laravel — busca por “filament” no pacote devolve zero. Não é lacuna: o Filament distribui a própria ferramenta, também baseada em Rector.
composer upgrade:filament # roda o vendor/bin/filament-v5 — o filament/upgrade já está no require-dev
Ela é mantida em lockstep com o framework — quem escreve as regras é quem quebra a API.
A leitura completa das quatro ferramentas está em wikis/qualidade-de-codigo.md.
Os testes do kit
O kit traz sua própria suíte, isolada em tests/Kit/ — acesso aos três painéis, telas de infra e admin de pé, invariantes da fundação (uuid, gates, auditoria) e o contrato da camada de IA.
Ela fica separada da sua de propósito: depois de um kit:update você quer saber se a fundação continua íntegra, sem esperar a suíte do seu negócio.
composer test:kit # em paralelo — ~3 min
composer test:kit:serial # em série, para investigar falha
php artisan test --testsuite=Feature # só os SEUS testes
Roda em paralelo por padrão. Medido nesta suíte: 12m26s → ~3min (20 núcleos), mesmos casos e mesmas asserções. Cada worker tem o próprio banco, porque o phpunit.xml usa SQLite :memory:, que é por processo.
Se uma falha aparecer só em paralelo, é sinal de teste que depende de ordem ou de estado compartilhado — composer test:kit:serial isola isso, e a diferença entre os dois é o diagnóstico.
Por que
--testsuitee não--group=kit: opest-plugin-browsersobe o Playwright já na coleta, ao parsear qualquer arquivo comvisit()— antes de qualquer filtro de grupo ser consultado. Num projeto recém-instalado, sem os browsers baixados,--group=kitmorre emPlaywrightNotInstalledExceptionsem rodar um único teste.
Argumento extra precisa de
--:composer test:kit --parallelé engolido em silêncio pelo Composer; o que funciona écomposer test:kit -- --parallel. Como o paralelo já é o padrão, você não precisa disso — mas vale saber para qualquer outra flag.
Seus testes vão em tests/Feature e tests/Unit, como de costume — o kit não encosta neles.
As imagens do README saem de um teste
As capturas de tela deste README não são feitas à mão. Elas nascem de tests/BrowserTenancy/CapturaDeArteTest.php, na mesma suíte que prova que as telas funcionam:
composer art
O comando navega de verdade, salva os PNG, publica em art/, gera as thumbs de art/thumbs/ e monta o GIF do fluxo. É o único jeito que encontramos de a documentação não envelhecer: ninguém refaz quinze imagens a cada release, e o resultado é um README mostrando uma versão do kit que não existe mais.
| Etapa | O que faz |
|---|---|
npm run build + view:cache | pré-requisitos duros da suíte de navegador |
KIT_ART=1 pest tests/BrowserTenancy/CapturaDeArteTest.php | navega e escreve os PNG em tests/Browser/Screenshots/ (caminho fixo do plugin) |
php artisan kit:arte | copia para art/, redimensiona as thumbs e monta o GIF |
Três decisões que valem saber antes de mexer:
KIT_ART=1não é enfeite. É variável só de teste — não existe emconfig/nem no.env.example; o próprio arquivo de teste a lê. Sem a variável o arquivo é skipped. Ele escreve emart/, e uma suíte de CI que suja a árvore de trabalho é pior que uma suíte lenta.- As medidas são fixas: 1400x875 no cheio, 760x475 na thumb. É a proporção das imagens que já estavam no
art/, e a galeria põe duas thumbs por linha — thumb com outra proporção desalinha a tabela. - O GIF é slideshow, montado com
ffmpega partir de três quadros. O plugin de navegador não grava vídeo, e quadro capturado é o que dá para reproduzir de forma determinística. Semffmpegno PATH o comando avisa e segue: as imagens estáticas já foram publicadas.
Precisa só refazer as thumbs, sem repetir a navegação? php artisan kit:arte --sem-gif.
Como os testes são pensados: varredura SFDIPOT
Toda feature nova passa por uma varredura SFDIPOT antes de virar caso de teste. A heurística, criada por James Bach, divide o sistema em sete perspectivas para que nenhuma dimensão seja esquecida na especificação:
| Letra | Perspectiva | O que cobre |
|---|---|---|
| S — Structure | Estrutura | Código, arquivos, componentes físicos ou lógicos |
| F — Function | Função | O que o software faz, suas funcionalidades |
| D — Data | Dados | O que o sistema processa, armazena ou manipula |
| I — Interfaces | Interfaces | Telas, APIs, integrações, entradas e saídas |
| P — Platform | Plataforma | Sistema operacional, hardware ou ambiente onde roda |
| O — Operations | Operações | Como o usuário ou administrador usa o sistema no dia a dia |
| T — Time | Tempo | Concorrência, desempenho, histórico ou a sequência dos eventos |
O benefício está em não derivar os testes só do “caminho feliz”. O que escapa raramente é mais um caso a mais — geralmente é uma dimensão inteira (dados, plataforma, tempo, operações) que ninguém lembrou de cobrir. A varredura força essa revisão no plano, antes do código existir.