Esta biblioteca é uma tradução para o Português do Brasil do:
Espanhol (escrito por César Escudero)
A biblioteca reúne as melhores práticas para codificação de aplicativos web com o framework Laravel e não é uma adaptação dos cinco princípios do S.O.L.I.D (Single Responsiblity Principle, Open-Closed Principle, Liskov Substitution Principle, Interface Segregation Principle e o Dependency Inversion Principle)
, muito menos dos atuais padrões de projetos. Aqui você encontrará as melhores práticas de codificação que geralmente são ignoradas em projetos reais.
Princípio da responsabilidade única
Modelos gordos, Controladoras magras
A lógica de negócios deve ficar em classe de serviço
Não escreva código duplicado (do princípio de Don't Repeat Yourself)
Comente seu código, mas faça o uso de métodos (funções) e de nomes de variáveis descritivos
Faça uso de arquivos de configuração e de linguagens ao invés de mensagens de textos no código
Use somente ferramentas padronizadas desenvolvidas pela comunidade Laravel
Siga a convenção de nomes do PHP e do Laravel
Use sintaxes curtas e legíveis sempre que possível
Use contêineres IoC (inversão de controle) ou Facades (interfaces estáticas) no lugar de Classes
Não extraia informações diretamente do arquivo .env
Classes e métodos devem ter apenas um propósito.
Péssimo:
public function getNomeCompletoAttribute()
{
if (auth()->usuario() && auth()->usuario()->hasRole('editor') && auth()->usuario()->isVerified()) {
return 'Sr. ' . $this->primeiro_nome . ' ' . $this->nome_do_meio . ' ' . $this->sobrenome;
} else {
return $this->primeiro_nome[0] . '. ' . $this->sobrenome;
}
}
Ótimo:
public function getNomeCompletoAttribute()
{
return $this->isVerifiedEditor() ? $this->getNomeCompletoLong() : $this->getNomeCompletoShort();
}
public function isVerifiedEditor()
{
return auth()->usuario() && auth()->usuario()->hasRole('editor') && auth()->usuario()->isVerified();
}
public function getNomeCompletoLong()
{
return 'Sr. ' . $this->primeiro_nome . ' ' . $this->nome_do_meio . ' ' . $this->sobrenome;
}
public function getNomeCompletoShort()
{
return $this->primeiro_nome[0] . '. ' . $this->sobrenome;
}
Coloque toda a lógica relacionada ao banco de dados em modelos do próprio Eloquent ou em uma Classe do tipo Repositório se você estiver usando o Query Builder ou consultas puras em SQL (Raw Expressions).
Péssimo:
public function index()
{
$artistas = Artistas::verificado()
->with(['obras_encomendadas' => function ($q) {
$q->where('created_at', '>', Carbon::today()->subWeek());
}])
->get();
return view('index', ['artistas' => $artistas]);
}
Ótimo:
public function index()
{
return view('index', ['artistas' => $this->artista->getWithObrasEncomendadas()]);
}
class Client extends Model
{
public function getWithObrasEncomendadas()
{
return $this->verificado()
->with(['obras_encomendadas' => function ($q) {
$q->where('created_at', '>', Carbon::today()->subWeek());
}])
->get();
}
}
Remova todas as validações das Classes Controladoras e coloque-as em Classes de Requisições (Request)
Péssimo:
public function store(Request $request)
{
$request->validate([
'titulo' => 'required|max:255',
'descricao' => 'required',
'publicado_em' => 'nullable|date',
]);
....
}
Ótimo:
public function store(PublicacaoRequest $request)
{
....
}
class PublicacaoRequest extends Request
{
public function rules()
{
return [
'titulo' => 'required|max:255',
'descricao' => 'required',
'publicado_em' => 'nullable|date',
];
}
}
A classe Controladora deve ter apenas um propósito, portanto, mova a lógica de negócios dessa classe para uma nova classe de serviço.
Péssimo:
public function store(Request $request)
{
if ($request->hasFile('foto')) {
$request->file('foto')->move(public_path('fotos') . 'temporario');
}
....
}
Ótimo:
public function store(Request $request)
{
$this->publicacaoService->handleUploadedFoto($request->file('foto'));
....
}
class PublicacaoService
{
public function handleUploadedFoto($foto)
{
if (!is_null($foto)) {
$foto->move(public_path('fotos') . 'temporario');
}
}
}
Reutilize seu código sempre que possível. A ideia da responsabilidade única ajuda você a evitar duplicação de código. Isso serve também para templates do Blade. Use também os "scopes" para filtrar consultas pelo Eloquent.
Péssimo:
public function getPublicado()
{
return $this->where('publicado', 1)->whereNotNull('deleted_at')->get();
}
public function getPublicacoes()
{
return $this->whereHas('artista', function ($q) {
$q->where('publicado', 1)->whereNotNull('deleted_at');
})->get();
}
Ótimo:
public function scopePublicado($q)
{
return $q->where('publicado', 1)->whereNotNull('deleted_at');
}
public function getPublicado()
{
return $this->publicado()->get();
}
public function getPublicacoes()
{
return $this->whereHas('artista', function ($q) {
$q->publicado();
})->get();
}
Priorize o uso do Eloquent ORM (o ActiveRecord do Framework). Priorize também o uso das Collections ao invés dos arrays (vetores)
O Eloquent permite que você escreva código legível e de fácil manutenção. Ele também possui ótimas ferramentas como, o deletar de forma suave (soft delete), os eventos, os "scopes", entre outros. Leia a documentação oficial do Laravel e conheça todas essas ferramentas.
Péssimo:
SELECT *
FROM `publicacoes`
WHERE EXISTS (SELECT *
FROM `editores`
WHERE `publicacoes`.`editor_id` = `editores`.`id`
AND EXISTS (SELECT *
FROM `perfis`
WHERE `perfis`.`editor_id` = `editores`.`id`)
AND `editores`.`deleted_at` IS NULL)
AND `verificado` = '1'
AND `ativo` = '1'
ORDER BY `created_at` DESC
Ótimo:
Publicacao::has('editor.perfil')->verificado()->latest()->get();
O método mais eficaz para lidar com ataques de atribuição em massa é passar apenas os campos que foram validados ao invés de passar todos os dados da solicitação (requisição).
Péssimo:
class Usuario extends Authenticatable {
protected $fillable = ['nome', 'email', 'senha'];
...
}
class UsuarioRequest extends Request {
public function rules() {
return [
'nome' => 'string|required',
'email' => 'email|required',
'senha' => 'string|required|min:6',
'confirmacao_senha' => 'same:password',
];
}
}
class UsuarioController extends Controller {
public function store(UsuarioRequest $request) {
$usuario = new Usuario();
$usuario->fill($request->all());
$usuario->save();
return response()->json([
'success' => true,
],201);
}
Ótimo:
class Usuario extends Authenticatable {
protected $fillable = ['nome', 'email', 'senha'];
...
}
class UsuarioRequest extends Request {
public function rules() {
return [
'nome' => 'string|required',
'email' => 'email|required',
'senha' => 'string|required|min:6',
'confirmacao_senha' => 'same:password',
];
}
}
class UsuarioController extends Controller {
public function store(UsuarioRequest $request) {
$usuario = new Usuario();
$usuario->fill($request->validated());
$usuario->save();
return response()->json([
'success' => true,
],201);
}
}
Não execute queries diretamente nos templates do Blade, faça o uso de carregamento prematuro (eager loading). Esse é um problema do tipo N + 1
Péssimo - Para 100 usuários, 101 consultas foram executadas:
@foreach (Usuario::all() as $usuario)
{{ $usuario->perfil->nome }}
@endforeach
Ótimo - Para 100 usuários, 2 consultas foram executadas:
$usuarios = Usuario::with('perfil')->get();
...
@foreach ($usuarios as $usuario)
{{ $usuario->perfil->nome }}
@endforeach
Péssimo:
if (count((array) $builder->getQuery()->joins) > 0)
Ótimo:
// Determina se existe algum método de junção
if (count((array) $builder->getQuery()->joins) > 0)
// hasJoins() - Método descritivo que verifica a existência de alguma junção
if ($this->hasJoins())
Não coloque código de JavaScript e de CSS nos templates do Blade. Jamais coloque código HTML em classes PHP
Péssimo:
let publicacao = `{{ json_encode($publicacao) }}`;
class Publicacao extends Model
{
...
public function laratablesRowData()
{
return [
'acoes' =>
"<div class=\"btn-group\">
<button type=\"button\" id=\"btnDropDown_{$this->id}\" class=\"btn btn-primary btn-sm dropdown-toggle\" data-toggle=\"dropdown\" aria-haspopup=\"true\" aria-expanded=\"false\">
<i class=\"fa fa-cogs fa-lg fa-fw fa-inverse\"></i> Opções
<span class=\"fa fa-caret-down fa-inverse fa-fw\"></span>
</button>
<ul class=\"dropdown-menu myDropDown\" aria-labelledby=\"btnDropDown_{$this->id}\">
<li data-toggle=\"tooltip\" data-placement=\"right\" title=\"Exibir {$this->titulo}\">
<a href=\"javascript:modalGlobalOpen('".route('publicacao.show', ["id" => $this->id])."')\">
<i class=\"fa fa-eye fa-lg fa-fw\"></i> Visualizar
</a>
</li>
<li data-toggle=\"tooltip\" data-placement=\"right\" title=\"Editar {$this->titulo}\">
<a href=\"".route('publicacao.edit', ["id" => $this->id])."\">
<i class=\"fa fa-edit fa-lg fa-fw\"></i> Editar
</a>
</li>
<li data-toggle=\"tooltip\" data-placement=\"right\" title=\"Deletar {$this->titulo}\">
<a href=\"javascript:void(0)\" onclick=\"doDelete('{$this->id}')\">
<i class=\"fa fa-ban fa-lg fa-fw\"></i> Deletar
</a>
</li>
</ul>
</div>"
];
}
}
}
Ótimo:
let publicacao = $('#publicacao').val();
<input id="publicacao" type="hidden" value='@json($publicacao)'>
Ou
<button class="btn btn-primary" data-publicacao='@json($publicacao)'>{{ $publicacao->titulo }}<button>
A maneira ideal para lidar com essa questão é usar algum pacote especializado para transferir informações do PHP para JavaScript.
Péssimo:
public function isNormal()
{
return $artigo->tipo === 'normal';
}
return back()->with('mensagem', 'Seu artigo foi publicado com sucesso!');
Ótimo:
public function isNormal()
{
return $artigo->tipo === Artigo::TIPO_NORMAL;
}
return back()->with('mensagem', __('app.artigo_publicado'));
Priorize o uso das classes e métodos do próprios framework e de pacotes fornecidos pela comunidade em vez de usar pacotes ou ferramentas de terceiros, pois qualquer novo desenvolvedor que trabalhará em seu aplicativo no futuro precisará aprender como usar essas ferramentas. Além disso, as chances de receber ajuda da comunidade são menores quando você usa ferramentas ou pacotes de terceiros. Não faça seu cliente pagar por isso.
Tarefa | Ferramenta padrão | Pacotes de terceiros |
---|---|---|
Autorização | Policies | Entrust, Sentinel, entre outros |
Compilação de assets | Laravel Mix | Grunt, Gulp, entre outros |
Ambiente de desenvolvimento | Homestead e Laradock | Docker |
Deployment | Laravel Forge e Laravel Vapor | Deployer, Anistrano, Storm.dev, entre outros |
Testes unitários | PHPUnit, PestPHP e Mockery | PHPSpec |
Teste em navegador | Laravel Dusk | Codeception e Nerrvana |
ORM - ActiveRecord | Eloquent | Raw SQL e Doctrine |
Templates | Blade e Vue.js | Twig |
Trabalhando com dados | Laravel Collections | Arrays (Vetores) |
Validação de formulários | Classes Request e Factory | pacotes de terceiros, validação pela controladora, entre outros |
Autenticação | Laravel | pacotes de terceiros, entre outros |
Autenticação API | Laravel Passport | JWT e OAuth2 |
Criação API | Laravel e Laravel 7 Sanctum | Dingo API, entre outros |
Trabalhando com estruturas de Bancos de Dados | Migrações | Laravel SQL Migrations de Peter Matseykanets, em SQL puro, entre outros |
Localização | Laravel | pacotes de terceiros |
Interface em tempo real | Laravel Echo e Pusher | pacotes de terceiros e trabalhar com WebSockets diretamente |
Gerar dados de teste | Classes Seeders, Modeles Factories e o Faker | Criar testes manualmente |
Agendador de tarefas | Laravel Task Scheduler | Scripts e pacotes de terceiros |
Banco de Dados | Laravel Drivers para MySQL, PostgreSQL, SQLite, SQL Server | Laravel MongoDB de Jens Segers e o Laravel Oracle OCI8 |
Confira o Guia para estilo para codificação PSR-12.
Siga também a convenção de nomes aceita pelo a comunidade Laravel:
O que? | Como? | Ótimo | Péssimo |
---|---|---|---|
Controladora | singular | ArtigoController | |
Rota | plural | artigos/1 | |
Nomes da rota | snake_case com notação de ponto | usuarios.show_active | |
Modelo | singular | Usuario | |
Relacionamento de Tabelas hasOne ou belongsTo | singular | artigoComnetario | |
Qualquer outro tipo de relacionamento | plural | artigoComentarios | |
Tabela do Banco de Dados | plural | artigo_comentarios | |
Tabela Pivô | Nomes de Modelos no singular e em ordem alfabética | artigo_usuario | |
Coluna das tabela | snake_case sem o nome da tabela (Modelo) | descricao_titulo | |
Propriedade do Modelo | snake_case | $model->created_at | |
Chave Estrangeira | Nome do modelo em singular com sufixo _id | usuario_id | |
Chave primária | - | id | |
Migração | - | 2020_01_04_174100_create_usuarios_table | |
Método | camelCase | getNomeAluno | |
Método na controladora de recursos | Documentação do Laravel - Resource Controllers | store | |
Método na classe de Teste | camelCase | testeUsuarioSemFuncaoAcessaSolicitarServico | |
Variável | camelCase | $alunoMatriculado | |
Coleção | descritivo e no plural | $usuariosAtivos = Usuario::ativo()->get() | |
Objeto | descritivo e no singular | $usuarioAtivo = Usuario::ativo()->first() | |
Índice de arquivo de configuração e de linguagem | snake_case | artigos_permitidos | |
Visualização | kebab-case | mostra-filtros.blade.php | |
Configuração | snake_case | sso_wso2.php | |
Contrato (interface) | adjetivo ou substantivo | Esquematico | |
Trait | adjetivo | Negador |
Péssimo:
$request->session()->get('usuario');
$request->input('nome');
Ótimo:
session('usuario');
$request->nome;
Outros exemplos
Sintaxe comum | Sintaxe curta e legível |
---|---|
Session::get('usuario') |
session('usuario') |
$request->session()->get('usuario') |
session('usuario') |
Session::put('usuario', $dados) |
session(['usuario' => $dados]) |
$request->input('nome'), Request::get('nome') |
$request->nome, request('nome') |
return Redirect::back() |
return back() |
is_null($object->relation) ? null : $object->relation->id |
optional($object->relation)->id |
return view('index')->with('titulo', $titulo)->with('aluno', $aluno) |
return view('index', compact('titulo', 'aluno')) |
$request->has('foto_perfil') ? $request->foto_perfil : 'default'; |
$request->get('foto_perfil', 'default') |
Carbon::now(), Carbon::today() |
now(), today() |
App::make('Curso') |
app('Curso') |
->where('aprovado', '=', 1) |
->where('aprovado', 1) |
->orderBy('created_at', 'desc') |
->latest() |
->orderBy('idade', 'desc') |
->latest('idade') |
->orderBy('created_at', 'asc') |
->oldest() |
->select('id', 'nome')->get() |
->get(['id', 'nome']) |
->first()->nome |
->value('nome') |
A sintaxe 'new Class' cria acoplamentos estreitos complicando os testes. Use contêineres IoC ou as Facades.
Péssimo:
$aluno = new Aluno;
$aluno->create($request->validated());
Ótimo:
public function __construct(Aluno $Aluno)
{
$this->aluno = $aluno;
...
}
...
$this->aluno->create($request->validated());
Em vez disso, coloque as informações em um arquivo de configuração e use o helper config ()
para obter as informações em sua aplicação.
Péssimo:
$chaveApi = env('API_KEY');
Ótimo:
// config/api.php
'chave' => env('API_KEY'),
// Fazendo uso da chave da api pelo helper config
$chaveApi = config('api.chave');
Salve datas em formatos padronizados. Use os modificadores de atributos das tabelas chamados de "accessors" e "mutators" para formatar as datas
Péssimo:
{{ Carbon::createFromFormat('Y-d-m H-i', $objeto->ordered_at)->toDateString() }}
{{ Carbon::createFromFormat('Y-d-m H-i', $objeto->ordered_at)->format('m-d') }}
Ótimo:
// Modelo
protected $datas = ['ordered_at', 'created_at', 'updated_at'];
public function getAtributoData($datas)
{
return $datas->format('m-d');
}
// Visualização
{{ $objeto->ordered_at->toDateString() }}
{{ $objeto->ordered_at->ano_publicacao }}
Não coloque nenhum tipo de lógica nos arquivos de rotas.
Minimize o uso de códigos PHP puros (vanilla) nos modelos em Blade (template engine).