Awesome
Talvez você deva checar o real-world Laravel example application
O que é descrito aqui não é uma adaptação ao principio SOLID, padrões e etc. Aqui você irá encontrar as melhores práticas que geralmente são ignoradas em um projeto Laravel na vida real.
Conteúdo
Princípio da responsabilidade única
Models gordos, controllers finos
Lógica de negócio deve ser posta em classes
Não se repita (Don't repeat yourself: DRY)
Não executar consultas no Blade templates e usar eager loading (N + 1)
Use chunk para tarefas de dados pesadas
Comente seu código, mas prefira um método descritivo e nomes de variáveis em vez de comentários
Não coloque JS e CSS em templates Blade. Não coloque HTML em classes PHP
Use arquivos de linguagem e configuração. Constantes em vez de texto no código
Use ferramentas padrões do Laravel aceitas pela comunidade
Siga a convenção de nomes usada no Laravel
Tente sempre usar sintaxes pequenas e legíveis
Use contaneirs IoC (inversão de controle) ou facades no lugar de classes
Não recupere informações diretamente do .env
Armazene datas em formatos padrões. Use "accessors" e "mutators" para modificar o formato das datas
Princípio da responsabilidade única
Classes e métodos devem possuir somente uma responsabilidade.
Ruim:
public function getFullNameAttribute()
{
if (auth()->user() && auth()->user()->hasRole('client') && auth()->user()->isVerified()) {
return 'Sr. ' . $this->first_name . ' ' . $this->middle_name . ' ' . $this->last_name;
} else {
return $this->first_name[0] . '. ' . $this->last_name;
}
}
Bom:
public function getFullNameAttribute()
{
return $this->isVerifiedClient() ? $this->getFullNameLong() : $this->getFullNameShort();
}
public function isVerifiedClient()
{
return auth()->user() && auth()->user()->hasRole('client') && auth()->user()->isVerified();
}
public function getFullNameLong()
{
return 'Sr. ' . $this->first_name . ' ' . $this->middle_name . ' ' . $this->last_name;
}
public function getFullNameShort()
{
return $this->first_name[0] . '. ' . $this->last_name;
}
Models gordos, controllers finos
Coloque toda a lógica relacionada a banco em modelos Eloquent ou em repositórios caso você esteja usando Query Builder ou consultas SQL.
Ruim:
public function index()
{
$clients = Client::verified()
->with(['orders' => function ($q) {
$q->where('created_at', '>', Carbon::today()->subWeek());
}])
->get();
return view('index', ['clients' => $clients]);
}
Bom:
public function index()
{
return view('index', ['clients' => $this->client->getWithNewOrders()]);
}
class Client extends Model
{
public function getWithNewOrders()
{
return $this->verified()
->with(['orders' => function ($q) {
$q->where('created_at', '>', Carbon::today()->subWeek());
}])
->get();
}
}
Validação
Não use validações em controllers e sim em classes de Requisição.
Ruim:
public function store(Request $request)
{
$request->validate([
'title' => 'required|unique:posts|max:255',
'body' => 'required',
'publish_at' => 'nullable|date',
]);
....
}
Bom:
public function store(PostRequest $request)
{
....
}
class PostRequest extends Request
{
public function rules()
{
return [
'title' => 'required|unique:posts|max:255',
'body' => 'required',
'publish_at' => 'nullable|date',
];
}
}
Lógica de negócio deve ser posta em classes
Controllers devem ter somente uma responsabilidade, então mova lógica de negócio para outros serviços.
Ruim:
public function store(Request $request)
{
if ($request->hasFile('image')) {
$request->file('image')->move(public_path('images') . 'temp');
}
....
}
Bom:
public function store(Request $request)
{
$this->articleService->handleUploadedImage($request->file('image'));
....
}
class ArticleService
{
public function handleUploadedImage($image)
{
if (!is_null($image)) {
$image->move(public_path('images') . 'temp');
}
}
}
Não se repita (Don't repeat yourself: DRY)
Reutilize seu código sempre que possível. A ideia da responsabilidade única ajuda você a evitar duplicação. Isso serve também para templates Blade.
Ruim:
public function getActive()
{
return $this->where('verified', 1)->whereNotNull('deleted_at')->get();
}
public function getArticles()
{
return $this->whereHas('user', function ($q) {
$q->where('verified', 1)->whereNotNull('deleted_at');
})->get();
}
Bom:
public function scopeActive($q)
{
return $q->where('verified', 1)->whereNotNull('deleted_at');
}
public function getActive()
{
return $this->active()->get();
}
public function getArticles()
{
return $this->whereHas('user', function ($q) {
$q->active();
})->get();
}
Usar o Eloquent em vez de Query Builder e consultas SQL puras (raw SQL). Usar collections no lugar de arrays
Eloquent permite que você escreva código legível e manutenível. Além disso, Eloquent possui ferramentas ótimas para implementar "soft deletes", eventos, escopos e etc.
Ruim:
SELECT *
FROM `articles`
WHERE EXISTS (SELECT *
FROM `users`
WHERE `articles`.`user_id` = `users`.`id`
AND EXISTS (SELECT *
FROM `profiles`
WHERE `profiles`.`user_id` = `users`.`id`)
AND `users`.`deleted_at` IS NULL)
AND `verified` = '1'
AND `active` = '1'
ORDER BY `created_at` DESC
Bom:
Article::has('user.profile')->verified()->latest()->get();
Atribuição em massa
Ruim:
$article = new Article;
$article->title = $request->title;
$article->content = $request->content;
$article->verified = $request->verified;
// Adicionar categoria em artigos
$article->category_id = $category->id;
$article->save();
Bom:
$category->article()->create($request->all());
Não executar consultas no Blade templates e usar eager loading (N + 1)
Ruim (para 100 usuários, 101 consultas são feitas):
@foreach (User::all() as $user)
{{ $user->profile->name }}
@endforeach
Bom (para 100 usuários, duas consultas são feitas):
$users = User::with('profile')->get();
...
@foreach ($users as $user)
{{ $user->profile->name }}
@endforeach
Use chunk para tarefas de dados pesadas
Ruim ():
$users = $this->get();
foreach ($users as $user) {
...
}
Bom:
$this->chunk(500, function ($users) {
foreach ($users as $user) {
...
}
});
Comente seu código, mas prefira um método descritivo e nomes de variáveis em vez de comentários
Ruim:
if (count((array) $builder->getQuery()->joins) > 0)
Melhor:
// Determine se há algum join.
if (count((array) $builder->getQuery()->joins) > 0)
Bom:
if ($this->hasJoins())
Não coloque JS e CSS em templates Blade. Não coloque HTML em classes PHP
Ruim:
let article = `{{ json_encode($article) }}`;
Melhor:
<input id="article" type="hidden" value="{{ json_encode($article) }}">
Ou
<button class="js-fav-article" data-article="{{ json_encode($article) }}">{{ $article->name }}<button>
No javascript:
let article = $('#article').val();
Use arquivos de linguagem e configuração. Constantes em vez de texto no código
Ruim:
public function isNormal()
{
return $article->type === 'normal';
}
return back()->with('message', 'Your article has been added!');
Bom:
public function isNormal()
{
return $article->type === Article::TYPE_NORMAL;
}
return back()->with('message', __('app.article_added'));
Use ferramentas padrões do Laravel aceitas pela comunidade
Preferir usar funcionalidades do próprio Laravel e pacotes da comunidade em vez de pacotes de terceiros. Qualquer desenvolvedor que irá trabalhar em seu sistema terá que aprender novas ferramentas no futuro. Além disso, ter ajuda da comunidade do Laravel se torna significativamente menor quando você utiliza um pacote ou ferramenta de terceiros.
Tarefas | Ferramentas padrões | Pacotes de terceiros |
---|---|---|
Autorização | Policies | Entrust, Sentinel e outros pacotes |
Compilar assets | Laravel Mix | Grunt, Gulp, pacotes de terceiros |
Ambiente de desenvolvimento | Homestead, Laradock | Docker |
Deployment | Laravel Forge | Deployer e outras soluções |
Testes unitários | PHPUnit, Mockery | Phpspec |
Teste em navegador | Laravel Dusk | Codeception |
DB | Eloquent | SQL, Doctrine |
Templates | Blade | Twig |
Trabalhando com dados | Laravel collections | Arrays |
Validação de formulários | Request classes | pacotes de terceiros, validação no controller |
Autenticação | Nativo | pacotes de terceiros, sua propria solução |
Autenticação API | Laravel Passport | JWT e pacotes OAuth |
Criar API | Nativo | Dingo API e similares |
Trabalhando com estrutura de DB | Migrações | Trabalhar com banco diretamente |
Localização | Nativo | pacotes de terceiros |
Interface em tempo real | Laravel Echo, Pusher | pacotes de terceiros e trabalhar com WebSockets diretamente |
Gerar dados de teste | Seeder classes, Model Factories, Faker | Criar testes manualmente |
Agendar tarefas | Laravel Task Scheduler | Scripts e pacotes de terceiros |
DB | MySQL, PostgreSQL, SQLite, SQL Server | MongoDB |
Siga a convenção de nomes usada no Laravel
Siga PSR standards.
Siga também a convenção de nomes aceita pelo a comunidade Laravel:
O que | Como | Bom | Ruim |
---|---|---|---|
Controller | singular | ArticleController | |
Route | plural | articles/1 | |
Named route | snake_case with dot notation | users.show_active | |
Model | singular | User | |
hasOne or belongsTo relationship | singular | articleComment | |
All other relationships | plural | articleComments | |
Table | plural | article_comments | |
Pivot table | singular model names in alphabetical order | article_user | |
Colunas em tabelas | snake_case without model name | meta_title | |
Model property | snake_case | $model->created_at | |
Foreign key | singular model name with _id suffix | article_id | |
Chaves primárias | - | id | |
Migrações | - | 2017_01_01_000000_create_articles_table | |
Métodos | camelCase | getAll | |
Métodos em controllers | table | store | |
Métodos e classes de teste | camelCase | testGuestCannotSeeArticle | |
Variáveis | camelCase | $articlesWithAuthor | |
Collection | descriptive, plural | $activeUsers = User::active()->get() | |
Object | descriptive, singular | $activeUser = User::active()->first() | |
Config e arquivos de linguagem | snake_case | articles_enabled | |
View | kebab-case | show-filtered.blade.php | |
Config | snake_case | google_calendar.php | |
Contract (interface) | adjective or noun | Authenticatable | |
Trait | adjective | Notifiable |
Tente sempre usar sintaxes pequenas e legíveis
Ruim:
$request->session()->get('cart');
$request->input('name');
Bom:
session('cart');
$request->name;
Mais exemplos:
Sintaxe comum | Pequena e mais legível |
---|---|
Session::get('cart') | session('cart') |
$request->session()->get('cart') | session('cart') |
Session::put('cart', $data) | session(['cart' => $data]) |
$request->input('name'), Request::get('name') | $request->name, request('name') |
return Redirect::back() | return back() |
is_null($object->relation) ? null : $object->relation->id | optional($object->relation)->id |
return view('index')->with('title', $title)->with('client', $client) | return view('index', compact('title', 'client')) |
$request->has('value') ? $request->value : 'default'; | $request->get('value', 'default') |
Carbon::now(), Carbon::today() | now(), today() |
App::make('Class') | app('Class') |
->where('column', '=', 1) | ->where('column', 1) |
->orderBy('created_at', 'desc') | ->latest() |
->orderBy('age', 'desc') | ->latest('age') |
->orderBy('created_at', 'asc') | ->oldest() |
->select('id', 'name')->get() | ->get(['id', 'name']) |
->first()->name | ->value('name') |
Use contaneirs IoC (inversão de controle) ou facades no lugar de classes
"new Class" sintaxe cria maior acoplamento de classes e teste. Use IoC ou facades em vez disso.
Ruim:
$user = new User;
$user->create($request->all());
Bom:
public function __construct(User $user)
{
$this->user = $user;
}
....
$this->user->create($request->all());
Não recupere informações diretamente do .env
Coloque os dados em arquivos de configuração e recupere através do helper config()
.
Ruim:
$apiKey = env('API_KEY');
Bom:
// config/api.php
'key' => env('API_KEY'),
// Use data
$apiKey = config('api.key');
Armazene datas em formatos padrões. Use "accessors" e "mutators" para modificar o formato das datas
Ruim:
{{ Carbon::createFromFormat('Y-d-m H-i', $object->ordered_at)->toDateString() }}
{{ Carbon::createFromFormat('Y-d-m H-i', $object->ordered_at)->format('m-d') }}
Bom:
// Model
protected $dates = ['ordered_at', 'created_at', 'updated_at']
public function getSomeDateAttribute($date)
{
return $date->format('m-d');
}
// View
{{ $object->ordered_at->toDateString() }}
{{ $object->ordered_at->some_date }}
Outras boas práticas
Nunca coloque lógica em arquivos de rota.
Minimize o uso de vanilla PHP em templates Blade.