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.
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
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;
}
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();
}
}
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',
];
}
}
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');
}
}
}
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();
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());
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
Ruim ():
$users = $this->get();
foreach ($users as $user) {
...
}
Bom:
$this->chunk(500, function ($users) {
foreach ($users as $user) {
...
}
});
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())
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();
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'));
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 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 |
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') |
"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());
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');
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 }}
Nunca coloque lógica em arquivos de rota.
Minimize o uso de vanilla PHP em templates Blade.