The Cheat Sheet
ContributeSearch
← FrameworksEdit this page

Laravel

Referência prática para criar um projeto Laravel, comandos Artisan, estrutura MVC e Eloquent.

Criando um Projeto

composer create-project laravel/laravel meu-app   # criar um projeto novo
cd meu-app

composer global require laravel/installer   # ou instale o instalador uma vez
laravel new meu-app                         # e crie projetos com ele
composer install          # instala as dependências PHP (vendor/ está no gitignore)
cp .env.example .env      # cria o arquivo de ambiente local
php artisan key:generate  # escreve a APP_KEY no .env
php artisan migrate       # cria as tabelas no banco
composer run dev    # servidor + worker de fila + Vite, tudo num terminal só
  • Os quatro passos composer installmigrate acima são o que um clone de um projeto existente precisa.
  • O .env não é commitado, por isso um projeto clonado não tem nenhum.
  • A APP_KEY criptografa sessões e cookies. Sem ela, nada sobe.
  • composer run dev (ou só php artisan serve) é para desenvolvimento; produção roda atrás de Nginx/Apache ou FrankenPHP.
  • Um projeto novo já vem com SQLite: o laravel new cria o database/database.sqlite e roda as migrations sozinho.

Starter Kits

O laravel new pergunta qual starter kit você quer: uma aplicação Laravel normal que já vem com autenticação e um frontend montado, tudo dentro do seu projeto para você editar.

  • React — Inertia, React 19, TypeScript, Tailwind, shadcn/ui. Frontend em resources/js/.
  • Vue — Inertia, Vue 3 Composition API, TypeScript, Tailwind, shadcn-vue. Frontend em resources/js/.
  • Svelte — Inertia, Svelte 5, TypeScript, Tailwind, shadcn-svelte. Frontend em resources/js/.
  • Livewire — Livewire, Tailwind, Flux UI. Sem framework JS, tudo em Blade e PHP. Frontend em resources/views/.
  • Nenhum — o esqueleto puro, para aprender o framework ou construir uma API.
laravel new meu-app                            # pergunta qual kit
laravel new meu-app --using=vendor/starter-kit # kit da comunidade, via Packagist
  • Os kits React, Vue e Svelte usam Inertia, mantendo o roteamento e os controllers no servidor, sem camada de API separada.
  • Teams — uma opção no mesmo prompt. Adiciona telas de gerenciamento de times e rotas escopadas por time (/{current_team}/dashboard).
  • WorkOS AuthKit — outra opção. Troca a autenticação nativa por um provedor hospedado, com login social, passkeys, magic link e SSO. Precisa de WORKOS_CLIENT_ID, WORKOS_API_KEY e WORKOS_REDIRECT_URL no .env.

Todo kit usa o Laravel Fortify para autenticação, configurado em config/fortify.php:

use Laravel\Fortify\Features;

'features' => [
    Features::registration(),          // remova esta linha para fechar o cadastro público
    Features::resetPasswords(),
    Features::emailVerification(),
    Features::twoFactorAuthentication(['confirm' => true]),
],
  • A lógica de cadastro fica em app/Actions/Fortify/CreateNewUser.php.
  • Nos kits com Inertia, desligar uma funcionalidade também exige remover as rotas dela usadas nos componentes, senão o build quebra.
  • Breeze e Jetstream eram a geração anterior de starter kits; a documentação atual não os lista mais, então use os kits acima em projetos novos.
  • Essa geração de kits chegou no Laravel 12; o kit Svelte e a autenticação via Fortify vieram no Laravel 13.

Ambiente Local: Herd e Sail

php artisan serve parte do princípio de que você já tem PHP, Composer e um banco instalados e configurados. Herd e Sail eliminam esse trabalho de setup.

Herd (macOS / Windows)

herd park ~/Sites          # serve todos os projetos dessa pasta
herd link meu-app          # serve um projeto específico como meu-app.test
herd php -v                # o binário PHP gerenciado pelo Herd
herd open                  # abre o projeto atual no navegador
  • O Herd é um app nativo com PHP, Composer e nginx. Projetos num diretório “parkeado” são servidos automaticamente em <pasta>.test.
  • Troque a versão do PHP por projeto pela interface ou com herd use php@8.3.
  • O Herd já traz os binários php, composer, laravel, node, npm e nvm; por isso o node pode não aparecer no PATH fora do seu shell.
  • A versão paga adiciona bancos de dados, captura de e-mails e visualização de logs.

Sail (Docker)

php artisan sail:install     # adiciona o Sail a um projeto existente
./vendor/bin/sail up -d      # sobe os containers
./vendor/bin/sail down       # derruba os containers
php artisan serve  sail artisan serve
php artisan migrate  sail artisan migrate
composer install  sail composer install
npm run dev  sail npm run dev
php artisan tinker  sail artisan tinker
  • Empacota PHP, MySQL, Redis e outros como containers via Docker Compose, sem instalar nada na máquina. Prefixe todo comando com sail.
  • Encurte com um alias no shell: alias sail='[ -f sail ] && sh sail || sh vendor/bin/sail'.
  • Rodar php artisan migrate puro na máquina é um erro comum; usa o PHP do host, não o banco do container.
  • No .env, DB_HOST é o nome do serviço (mysql), não 127.0.0.1.

Herd e Sail mudam onde os comandos rodam, não o que eles fazem. Todo o resto deste cheat sheet é igual nos dois casos.

Arquitetura: MVC

O Laravel segue o padrão MVC. A requisição percorre a aplicação em uma direção:

Requisição → routes/web.php → Controller → Model (Eloquent) → Banco

                          View (Blade) → Resposta
  • Rota (routes/web.php, api.php) — liga uma URL e um verbo HTTP a um código.
  • Controller (app/Http/Controllers/) — recebe a requisição, orquestra, responde.
  • Model (app/Models/) — representa uma tabela, guarda queries e relações.
  • View (resources/views/) — templates Blade que renderizam o HTML.
app/Http/Middleware/     # código que roda antes/depois de cada requisição
bootstrap/app.php        # bootstrap da app: rotas, middleware, exceções
config/                  # arquivos de configuração, que leem do .env
database/migrations/     # mudanças de schema versionadas
database/seeders/        # dados iniciais/de exemplo
public/                  # raiz web, ponto de entrada (index.php) e assets buildados
resources/js|css/        # código-fonte do frontend, compilado pelo Vite
routes/                  # definição das rotas
storage/                 # logs, cache, views compiladas, arquivos enviados
  • Mantenha os controllers magros: valide, delegue, responda. Regra de negócio vive nos models ou em classes dedicadas, não no controller.

Comandos Artisan do Dia a Dia

php artisan serve                   # sobe o servidor de desenvolvimento
php artisan serve --port=8001       # em outra porta
php artisan tinker                  # REPL interativo com a aplicação carregada
php artisan route:list              # todas as rotas registradas
php artisan route:list --path=user  # filtrado
php artisan about                   # ambiente, versões, estado dos caches
  • artisan é a CLI. php artisan list mostra tudo; php artisan help <comando> explica um.

Geradores

php artisan make:model Post -mcr    # model + migration + controller + rotas resource
php artisan make:model Post -mfc    # model + migration + factory + controller
php artisan make:model Post -m      # model + migration
php artisan make:controller PostController --resource
php artisan make:migration create_posts_table
php artisan make:request StorePostRequest
php artisan make:middleware EnsureUserIsAdmin
php artisan make:seeder PostSeeder
php artisan make:factory PostFactory
php artisan make:command SyncPosts

Banco de Dados

php artisan migrate                 # roda as migrations pendentes
php artisan migrate --path=database/migrations/2024_01_01_000000_create_posts_table.php  # roda uma migration específica
php artisan migrate:status          # o que rodou e o que não rodou
php artisan migrate:rollback        # desfaz o último lote
php artisan migrate:fresh --seed    # dropa tudo, re-migra e roda os seeders
php artisan db:seed                 # roda só os seeders
  • migrate:fresh apaga todos os dados. Nunca rode em produção.

Cache e Manutenção

php artisan optimize:clear   # limpa cache de config, rotas, views e aplicação de uma vez
php artisan config:clear
php artisan cache:clear
php artisan view:clear
php artisan storage:link     # cria o link public/storage → storage/app/public
php artisan queue:work       # processa os jobs da fila
php artisan down             # modo manutenção
php artisan up
  • Em produção, cacheie: php artisan config:cache route:cache view:cache.
  • Em desenvolvimento, mantenha limpo, já que config cacheada ignora mudanças no .env.

Rotas

// routes/web.php
use App\Http\Controllers\PostController;

Route::get('/', fn () => view('welcome'));

Route::get('/posts', [PostController::class, 'index'])->name('posts.index');
Route::post('/posts', [PostController::class, 'store']);
Route::get('/posts/{post}', [PostController::class, 'show']);
Route::put('/posts/{post}', [PostController::class, 'update']);
Route::delete('/posts/{post}', [PostController::class, 'destroy']);

// As sete rotas de CRUD de uma vez
Route::resource('posts', PostController::class);

// Agrupamento
Route::middleware('auth')->prefix('admin')->group(function () {
    Route::get('/dashboard', [DashboardController::class, 'index']);
});
  • Tipar o model no método (show(Post $post)) ativa o route model binding: o Laravel busca o registro e devolve 404 automaticamente se não existir.
  • Use rotas nomeadas e route('posts.index') em vez de URLs escritas na mão.
  • routes/web.php tem sessão e CSRF; routes/api.php é stateless.

Controllers

namespace App\Http\Controllers;

use App\Models\Post;
use Illuminate\Http\Request;

class PostController extends Controller
{
    public function index()
    {
        return view('posts.index', ['posts' => Post::latest()->paginate(15)]);
    }

    public function show(Post $post)   // route model binding
    {
        return view('posts.show', compact('post'));
    }

    public function store(Request $request)
    {
        $data = $request->validate([
            'title' => ['required', 'string', 'max:255'],
            'body'  => ['required'],
        ]);

        $post = Post::create($data);

        return redirect()->route('posts.show', $post)->with('status', 'Post criado!');
    }
}

Eloquent

// Leitura
Post::all();
Post::find(1);
Post::findOrFail(1);                       // lança 404 se não existir
Post::where('published', true)->get();
Post::where('views', '>', 100)->orderBy('views', 'desc')->take(5)->get();
Post::first();
Post::count();

// Escrita
$post = Post::create(['title' => 'Oi', 'body' => '...']);   // precisa de $fillable
$post->update(['title' => 'Atualizado']);
$post->delete();

// Relações
class Post extends Model
{
    protected $fillable = ['title', 'body'];

    public function user()     { return $this->belongsTo(User::class); }
    public function comments() { return $this->hasMany(Comment::class); }
}

$post->comments;                       // carregamento sob demanda
Post::with('comments')->get();         // eager loading, evita o problema N+1
  • create() e update() só atribuem campos listados em $fillable. Uma coluna que “sumiu” silenciosamente quase sempre é um $fillable incompleto.
  • O model Post mapeia para a tabela posts por convenção (plural, snake_case).
use Illuminate\Database\Eloquent\SoftDeletes;

class Post extends Model
{
    use SoftDeletes;   // precisa de uma coluna deleted_at anulável, veja Migrations
}

$post->delete();              // marca deleted_at, mantém a linha
$post->trashed();             // true se foi soft deleted
$post->restore();             // limpa o deleted_at
$post->forceDelete();         // remove a linha de verdade

Post::withTrashed()->get();   // inclui linhas soft deleted
Post::onlyTrashed()->get();   // só as linhas soft deleted
  • SoftDeletes marca a linha como deletada em vez de removê-la. Toda query normal exclui as linhas deletadas automaticamente, então nada mais no app precisa filtrar isso na mão.
  • Adicione a coluna com $table->softDeletes() numa migration.

Models Limpos

class Order extends Model
{
    public function isPaid(): bool
    {
        return $this->status === 'paid';
    }
}

if ($order->isPaid()) {
    // ...
}
  • Lógica usada em mais de um lugar, um controller, um job, uma view, pertence ao model como método, não copiada e colada em cada chamada.
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Support\Facades\Hash;

class User extends Model
{
    protected function fullName(): Attribute
    {
        return Attribute::make(
            get: fn () => "{$this->first_name} {$this->last_name}",
        );
    }

    protected function password(): Attribute
    {
        return Attribute::make(
            set: fn (string $value) => Hash::make($value),
        );
    }
}

$user->full_name;            // acessado como uma coluna, sem parênteses
$user->password = 'secret';  // já sai com hash ao ser atribuído
  • Acesse um accessor como $user->full_name, não $user->fullName(). Quem chama não precisa saber se um atributo é uma coluna de verdade ou calculado; os dois devem parecer iguais.
  • O método é camelCase (fullName); o Eloquent expõe como o atributo snake_case (full_name).
use Illuminate\Database\Eloquent\Attributes\Scope;
use Illuminate\Database\Eloquent\Builder;

class Post extends Model
{
    #[Scope]
    protected function published(Builder $query): void
    {
        $query->where('published', true);
    }
}

Post::published()->latest()->get();
Post::published()->where('user_id', $userId)->get();
  • Um where repetido em vários controllers é sinal de que ele pertence a um scope, encadeável como qualquer outro método de query.
{{-- Evite --}}
@php
    $total = 0;
    foreach ($order->items as $item) {
        $total += $item->price * $item->quantity;
    }
@endphp
<p>Total: {{ $total }}</p>

{{-- Prefira --}}
<p>Total: {{ $order->total }}</p>
  • @php / @endphp numa view geralmente é sinal de que a lógica pertence ao model, como um accessor, um scope, ou um método dedicado.
// app/Services/OrderService.php
class OrderService
{
    public function checkout(User $user, array $items): Order
    {
        // cria o pedido, cobra o pagamento, envia o e-mail de confirmação...
    }
}

// app/Http/Controllers/OrderController.php
class OrderController extends Controller
{
    public function __construct(private OrderService $orderService) {}

    public function store(Request $request)
    {
        $order = $this->orderService->checkout($request->user(), $request->validated());

        return redirect()->route('orders.show', $order);
    }
}
  • Quando um método de controller começa a coordenar vários models, um pagamento, uma notificação, mova essa lógica pra uma Service injetada pelo construtor. O controller fica um coordenador magro: recebe a requisição, delega, responde.
  • O Laravel não vem com um comando make:service; uma service é só uma classe comum, por convenção dentro de app/Services/.

Migrations

// database/migrations/xxxx_create_posts_table.php
public function up(): void
{
    Schema::create('posts', function (Blueprint $table) {
        $table->id();
        $table->foreignId('user_id')->constrained()->cascadeOnDelete();
        $table->string('title');
        $table->text('body');
        $table->boolean('published')->default(false);
        $table->timestamps();
    });
}

public function down(): void
{
    Schema::dropIfExists('posts');
}
  • Nunca edite uma migration que já rodou em produção; crie uma nova.
  • Localmente, php artisan migrate:fresh é o jeito rápido de refazer tudo.

Templates Blade

{{-- resources/views/posts/index.blade.php --}}
@extends('layouts.app')

@section('content')
    <h1>{{ $title }}</h1>          {{-- saída escapada --}}
    {!! $htmlConfiavel !!}         {{-- saída crua, só para conteúdo confiável --}}

    @if ($posts->isEmpty())
        <p>Nenhum post ainda.</p>
    @else
        @foreach ($posts as $post)
            <a href="{{ route('posts.show', $post) }}">{{ $post->title }}</a>
        @endforeach
    @endif

    @auth
        <p>Olá, {{ auth()->user()->name }}</p>
    @endauth

    <form method="POST" action="{{ route('posts.store') }}">
        @csrf
        <input name="title" value="{{ old('title') }}">
        @error('title') <span>{{ $message }}</span> @enderror
        <button>Salvar</button>
    </form>
@endsection
  • Todo formulário não-GET precisa de @csrf, senão a requisição é rejeitada com 419.
  • Use @method('PUT') / @method('DELETE') para verbos que o HTML não envia.

Validação

$request->validate([
    'email'    => ['required', 'email', 'unique:users,email'],
    'password' => ['required', 'confirmed', 'min:8'],
    'age'      => ['nullable', 'integer', 'between:18,120'],
]);
php artisan make:request StorePostRequest
  • Em caso de falha, o Laravel redireciona de volta automaticamente com os erros e os dados antigos, sem try/catch.
  • Para conjuntos maiores de regras, use um Form Request, gerado acima.

Vite e Assets

{{-- No <head> do seu layout --}}
@vite(['resources/css/app.css', 'resources/js/app.js'])
npm run dev     # desenvolvimento: hot reload, precisa ficar rodando
npm run build   # produção: gera public/build/ + manifest.json
  • Em desenvolvimento, o npm run dev roda junto com o php artisan serve. O composer run dev sobe os dois, mais o worker de fila, num terminal só.
  • Em produção, ou sempre que o servidor de dev não estiver de pé, você precisa ter rodado npm run build pelo menos uma vez.

Testes

php artisan make:test PostTest          # teste de feature, em tests/Feature
php artisan make:test PostTest --unit   # teste unitário, em tests/Unit
php artisan test                        # roda toda a suíte
php artisan test --filter=PostTest      # roda uma única classe de teste
use Illuminate\Foundation\Testing\RefreshDatabase;

class PostTest extends TestCase
{
    use RefreshDatabase;

    public function test_post_can_be_created(): void
    {
        $response = $this->post('/posts', ['title' => 'Hi', 'body' => '...']);

        $response->assertRedirect();
        $this->assertDatabaseHas('posts', ['title' => 'Hi']);
    }
}
  • O RefreshDatabase reseta seu banco. Ele migra o schema antes da suíte rodar e envolve cada teste numa transação que é desfeita depois, então um teste não vê os dados do outro.
  • Um phpunit.xml recém-criado aponta os testes para DB_CONNECTION=sqlite, DB_DATABASE=:memory:, um banco isolado e descartado a cada execução. Se essas linhas estiverem faltando, comentadas, ou editadas para apontar pro seu banco de verdade, rodar os testes vai apagar seus dados locais.
  • Confira qual banco os testes realmente usam olhando o phpunit.xml, ou rode php artisan config:show database.default (depois de php artisan config:clear) dentro do ambiente testing.
  • Testes unitários (tests/Unit) não sobem a aplicação e não conseguem tocar no banco; testes de feature (tests/Feature) sobem, e é ali que a maioria dos seus testes deve morar.

Jobs, Commands e Scheduler

php artisan make:job ProcessPodcast
namespace App\Jobs;

use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;

class ProcessPodcast implements ShouldQueue
{
    use Queueable;

    public function __construct(public Podcast $podcast) {}

    public function handle(): void
    {
        // transcodifica o podcast, gera a thumbnail...
    }
}

ProcessPodcast::dispatch($podcast);       // empurrado pra fila
ProcessPodcast::dispatchSync($podcast);   // roda na hora, sem fila
  • Um job é um trabalho que não deveria travar a requisição: enviar um e-mail, processar um arquivo, chamar uma API lenta. dispatch() coloca na fila; um worker (php artisan queue:work) pega e roda o handle().
  • Precisa de uma conexão de fila no .env (QUEUE_CONNECTION=database, redis, etc). O sync, padrão num projeto novo, roda os jobs na hora sem fila de verdade, útil em desenvolvimento local.
php artisan make:command SendEmails
namespace App\Console\Commands;

use Illuminate\Console\Command;

class SendEmails extends Command
{
    protected $signature = 'emails:send {user}';
    protected $description = 'Send a batch of emails to a user';

    public function handle(): void
    {
        $this->info("Sending emails to user #{$this->argument('user')}...");
    }
}
php artisan emails:send 1
  • Um command é qualquer coisa que você quer rodar pela CLI: um script avulso, uma tarefa de manutenção, algo que você também vai querer agendar. Commands em app/Console/Commands são descobertos automaticamente.
// routes/console.php
use Illuminate\Support\Facades\Schedule;

Schedule::command('emails:send 1')->daily();
Schedule::job(new ProcessPodcast($podcast))->everyFiveMinutes();
Schedule::call(fn () => Log::info('Heartbeat'))->everyMinute();
  • O scheduler substitui uma entrada de cron por tarefa por código, definido em routes/console.php. O servidor só precisa de uma linha de cron, rodando a cada minuto:
* * * * * cd /path-to-your-project && php artisan schedule:run >> /dev/null 2>&1
  • Localmente, pule a entrada de cron e rode php artisan schedule:work no lugar; ele fica em loop no terminal e dispara as tarefas devidas a cada minuto.
  • php artisan schedule:list mostra toda tarefa agendada e a próxima hora que ela vai rodar.

Erros Comuns

  • No application encryption key has been specifiedAPP_KEY vazia ou .env faltando. Correção: cp .env.example .env && php artisan key:generate.
  • Página em branco ou 500 genérico — o erro real está escondido. Ponha APP_DEBUG=true no .env, ou leia storage/logs/laravel.log.
  • SQLSTATE[HY000] [2002] Connection refused — o banco não está rodando, ou DB_HOST/DB_PORT no .env estão errados.
  • SQLSTATE[HY000] [1045] Access denied for userDB_USERNAME/DB_PASSWORD errados. Ajuste e rode php artisan config:clear.
  • SQLSTATE[42S02] Base table or view not found — as migrations nunca rodaram. Correção: php artisan migrate.
  • Unable to open database file (SQLite) — o arquivo não existe. Correção: touch database/database.sqlite.
  • Vite manifest not found at public/build/manifest.json — assets nunca foram buildados e o dev server está desligado. Correção: npm run build, ou rode npm run dev.
  • Página carrega mas sem CSS/JS — o npm run dev parou. Reinicie, ou rode npm run build.
  • 419 Page Expired — falta @csrf ou a sessão expirou. Adicione @csrf no formulário, ou limpe os cookies.
  • 404 numa rota que você acabou de criar — rotas cacheadas, ou verbo errado. Rode php artisan route:clear, depois confira php artisan route:list.
  • Target class [FooController] does not exist — namespace errado ou typo na rota. Importe o controller e use [FooController::class, 'metodo'].
  • Class "App\Models\Post" not found — autoloader desatualizado. Correção: composer dump-autoload.
  • Mudança no .env não surte efeito — config está cacheada. Correção: php artisan config:clear (ou optimize:clear).
  • The stream or file .../laravel.log could not be opened — sem permissão de escrita. Correção: chmod -R 775 storage bootstrap/cache.
  • Imagens enviadas dão 404 em /storage/... — falta o symlink. Correção: php artisan storage:link.
  • Add [title] to fillable property — mass assignment bloqueado. Adicione a coluna ao $fillable do model.
  • Attempt to read property on null — uma query retornou null. Use findOrFail(), ou proteja com @if/?->.
  • composer install falha em ext-... — uma extensão do PHP está faltando. Instale (ex.: php-mbstring, php-xml, php-curl).
  • Usando Sail, Connection refused em 127.0.0.1 — o comando rodou no host, ou DB_HOST está errado. Prefixe com sail; use DB_HOST=mysql.

A Ordem de Depuração

tail -f storage/logs/laravel.log   # 1. leia o erro de verdade
php artisan optimize:clear         # 2. limpe todos os caches
php artisan migrate:status         # 3. o schema está atualizado?
php artisan route:list             # 4. a rota existe como você imagina?
php artisan about                  # 5. ambiente, conexão com o banco, caches
  • A maioria dos 500 de iniciante é uma de três coisas: falta de APP_KEY, falta de conexão com o banco, ou migrations que nunca rodaram.

References

Related Cheat Sheets