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.

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, $data
Object (Variável) Descritivo, Singular $activeUser = User::active()->first() $users, $obj
+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 e whereHas de um método index 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étodo store do Controller: public function store(StorePostRequest $request).
  • 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 e deleted_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), prefira if ($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 em config/). Use a função helper config('arquivo.chave').
    • Motivo: O Laravel faz cache das configurações para otimizar o desempenho. Após o cache ser gerado, env() pode retornar null. Usar config() garante que o valor correto (do cache ou do arquivo .env) seja lido.
    • Exemplo: Defina API_KEY no .env, crie config/api.php com 'key' => env('API_KEY'), e use config('api.key') no código.
  • 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) (ou chunk/lazy). Estes métodos processam os registros em lotes menores, reduzindo significativamente o consumo de memória. lazyById retorna uma LazyCollection, 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 ...
      }
      
  • 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),
      //     );
      // }
      
  • 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
      
  • Atribuição em Massa (Mass Assignment):

    • Conceito: Use os métodos create() ou fill() 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
      
  • 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 ...
      }