Padrões de Código e Boas Práticas - ime-usp-br/laravel_11_starter_kit GitHub Wiki
Padrões de Código e Boas Práticas
Este documento descreve os padrões de codificação e as boas práticas adotadas no Projeto Base USP, visando a qualidade, manutenabilidade e consistência do código.
Aderência ao PSR-12
O código deste projeto DEVE seguir as regras definidas na especificação PSR-12 (Extended Coding Style). Esta especificação expande e substitui a PSR-2, fornecendo um conjunto abrangente de regras para formatação de código PHP.
- Referência: PSR-12: Extended Coding Style
A adesão ao PSR-12 garante um estilo de código consistente, reduzindo o atrito cognitivo ao ler e trabalhar com código de diferentes desenvolvedores. Ferramentas como o Laravel Pint (configurado no composer.json
) podem ser usadas para verificar e corrigir automaticamente o estilo do código.
Uso do RFC 2119 (Níveis de Exigência)
Para indicar claramente o nível de obrigatoriedade das diretrizes nesta documentação, utilizamos as palavras-chave definidas na RFC 2119, traduzidas para o Português do Brasil.
Tabela de Equivalência RFC 2119 (PT-BR):
Inglês | Português (Tradução RFC 2119 PT-BR) | Significado |
---|---|---|
MUST | DEVE, DEVEM | Exigência absoluta. |
REQUIRED | REQUER | Exigência absoluta. |
SHALL | DEVERIA, DEVERIAM | Exigência absoluta (usado como sinônimo de MUST na RFC). |
MUST NOT | NÃO DEVE, NÃO DEVEM | Proibição absoluta. |
SHALL NOT | NÃO DEVERIA | Proibição absoluta (usado como sinônimo de MUST NOT na RFC). |
SHOULD | PODERIA, PODERIAM | Recomendação forte. Ignorar apenas com bom motivo e compreensão das implicações. |
RECOMMENDED | RECOMENDÁVEL | Recomendação forte. |
SHOULD NOT | NÃO PODERIA | Desaconselhamento forte. Implementar apenas com bom motivo e ponderação. |
NOT RECOMMENDED | NÃO RECOMENDÁVEL | Desaconselhamento forte. |
MAY | PODE, PODEM | Item verdadeiramente opcional. A interoperabilidade deve ser mantida. |
OPTIONAL | OPCIONAL | Item verdadeiramente opcional. |
Convenções de Nomenclatura Laravel
Além do PSR-12, seguimos as convenções de nomenclatura amplamente aceitas na comunidade Laravel para diversos componentes:
O quê | Como | Bom | Ruim |
---|---|---|---|
Controller | Singular | ArticleController |
ArticlesController |
Route (path) | Plural | articles/1 |
article/1 |
Named Route | snake_case com notação de ponto | users.show_active |
users.show-active show-active-users |
Model | Singular | User |
Users |
Relacionamento hasOne |
Singular | articleComment |
articleComments article_comment |
Relacionamento belongsTo |
Singular | author (método), author_id (chave) |
authors |
Outros Relacionamentos | Plural | articleComments |
articleComment article_comments |
Tabela | snake_case, Plural | article_comments |
article_comment articleComments |
Tabela Pivot | Nomes dos models (singular), ordem alfab. | article_user |
user_article articles_users |
Coluna (Tabela) | snake_case, sem nome do model | meta_title , created_at |
MetaTitle article_meta_title createdAt |
Propriedade (Model) | snake_case | $model->created_at |
$model->createdAt |
Chave Estrangeira | Nome do model (singular) + _id |
article_id |
ArticleId id_article articles_id |
Chave Primária | - | id |
custom_id |
Migration | Padrão descritivo create_*_table etc. |
2017_01_01_000000_create_articles_table |
2017_01_01_000000_articles |
Método (Classe PHP) | camelCase | getAll |
get_all |
Método (Controller Recurso) | Verbos padrão (index , create , store ...) |
store |
saveArticle |
Método (Teste) | camelCase (ex: testFeature ) |
testGuestCannotSeeArticle |
test_guest_cannot_see_article |
Variável (PHP) | camelCase | $articlesWithAuthor |
$articles_with_author |
Collection (Variável) | Descritivo, Plural | $activeUsers = User::active()->get() |
$active |
Object (Variável) | Descritivo, Singular | $activeUser = User::active()->first() |
$users |
+Config/Linguagem (Chave) | snake_case | articles_enabled |
ArticlesEnabled articles-enabled |
View (Arquivo Blade) | kebab-case | show-filtered.blade.php |
showFiltered.blade.php show_filtered.blade.php |
Config (Arquivo) | snake_case | google_calendar.php |
googleCalendar.php google-calendar.php |
Contract (Interface) | Adjetivo ou Substantivo | Authenticatable , Notification |
AuthenticationInterface IAuthentication |
Trait | Adjetivo | Notifiable |
NotificationTrait |
Princípios e Boas Práticas Adotadas
O Projeto Base USP incentiva e, em alguns casos, implementa as seguintes boas práticas do desenvolvimento Laravel:
-
Princípio da Responsabilidade Única (SRP):
- Conceito: Cada classe e método DEVE ter apenas uma responsabilidade bem definida. Isso torna o código mais fácil de entender, testar e manter.
- Exemplo: Em vez de um método
getFullNameAttribute
que verifica roles e formata o nome de maneiras diferentes, separe em métodos menores:isVerifiedClient()
,getFullNameLong()
,getFullNameShort()
. O método principal apenas orquestra a chamada aos métodos menores.
-
Controllers Finos, Models (ou Camada de Serviço) Gordos:
- Conceito: Controllers DEVEM ser responsáveis apenas por receber a requisição, delegar a lógica de negócio/acesso a dados para outra camada (Models, Repositories, Services) e retornar a resposta. Lógica de banco (queries complexas) ou regras de negócio NÃO DEVEM residir nos controllers.
- Exemplo: Mover uma query Eloquent complexa com
with
ewhereHas
de um métodoindex
do Controller para um método no Model correspondente (ex:Client::getWithNewOrders()
).
-
Validação em Form Requests:
- Conceito: A validação dos dados da requisição DEVE ser feita em classes dedicadas que estendem
Illuminate\Foundation\Http\FormRequest
, em vez de diretamente no Controller usando$request->validate()
. Isso mantém os controllers mais limpos e a lógica de validação reutilizável e centralizada. - Exemplo: Criar
App\Http\Requests\StorePostRequest
com as regras de validação e injetá-la no métodostore
do Controller:public function store(StorePostRequest $request)
.
- Conceito: A validação dos dados da requisição DEVE ser feita em classes dedicadas que estendem
-
Não se Repita (Don't Repeat Yourself - DRY):
- Conceito: Evite duplicação de código. Abstraia lógicas comuns em métodos, traits, classes ou escopos reutilizáveis.
- Exemplo: Se várias queries precisam filtrar por
verified = 1
edeleted_at IS NOT NULL
, crie um Query Scope no Model:public function scopeActive($query) { return $query->where('verified', 1)->whereNotNull('deleted_at'); }
. Use-o nas queries com$query->active()
.
-
Nomes Expressivos:
- Conceito: Variáveis, métodos, classes e outros elementos DEVEM ter nomes que descrevam claramente seu propósito. Nomes claros reduzem a necessidade de comentários e facilitam a leitura do código.
- Exemplo: Em vez de
if (count((array) $builder->getQuery()->joins) > 0)
, prefiraif ($this->hasJoins())
.
-
Acesso a Variáveis de Ambiente:
- Conceito: NÃO DEVE acessar variáveis de ambiente diretamente com
env('VARIAVEL')
no código da aplicação (exceto nos arquivos de configuração emconfig/
). Use a função helperconfig('arquivo.chave')
. - Motivo: O Laravel faz cache das configurações para otimizar o desempenho. Após o cache ser gerado,
env()
pode retornarnull
. Usarconfig()
garante que o valor correto (do cache ou do arquivo.env
) seja lido. - Exemplo: Defina
API_KEY
no.env
, crieconfig/api.php
com'key' => env('API_KEY')
, e useconfig('api.key')
no código.
- Conceito: NÃO DEVE acessar variáveis de ambiente diretamente com
-
Processamento de Grandes Volumes (Chunking/Lazy Collections):
- Conceito: Ao processar um grande número de registros do banco (ex: milhares de usuários), evite carregar todos na memória de uma vez com
->get()
ou::all()
. Isso PODE levar a erros de "Allowed memory size exhausted". - Solução: Use
->chunkById(tamanho, callback)
ou->lazyById(tamanho)
(ouchunk
/lazy
). Estes métodos processam os registros em lotes menores, reduzindo significativamente o consumo de memória.lazyById
retorna umaLazyCollection
, permitindo encadear métodos de coleção de forma eficiente. - Exemplo:
// Ruim: Pode estourar a memória // $users = User::where('active', 1)->get(); // foreach ($users as $user) { ... } // Bom: Processa em lotes de 200 User::where('active', 1)->chunkById(200, function ($users) { foreach ($users as $user) { // ... processar $user ... } }); // Alternativa com LazyCollection: foreach (User::where('active', 1)->lazyById(200) as $user) { // ... processar $user ... }
- Conceito: Ao processar um grande número de registros do banco (ex: milhares de usuários), evite carregar todos na memória de uma vez com
-
Accessors & Mutators:
- Conceito: Use Accessors para formatar atributos ao recuperá-los do model (ex: formatar datas, concatenar nomes) e Mutators para transformar dados antes de salvá-los no banco (ex: hashear senhas, garantir formato de data). No Laravel 9+, a sintaxe unificada com
Attribute::make()
é RECOMENDÁVEL. - Exemplo (Accessor - Formato de Data):
// No Model User.php use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Support\Carbon; protected function createdAt(): Attribute { return Attribute::make( get: fn ($value) => Carbon::parse($value)->format('d/m/Y H:i'), ); } // Na view: {{ $user->created_at }} // Exibirá 'dd/mm/yyyy HH:ii'
- Exemplo (Mutator - Hashing de Senha já é feito no User Model padrão):
// Exemplo conceitual (não necessário para password no User Model padrão) // protected function password(): Attribute { // return Attribute::make( // set: fn ($value) => bcrypt($value), // ); // }
- Conceito: Use Accessors para formatar atributos ao recuperá-los do model (ex: formatar datas, concatenar nomes) e Mutators para transformar dados antes de salvá-los no banco (ex: hashear senhas, garantir formato de data). No Laravel 9+, a sintaxe unificada com
-
Evitar Lógica/Queries em Views Blade:
- Conceito: Views Blade DEVEM ser responsáveis apenas pela apresentação dos dados. Cálculos complexos, lógica de negócio ou queries ao banco de dados NÃO DEVEM ser feitas diretamente nos arquivos
.blade.php
. - Motivo: Viola a separação de responsabilidades (MVC/SoC), dificulta testes e pode levar a problemas de performance (como o N+1).
- Problema N+1: Ocorre quando uma query inicial busca uma coleção de itens (N) e, dentro do loop que itera sobre essa coleção na view, uma nova query é executada para cada item (1 query para cada N, total N+1 queries).
- Solução: Prepare todos os dados necessários no Controller (ou camada de serviço) e passe-os para a view. Use Eager Loading (
with()
) para carregar relacionamentos necessários junto com a query principal. - Exemplo:
// Ruim (N+1 na View): // @foreach (User::all() as $user) // {{ $user->profile->name }} // Query para profile a cada iteração // @endforeach // Bom (Controller): // $users = User::with('profile')->get(); // return view('users.index', compact('users')); // Bom (View): // @foreach ($users as $user) // {{ $user->profile->name }} // Dados já carregados // @endforeach
- Conceito: Views Blade DEVEM ser responsáveis apenas pela apresentação dos dados. Cálculos complexos, lógica de negócio ou queries ao banco de dados NÃO DEVEM ser feitas diretamente nos arquivos
-
Atribuição em Massa (
Mass Assignment
):- Conceito: Use os métodos
create()
oufill()
para popular um model com um array de dados (geralmente vindo de$request->all()
ou$request->validated()
). É mais conciso do que atribuir cada propriedade individualmente. - Segurança: Para prevenir vulnerabilidades de atribuição em massa (onde um usuário malicioso envia campos não esperados), você DEVE proteger seus models definindo explicitamente quais atributos podem ser preenchidos em massa (propriedade
$fillable
) ou quais NÃO podem (propriedade$guarded
). É RECOMENDÁVEL usar$fillable
. - Exemplo:
// No Model Post.php // protected $fillable = ['title', 'content', 'category_id']; // ou (menos recomendado, mas funciona se você sempre validar os dados) // protected $guarded = []; // No Controller // Ruim (verboso): // $post = new Post; // $post->title = $request->title; // $post->content = $request->content; // $post->category_id = $request->category_id; // $post->save(); // Bom (conciso e seguro se $fillable/$guarded estiver definido): $post = Post::create($request->validated()); // Usa dados validados pelo Form Request
- Conceito: Use os métodos
-
Comentários:
- Prefira código autoexplicativo com nomes claros.
- Use comentários para explicar o porquê de algo complexo ou não óbvio, não o o quê.
- Para métodos, siga o padrão DocBlock (RECOMENDÁVEL):
/** * Descreve o propósito do método. * /uri-da-rota (opcional, se for action de controller) * * @param Tipo $nomeParametro Descrição do parâmetro. * @param Tipo|null $outroParam (Opcional) Descrição. * @return TipoRetorno Descrição do que é retornado. * @throws TipoExcecao Se uma exceção específica pode ocorrer. */ public function nomeDoMetodo(Tipo $nomeParametro, ?Tipo $outroParam = null): TipoRetorno { // ... corpo do método ... }