Home

Awesome

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.

TarefasFerramentas padrõesPacotes de terceiros
AutorizaçãoPoliciesEntrust, Sentinel e outros pacotes
Compilar assetsLaravel MixGrunt, Gulp, pacotes de terceiros
Ambiente de desenvolvimentoHomestead, LaradockDocker
DeploymentLaravel ForgeDeployer e outras soluções
Testes unitáriosPHPUnit, MockeryPhpspec
Teste em navegadorLaravel DuskCodeception
DBEloquentSQL, Doctrine
TemplatesBladeTwig
Trabalhando com dadosLaravel collectionsArrays
Validação de formuláriosRequest classespacotes de terceiros, validação no controller
AutenticaçãoNativopacotes de terceiros, sua propria solução
Autenticação APILaravel PassportJWT e pacotes OAuth
Criar APINativoDingo API e similares
Trabalhando com estrutura de DBMigraçõesTrabalhar com banco diretamente
LocalizaçãoNativopacotes de terceiros
Interface em tempo realLaravel Echo, Pusherpacotes de terceiros e trabalhar com WebSockets diretamente
Gerar dados de testeSeeder classes, Model Factories, FakerCriar testes manualmente
Agendar tarefasLaravel Task SchedulerScripts e pacotes de terceiros
DBMySQL, PostgreSQL, SQLite, SQL ServerMongoDB

🔝 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 queComoBomRuim
ControllersingularArticleControllerArticlesController
Routepluralarticles/1article/1
Named routesnake_case with dot notationusers.show_activeusers.show-active, show-active-users
ModelsingularUserUsers
hasOne or belongsTo relationshipsingulararticleCommentarticleComments, article_comment
All other relationshipspluralarticleCommentsarticleComment, article_comments
Tablepluralarticle_commentsarticle_comment, articleComments
Pivot tablesingular model names in alphabetical orderarticle_useruser_article, articles_users
Colunas em tabelassnake_case without model namemeta_titleMetaTitle; article_meta_title
Model propertysnake_case$model->created_at$model->createdAt
Foreign keysingular model name with _id suffixarticle_idArticleId, id_article, articles_id
Chaves primárias-idcustom_id
Migrações-2017_01_01_000000_create_articles_table2017_01_01_000000_articles
MétodoscamelCasegetAllget_all
Métodos em controllerstablestoresaveArticle
Métodos e classes de testecamelCasetestGuestCannotSeeArticletest_guest_cannot_see_article
VariáveiscamelCase$articlesWithAuthor$articles_with_author
Collectiondescriptive, plural$activeUsers = User::active()->get()$active, $data
Objectdescriptive, singular$activeUser = User::active()->first()$users, $obj
Config e arquivos de linguagemsnake_casearticles_enabledArticlesEnabled; articles-enabled
Viewkebab-caseshow-filtered.blade.phpshowFiltered.blade.php, show_filtered.blade.php
Configsnake_casegoogle_calendar.phpgoogleCalendar.php, google-calendar.php
Contract (interface)adjective or nounAuthenticatableAuthenticationInterface, IAuthentication
TraitadjectiveNotifiableNotificationTrait

🔝 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 comumPequena 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->idoptional($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