From filament-ai-skills
Análise técnica profunda de código PHP/Laravel já implementado (não staged). 3 dimensões: segurança (OWASP), qualidade (SOLID, CC, DRY), padrões (PHP 8.4, Laravel 12, Filament v5). Foco em "como melhorar" — refactor, design, arquitetura. Gera relatório com scores e plano de ação. **Use pre-commit-review pra checklist rápido antes de commit.** Ativa: analisar implementação em profundidade, revisar feature pronta, audit de segurança, refatorar código existente, melhorar arquitetura, review profundo, análise completa, avaliar qualidade de feature, scoring de código.
How this skill is triggered — by the user, by Claude, or both
Slash command
/filament-ai-skills:deep-code-reviewThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Análise técnica profunda em 3 dimensões: **Segurança**, **Qualidade**, **Padrões** (PHP 8.4, Laravel 12, Filament v5).
Análise técnica profunda em 3 dimensões: Segurança, Qualidade, Padrões (PHP 8.4, Laravel 12, Filament v5).
git diff --name-only HEAD~1 HEAD # recentes
git diff --name-only main...HEAD # feature
git diff --name-only --cached # staged
Criticidade: Models/, Controllers/, Services/, Actions/, migrations/ = Alta. Filament/Resources/, Jobs/, tests/ = Média.
Injeção (SQL/Command/LDAP):
grep -n 'DB::select\|DB::insert\|DB::update\|DB::delete' arquivo.php
grep -n 'exec\|shell_exec\|system\|passthru\|proc_open' arquivo.php
grep -n '".*\$[a-zA-Z]' arquivo.php | grep -i 'where\|select\|insert'
// ❌ DB::select("SELECT * FROM users WHERE name = '$name'");
// ✅ DB::select('SELECT * FROM users WHERE name = ?', [$name]);
// ✅ Model::query()->where('name', $name)->get();
Mass Assignment:
grep -n 'create\|fill\|update' arquivo.php | grep 'request->all\|request->input\b'
// ❌ User::create($request->all());
// ✅ User::create($request->validated());
// ✅ User::create($request->only(['name', 'email']));
XSS:
grep -n '{!!' arquivo.php
grep -n 'HtmlString\|->html(' arquivo.php
{!! !!} só pra HTML confiável gerado pelo sistema, nunca input do usuário.
Broken Access Control:
grep -rn 'getRecord\|record' arquivo.php | grep -v 'null\|?\|test'
Verificar em cada Action/Controller: ownership ($record->tenant_id === Filament::getTenant()->id), policies aplicadas, Filament resources com canAccess()/canView()/canEdit().
// ❌ $order = Order::find($id);
// ✅
$order = Order::query()
->where('tenant_id', Filament::getTenant()->id)
->findOrFail($id);
Antes de marcar vazamento de tenant (evita falso-positivo): checar se o model tem seu trait de tenant scope (TenantScope global). Se tem e roda em painel → já filtrado, não é finding. Vazamento real só em: model SEM scope (User, Product...), contexto tenant null (Job/Webhook/API/Admin), ou withoutGlobalScopes() sem where manual.
Exposição de Dados Sensíveis:
grep -n 'Log::\|dd\|dump\|var_dump' arquivo.php
grep -rn 'password\|token\|secret\|cpf\|document' arquivo.php | grep 'Log::'
Nunca logar: password, password_hash, api_key, token, secret, cpf, cnpj, document, card_number, cvv.
CSRF/Auth:
grep -n 'Route::' routes/ | grep "'web'" | grep -v 'csrf\|auth'
Rotas web → middleware auth. APIs → auth:sanctum. Ações destrutivas → confirmação.
Configuração:
grep -rn 'env(' app/ # env() fora de config/ proibido
// ❌ $key = env('APP_KEY');
// ✅ $key = config('app.key');
Uploads:
// ❌ $file->store('uploads');
// ✅
FileUpload::make('attachment')
->acceptedFileTypes(['application/pdf', 'image/jpeg', 'image/png'])
->maxSize(5120)
->visibility('private');
Autorização Filament Actions:
// ❌ Action::make('delete')->action(fn ($record) => $record->delete());
// ✅
Action::make('delete')
->authorize(fn ($record) => auth()->user()->can('delete', $record))
->action(fn ($record) => $record->delete());
| Score | Critério |
|---|---|
| 🔴 Crítico | SQL injection, mass assignment sem validação, exposição dados sensíveis |
| 🟠 Alto | Falta autorização, CSRF vulnerável, uploads sem validação |
| 🟡 Médio | env() fora config, logs sensíveis, debug esquecido |
| 🟢 Baixo | Hardening sem impacto imediato |
SRP — sinal: classe faz mais de uma coisa.
grep -c 'public function' arquivo.php # >10 → avaliar divisão
// ❌ Model com responsabilidades demais
class Order extends Model {
public function generatePdf(): string { ... }
public function sendEmail(): void { ... }
public function calculateTax(): float { ... }
}
// ✅ Model focado em domínio
class Order extends Model {
public function isCompleted(): bool { ... }
public function markAsCompleted(): void { ... }
public function scopePending(Builder $q): Builder { ... }
}
OCP — sinal: if/elseif crescentes pra cada novo caso.
// ❌ if ($this->type === 'order') return 'heroicon-o-wrench';
// ✅ Extensível via Enum
enum InvoiceType: string {
case Order = 'order';
case Sale = 'sale';
public function icon(): string {
return match($this) {
self::Order => 'heroicon-o-wrench',
self::Sale => 'heroicon-o-shopping-cart',
};
}
}
DIP:
// ❌ private readonly StripeGateway $gateway // concreto
// ✅ private readonly PaymentGatewayInterface $gateway // abstração
Contar if/elseif/else/for/foreach/while/case/catch/&&/||.
| CC | Ação |
|---|---|
| 1–5 | ✅ |
| 6–10 | ⚠️ considerar refatoração |
| 11–20 | 🟠 refatorar |
| 21+ | 🔴 dividir obrigatório |
// ❌ CC=6
public function getStatus(): string {
if ($this->paid_at) {
if ($this->amount > 0) return 'paid';
else return 'zero';
} elseif ($this->due_date < now()) return 'overdue';
elseif ($this->cancelled_at) return 'cancelled';
else return 'pending';
}
// ✅ Guard clauses (CC=4)
public function getStatus(): string {
if ($this->cancelled_at) return 'cancelled';
if ($this->paid_at && $this->amount > 0) return 'paid';
if ($this->paid_at) return 'zero';
if ($this->due_date < now()) return 'overdue';
return 'pending';
}
grep -n 'Filament::getTenant()->id' arquivo.php
grep -n 'Number::currency\|number_format' arquivo.php
// ❌ Mesma query em 3 lugares
Invoice::query()->where('tenant_id', Filament::getTenant()->id)->where('status', 'pending');
// ✅ Scopes nomeados
public function scopePending(Builder $q): Builder {
return $q->where('status', InvoiceStatusEnum::PENDING);
}
public function scopeForTenant(Builder $q, int $tenantId): Builder {
return $q->where('tenant_id', $tenantId);
}
// Invoice::query()->forTenant($tenantId)->pending()->get();
Baixa coesão: >8 traits/interfaces, Model importando >5 outras Models, Action acessando >3 services.
Alto acoplamento: muitos use App\Models\X sem relação direta, constructor com >4 dependências.
// ❌ Side effect oculto
public function getTotal(): float {
$this->tax = $this->amount * 0.1;
return $this->amount + $this->tax;
}
// ✅ Puro
public function getTotal(): float {
return $this->amount + ($this->amount * 0.1);
}
public function calculateAndSaveTax(): void {
$this->tax = $this->amount * 0.1;
$this->save();
}
grep -L 'declare(strict_types=1)' arquivo.php
grep -n 'public function\|protected function\|private function' arquivo.php | grep -v ': '
grep -n 'function ' arquivo.php | grep -v 'function()' | grep '($[a-zA-Z]' | grep -v ': \|= null'
declare(strict_types=1);
// Constructor property promotion
public function __construct(
private readonly OnboardingService $onboardingService,
private readonly int $maxRetries = 3,
) {}
// Return types em TODOS métodos
public function isPaid(): bool { ... }
public function getInvoices(): Collection { ... }
// Named arguments
$this->invoice->markAsPaid(paidBy: $user);
// match em vez de switch
$label = match($this->status) {
InvoiceStatusEnum::PENDING => 'Pendente',
InvoiceStatusEnum::PAID => 'Pago',
};
// Nullsafe em FK nullable
$amount = $order->invoice?->invoice_amount ?? 0;
// ❌ public $name; // sem tipo
// ✅ public string $name;
Models:
// ✅ casts() como método
protected function casts(): array {
return [
'status' => InvoiceStatusEnum::class,
'paid_at' => 'datetime',
'invoice_amount' => 'decimal:2',
];
}
// ✅ Relações tipadas
public function user(): BelongsTo {
return $this->belongsTo(User::class);
}
// ✅ Scopes tipados
public function scopePending(Builder $q): Builder {
return $q->where('status', InvoiceStatusEnum::PENDING);
}
// ✅ $guarded = [] é o padrão DESTE projeto (CLAUDE.md) — não reportar como finding nem sugerir $fillable
Form Requests (validação separada do controller):
class StoreInvoiceRequest extends FormRequest {
public function authorize(): bool {
return $this->user()->can('create', Invoice::class);
}
public function rules(): array {
return [
'user_id' => ['required', 'exists:users,id'],
'invoice_percentage' => ['required', 'numeric', 'min:0', 'max:100'],
];
}
}
Eloquent N+1:
grep -n 'foreach\|->each(' arquivo.php
// ❌ foreach ($invoices as $i) { echo $i->user->name; }
// ✅
$invoices = Invoice::query()
->with(['user', 'order'])
->where('tenant_id', $tenantId)
->get();
Namespaces:
// Form fields
use Filament\Forms\Components\{TextInput, Select, DatePicker};
// Layout (NÃO Forms\Components)
use Filament\Schemas\Components\{Section, Grid, Tabs};
// Infolist (read-only)
use Filament\Infolists\Components\{TextEntry, IconEntry};
// Utilities
use Filament\Schemas\Components\Utilities\{Get, Set};
// Actions (NÃO Tables\Actions)
use Filament\Actions\{Action, DeleteAction};
// Icons
use Filament\Support\Icons\Heroicon;
Action com form:
final class MyAction extends Action {
protected function setUp(): void {
parent::setUp();
$this
->label('Minha Action')
->modalWidth('xl')
->form($this->getFormSchema())
->action(fn (array $data) => $this->execute($data));
}
public static function make(?string $name = 'my_action'): static {
return parent::make($name);
}
protected function getFormSchema(): array { ... }
protected function execute(array $data): void { ... }
}
Visibilidade de arquivos (v5 não é pública por padrão):
FileUpload::make('attachment')->disk('r2')->visibility('public');
Formatação:
// ❌ number_format($value, 2, ',', '.')
// ✅
use Illuminate\Support\Number;
Number::currency($value, 'BRL') // R$ 1.234,56
number_format($value, 2, ',', '.') // 1.234,56
number_format($value, 2, ',', '.') . '%' // 12,50%
// Cases TitleCase
enum OrderStatus: string {
case Draft = 'draft';
case InProgress = 'in_progress';
case Completed = 'completed';
}
// Comportamento no próprio Enum
enum InvoiceStatusEnum: string {
case Pending = 'pending';
case Paid = 'paid';
public function label(): string {
return match($this) {
self::Pending => 'Pendente',
self::Paid => 'Pago',
};
}
public function color(): string {
return match($this) {
self::Pending => 'warning',
self::Paid => 'success',
};
}
}
Lógica de Enum espalhada em N arquivos com mesmo match → mover pro Enum.
grep -n 'Model::query()\|Model::find\|Model::all\|Model::first' arquivo.php
// ❌ Invoice::query()->where('status', 'pending')->get();
// ✅ explícito
$invoices = Invoice::query()
->where('tenant_id', Filament::getTenant()->id)
->where('status', 'pending')
->get();
// ✅ Global scope (mais seguro)
protected static function booted(): void {
static::addGlobalScope('tenant', function (Builder $q): void {
if (Filament::hasTenant()) {
$q->where('tenant_id', Filament::getTenant()->id);
}
});
}
// ✅
class ProcessInvoiceJob implements ShouldQueue {
public function __construct(
private readonly int $invoiceId,
private readonly int $userId,
) {}
public function handle(): void {
$invoice = Invoice::findOrFail($this->invoiceId);
}
}
// ❌ private readonly Invoice $invoice // serializa dados sensíveis
// ❌ Observer dispara update no mesmo model = loop
public function updated(Order $order): void {
$order->update(['notes' => 'Updated']);
}
// ✅ saveQuietly()
public function updated(Order $order): void {
$order->saveQuietly();
}
// ❌ Sem transação = estado inconsistente em falha
// ✅
DB::transaction(function () use ($data): void {
$account = Accounts::create([...]);
foreach ($installments as $i) {
$account->installments()->create($i);
}
});
## Code Review — Relatório
### Escopo
- Arquivos: X | Linhas: ~XXX | Domínio: [...]
### 🔴 Crítico — Imediato
- [ ] [SEGURANÇA] arquivo.php:42 — SQL injection em getInvoices()
- [ ] [SEGURANÇA] arquivo.php:87 — Mass assignment sem validated() em store()
### 🟠 Importante — Sprint
- [ ] [QUALIDADE] arquivo.php:120 — process() CC=14, dividir
- [ ] [PADRÃO] arquivo.php:15 — env() fora de config/
- [ ] [N+1] arquivo.php:67 — Loop sem eager load do user
### 🟡 Backlog
- [ ] [QUALIDADE] arquivo.php:200 — Lógica status duplicada 3x, extrair Enum
- [ ] [SOLID] arquivo.php:10 — SRP violado (formatação+domínio+IO)
### 🟢 Boas Práticas
- ✅ Observer usa saveQuietly()
- ✅ Eager loading nas queries principais
### Score por Dimensão
| Dimensão | Score | Obs |
|---|---|---|
| Segurança | 🟡 7/10 | Mass assignment 1x |
| Qualidade | 🟠 5/10 | CC alta, DRY violado |
| Padrões | 🟢 9/10 | PHP 8.4 + Laravel 12 OK |
**Geral: 7/10**
### Ação
1. Imediato (pré-deploy): 🔴
2. Sprint: 🟠
3. Backlog: 🟡
Segurança:
create()/update() com validated()/only()->authorize()env() fora config/acceptedFileTypes + maxSizeLog::dd()/dump()/var_dump() esquecidosQualidade:
saveQuietly() onde precisoPadrões:
declare(strict_types=1) em todo arquivocasts() como métodoNumber::currency() pra formatação monetáriaProvides a checklist for code reviews covering functionality, security, performance, maintainability, tests, and quality. Use for pull requests, audits, team standards, and developer training.
npx claudepluginhub felipearnold/filament-ai-skills --plugin filament-ai-skills