laravel-best-practices icon indicating copy to clipboard operation
laravel-best-practices copied to clipboard

Laravel best practices

Boas Práticas Laravel

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

Validação

Lógica de negócio deve ser posta em classes

Não se repita (Don't repeat yourself: DRY)

Usar o Eloquent em vez de Query Builder e consultas SQL puras (raw SQL). Usar collections no lugar de arrays

Atribuição em massa

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

Outras boas práticas

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;
}

🔝 Voltar para o início

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();
    }
}

🔝 Voltar para o início

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',
        ];
    }
}

🔝 Voltar para o início

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');
        }
    }
}

🔝 Voltar para o início

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();
}

🔝 Voltar para o início

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();

🔝 Voltar para o início

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());

🔝 Voltar para o início

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

🔝 Voltar para o início

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) {
        ...
    }
});

🔝 Voltar para o início

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())

🔝 Voltar para o início

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();

🔝 Voltar para o início

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'));

🔝 Voltar para o início

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

🔝 Voltar para o início

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 ~~ArticlesController~~
Route plural articles/1 ~~article/1~~
Named route snake_case with dot notation users.show_active ~~users.show-active, show-active-users~~
Model singular User ~~Users~~
hasOne or belongsTo relationship singular articleComment ~~articleComments, article_comment~~
All other relationships plural articleComments ~~articleComment, article_comments~~
Table plural article_comments ~~article_comment, articleComments~~
Pivot table singular model names in alphabetical order article_user ~~user_article, articles_users~~
Colunas em tabelas snake_case without model name meta_title ~~MetaTitle; article_meta_title~~
Model property snake_case $model->created_at ~~$model->createdAt~~
Foreign key singular model name with _id suffix article_id ~~ArticleId, id_article, articles_id~~
Chaves primárias - id ~~custom_id~~
Migrações - 2017_01_01_000000_create_articles_table ~~2017_01_01_000000_articles~~
Métodos camelCase getAll ~~get_all~~
Métodos em controllers table store ~~saveArticle~~
Métodos e classes de teste camelCase testGuestCannotSeeArticle ~~test_guest_cannot_see_article~~
Variáveis camelCase $articlesWithAuthor ~~$articles_with_author~~
Collection descriptive, plural $activeUsers = User::active()->get() ~~$active, $data~~
Object descriptive, singular $activeUser = User::active()->first() ~~$users, $obj~~
Config e arquivos de linguagem snake_case articles_enabled ~~ArticlesEnabled; articles-enabled~~
View kebab-case show-filtered.blade.php ~~showFiltered.blade.php, show_filtered.blade.php~~
Config snake_case google_calendar.php ~~googleCalendar.php, google-calendar.php~~
Contract (interface) adjective or noun Authenticatable ~~AuthenticationInterface, IAuthentication~~
Trait adjective Notifiable ~~NotificationTrait~~

🔝 Voltar para o início

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')

🔝 Voltar para o início

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());

🔝 Voltar para o início

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');

🔝 Voltar para o início

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 }}

🔝 Voltar para o início

Outras boas práticas

Nunca coloque lógica em arquivos de rota.

Minimize o uso de vanilla PHP em templates Blade.

🔝 Voltar para o início