DevTools

Cheatsheet Laravel

Framework PHP para desenvolvimento web

Voltar às linguagens
Laravel
167 cards encontrados
Categorias:
Versões:

Coleções


13 cards
Criar coleção
$colecao = collect([1, 2, 3, 4]);
$colecao = new Collection([1, 2, 3]);

// Métodos encadeáveis:
collect([1, 2, 3])->map(...)->filter(...)->values();

O collect() transforma um array numa Collection com dezenas de métodos úteis. Todos são encadeáveis e não mutam o original.

Where em coleção
$ativos = $produtos->where('ativo', true);
$caros = $produtos->where('preco', '>', 100);
$primeiros = $produtos->whereIn('id', [1, 2, 3]);

O where() filtra por valor de campo (comparação loose). Suporta operadores e variantes: whereIn, whereBetween, whereNotNull.

Sum, Avg, Max, Min
$total = $produtos->sum('preco');
$media = $produtos->avg('preco');
$max = $produtos->max('preco');
$min = $produtos->min('preco');
$contagem = $produtos->count();

Métodos de agregação sobre um campo: sum(), avg(), max(), min(), count(). Funcionam em coleções de models ou arrays.

Reduce e Each
$total = collect([1, 2, 3])->reduce(
    fn($carry, $n) => $carry + $n, 0
); // 6

$produtos->each(function ($p) {
    $p->update(['visto' => true]);
});

O reduce() acumula um valor ao iterar. O each() executa uma ação em cada elemento (sem transformar). Retorna a coleção original.

Map (transformar)
$dobro = collect([1, 2, 3])
    ->map(fn($n) => $n * 2);
// [2, 4, 6]

$nomes = $produtos->map(fn($p) => $p->nome);

O map() aplica uma função a cada elemento e retorna uma nova coleção com os resultados transformados. Não altera o original.

GroupBy (agrupar)
$porCategoria = $produtos->groupBy('categoria');
// ['eletrónica' => [...], 'acessórios' => [...]]

$porAno = $pedidos->groupBy(fn($p) => $p->created_at->year);

O groupBy() agrupa elementos pelo valor de um campo ou callback. Retorna uma coleção de coleções indexada pela chave.

Contains e Search
$tem = $produtos->contains('nome', 'Portátil');
$tem = collect([1, 2, 3])->contains(2);
$indice = collect(['a', 'b'])->search('b'); // 1

O contains() verifica se existe um elemento com o valor. O search() retorna o índice da primeira ocorrência.

Filter (filtrar)
$pares = collect([1, 2, 3, 4])
    ->filter(fn($n) => $n % 2 === 0);
// [2, 4]

$ativos = $produtos->filter(fn($p) => $p->ativo);

O filter() mantém apenas os elementos que passam no teste. Sem callback, remove valores falsy. Usa values() para reindexar.

SortBy (ordenar)
$ordenado = $produtos->sortBy('preco');
$desc = $produtos->sortByDesc('preco');
$multi = $produtos->sortBy([['nome', 'asc'], ['preco', 'desc']]);

O sortBy() ordena por um campo (ascendente). O sortByDesc() é descendente. Aceita array para ordenação múltipla.

First, Last e Find
$primeiro = $colecao->first();
$ultimo = $colecao->last();
$maior = $colecao->first(fn($n) => $n > 10);
$padrao = $colecao->first(fn($n) => $n > 100, 0);

O first() e last() obtêm extremos. Com callback, retorna o primeiro que satisfaz a condição. O segundo argumento é o valor padrão.

Pluck (extrair coluna)
$nomes = $produtos->pluck('nome');
// ['Portátil', 'Rato', 'Teclado']

$pares = $produtos->pluck('nome', 'id');
// [1 => 'Portátil', 2 => 'Rato']

O pluck() extrai um campo de todos os elementos. O segundo argumento define a chave do array resultante.

Unique e Values
$unicos = collect([1, 2, 2, 3])->unique();
// [1, 2, 3]

$cats = $produtos->unique('categoria');
$reindex = $cats->values();

O unique() remove duplicados (por valor ou campo). O values() reindexa as chaves numéricas após filtrar.

Chunk e Split
$blocos = collect([1, 2, 3, 4, 5])->chunk(2);
// [[1,2], [3,4], [5]]

$grupos = collect([1, 2, 3, 4])->split(2);
// [[1,2], [3,4]]

O chunk() divide em blocos de N elementos. O split() divide em N grupos de tamanho aproximadamente igual.

Instalação e Configuração


12 cards
Criar novo projeto
composer create-project laravel/laravel meu-projeto
cd meu-projeto
php artisan serve

Cria um novo projeto Laravel com todas as dependências. O composer descarrega o framework e o artisan serve inicia o servidor em http://127.0.0.1:8000.

Variáveis de ambiente
# .env
APP_NAME=MeuSite
APP_ENV=local
APP_DEBUG=true
APP_URL=http://localhost

# Acesso no código:
$nome = env('APP_NAME');
$debug = env('APP_DEBUG', false);

Valores sensíveis ficam no .env (nunca no repositório). Acede com env(), passando um valor padrão como segundo argumento. Em produção usa APP_DEBUG=false.

Instalar Breeze (auth)
composer require laravel/breeze --dev
php artisan breeze:install blade
npm install && npm run dev

O Breeze instala login, registo e recuperação de password prontos. Escolhe o stack (blade, react ou vue) e faz npm install para os assets.

Laravel Installer
composer global require laravel/installer
laravel new meu-projeto

O laravel/installer é uma alternativa mais rápida ao create-project. Depois de instalado globalmente, usa laravel new para criar projetos.

Estrutura de pastas
app/           → Models, Controllers, Services
bootstrap/     → Inicialização do framework
config/        → Ficheiros de configuração
database/      → Migrations, seeders, factories
public/        → index.php, CSS, JS, imagens
resources/     → Views Blade, assets
routes/        → web.php, api.php, console.php
storage/       → Logs, cache, uploads
vendor/        → Dependências Composer

A estrutura padrão do Laravel separa responsabilidades. O app/ contém a lógica, routes/ as rotas e resources/views/ os templates Blade.

Configuração por ficheiros
// config/app.php
'name' => env('APP_NAME', 'Laravel'),
'timezone' => 'Europe/Lisbon',
'locale' => 'pt',

// Acesso:
$nome = config('app.name');
config(['app.locale' => 'en']);

Cada ficheiro em config/ agrupa definições por área. Acede com config('ficheiro.chave') e define em runtime com config(['chave' => valor]).

Gerar APP_KEY
php artisan key:generate

Gera a APP_KEY no ficheiro .env. É obrigatória para encriptação de sessões, cookies e dados sensíveis. Sem ela a aplicação não funciona.

Modo de manutenção
php artisan down
php artisan down --secret=abc123
php artisan up

O down coloca o site em manutenção (retorna 503). Com --secret podes aceder via URL secreta. O up retira a manutenção.

Ficheiro de rotas web.php
// routes/web.php
use Illuminate\Support\Facades\Route;

Route::get('/', function () {
    return view('welcome');
});

O ficheiro routes/web.php define as rotas web com sessão e CSRF. Para APIs usa routes/api.php (sem sessão, prefixo /api).

Configurar base de dados
# .env
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=meu_banco
DB_USERNAME=root
DB_PASSWORD=

Edita o ficheiro .env na raiz do projeto com as credenciais da base de dados. O Laravel suporta mysql, pgsql, sqlite e sqlsrv.

Ver versão e info
php artisan --version
php artisan about

O --version mostra a versão do framework. O about (Laravel 9+) exibe um resumo completo: versão PHP, drivers, ambiente e configuração.

Aliases e Facades
// config/app.php → aliases
'Cache' => Illuminate\Support\Facades\Cache::class,
'DB'    => Illuminate\Support\Facades\DB::class,

// Uso sem import:
Cache::get('chave');
DB::table('users')->get();

Os Facades dão acesso estático a serviços do container. Os aliases em config/app.php permitem usá-los sem use (ex.: Cache::, DB::).

Artisan CLI


14 cards
Listar comandos
php artisan list
php artisan list --raw
php artisan help make:model

O list mostra todos os comandos Artisan disponíveis. O help explica a sintaxe e opções de um comando específico.

Tinker (REPL)
php artisan tinker
>>> App\Models\Produto::count()
>>> $p = App\Models\Produto::find(1)
>>> $p->nome

O tinker é um terminal interativo (REPL) para testar código e consultar models diretamente. Ideal para debug rápido e exploração da base de dados.

Criar Seeder e Factory
php artisan make:seeder ProdutoSeeder
php artisan make:factory ProdutoFactory --model=Produto

O seeder insere dados fixos na BD. A factory gera dados falsos realistas para testes. Ambos ficam em database/.

Criar Job e Command
php artisan make:job EnviarEmail
php artisan make:command LimparLogs

O job é uma tarefa assíncrona para filas. O command é um comando Artisan personalizado. Ambos ficam em app/.

Criar Model
php artisan make:model Produto
php artisan make:model Produto -mcrf
# -m migration, -c controller, -r resource, -f factory

Cria o Model em app/Models/. As flags combinam criação de migration, controller resource e factory num só comando.

Listar rotas
php artisan route:list
php artisan route:list --name=produtos
php artisan route:list --method=GET

Mostra todas as rotas registadas com método HTTP, URI, nome e middleware. Os filtros --name e --method ajudam a encontrar rotas específicas.

Criar FormRequest
php artisan make:request StoreProdutoRequest

Cria uma classe de validação dedicada em app/Http/Requests/. Separa as regras do controller, tornando-o mais limpo e reutilizável.

Agendar e executar tarefas
php artisan schedule:run      # executa tarefas agendadas
php artisan queue:work        # processa fila
php artisan queue:listen      # processa com reload
php artisan test              # corre testes PHPUnit

O schedule:run executa tarefas cron. O queue:work processa jobs em fila. O test corre a suite de testes PHPUnit.

Criar Controller
php artisan make:controller ProdutoController
php artisan make:controller ProdutoController --resource
php artisan make:controller Api/ProdutoController --api

Cria um controller em app/Http/Controllers/. O --resource gera os 7 métodos CRUD e o --api gera 5 (sem create/edit).

Limpar caches
php artisan config:clear
php artisan cache:clear
php artisan view:clear
php artisan route:clear
php artisan optimize:clear  # todos de uma vez

Limpa caches de configuração, dados, views compiladas e rotas. O optimize:clear limpa tudo num só comando. Essencial após alterar configurações.

Criar Middleware
php artisan make:middleware VerificaAdmin

Cria um middleware em app/Http/Middleware/. Regista-o em app/Http/Kernel.php ou aplica diretamente na rota.

Criar Migration
php artisan make:migration create_produtos_table
php artisan make:migration add_sku_to_produtos_table --table=produtos

Cria uma migration com timestamp em database/migrations/. Se o nome contém create_x_table, o Laravel gera o esqueleto com Schema::create.

Otimizar para produção
php artisan config:cache
php artisan route:cache
php artisan view:cache
php artisan optimize

Gera caches de configuração, rotas e views para melhor performance em produção. O optimize faz config + route cache de uma vez.

Criar Policy e Event
php artisan make:policy ProdutoPolicy --model=Produto
php artisan make:event PedidoCriado
php artisan make:listener EnviarNotificacao --event=PedidoCriado

A policy define regras de autorização por model. O event/listener implementam o padrão observador para reagir a ações.

Rotas


14 cards
Rota GET básica
Route::get('/produtos', [ProdutoController::class, 'index']);

Route::get('/ola', function () {
    return 'Olá Mundo';
});

Define uma rota GET que chama um método do controller ou uma closure. O primeiro argumento é a URI e o segundo o handler.

Rota com nome
Route::get('/produtos', [ProdutoController::class, 'index'])
    ->name('produtos.index');

// Gerar URL:
$url = route('produtos.index');
$link = route('produtos.show', $produto);

O name() dá um nome à rota, permitindo gerar URLs com route(). Se a URI mudar, os links continuam válidos.

Restringir parâmetros
Route::get('/produtos/{id}', ...)
    ->whereNumber('id');

Route::get('/users/{nome}', ...)
    ->where('nome', '[A-Za-z]+');

Route::get('/files/{path}', ...)
    ->where('path', '.*');

O where() aplica regex aos parâmetros. Atalhos: whereNumber, whereAlpha, whereAlphaNumeric, whereUuid.

Route model binding
Route::get('/produtos/{produto}', function (Produto $produto) {
    return $produto->nome;
});
// /produtos/1 → Produto::find(1) automático

O model binding injeta automaticamente a instância do Model a partir do ID na URL. Se não existir, retorna 404 automaticamente.

Rotas POST, PUT, DELETE
Route::post('/produtos', [ProdutoController::class, 'store']);
Route::put('/produtos/{id}', [ProdutoController::class, 'update']);
Route::delete('/produtos/{id}', [ProdutoController::class, 'destroy']);
Route::patch('/produtos/{id}', [ProdutoController::class, 'patch']);

Cada verbo HTTP tem o seu método: post, put, patch, delete. O PUT substitui tudo, o PATCH atualiza parcialmente.

Route::resource
Route::resource('produtos', ProdutoController::class);

// Gera 7 rotas:
// GET    /produtos        → index
// GET    /produtos/create → create
// POST   /produtos        → store
// GET    /produtos/{id}   → show
// GET    /produtos/{id}/edit → edit
// PUT    /produtos/{id}   → update
// DELETE /produtos/{id}   → destroy

O resource regista as 7 rotas RESTful de uma vez. Usa apiResource para APIs (exclui create e edit).

Redirecionamento
Route::redirect('/antigo', '/novo');
Route::redirect('/antigo', '/novo', 301);
Route::permanentRedirect('/velho', '/novo');

O redirect() redireciona uma URI para outra (302 por padrão). O permanentRedirect() usa 301 (permanente, bom para SEO).

Rate limiting
Route::middleware('throttle:60,1')->group(function () {
    Route::get('/api/dados', ...);
});
// 60 pedidos por minuto

O throttle limita pedidos por utilizador. O formato é throttle:max,minutos. Essencial para proteger APIs de abuso.

Rota com parâmetros
Route::get('/produtos/{id}', function ($id) {
    return "Produto: " . $id;
});

Route::get('/users/{user}/posts/{post}', function ($user, $post) {
    return [$user, $post];
});

Os parâmetros entre chavetas são passados para a closure ou método. Podes ter múltiplos parâmetros na mesma URI.

Grupo com middleware
Route::middleware('auth')->group(function () {
    Route::get('/painel', [PainelController::class, 'index']);
    Route::get('/perfil', [PerfilController::class, 'show']);
});

O middleware() aplica proteção a todas as rotas do grupo. Neste caso, só utilizadores autenticados (auth) acedem.

Rota que retorna view
Route::view('/sobre', 'pages.sobre');
Route::view('/faq', 'pages.faq', ['titulo' => 'FAQ']);

O Route::view() retorna uma view sem precisar de controller. O terceiro argumento opcional passa dados para a view.

Parâmetro opcional
Route::get('/users/{id?}', function ($id = null) {
    return $id ?? 'Todos os users';
});

O ? torna o parâmetro opcional. A variável deve ter um valor padrão (= null) para funcionar quando o parâmetro não é fornecido.

Grupo com prefixo
Route::prefix('admin')->name('admin.')->group(function () {
    Route::get('/users', [AdminController::class, 'users'])
        ->name('users');  // admin.users
});
// URL: /admin/users

O prefix() adiciona um segmento a todas as URIs do grupo. O name() prefixa os nomes das rotas (ex.: admin.users).

Rota fallback (404)
Route::fallback(function () {
    return view('erros.404');
});

O fallback é executado quando nenhuma rota corresponde ao pedido. Deve ser a última rota registada no ficheiro.

Controllers e Middleware


12 cards
Controller básico
namespace App\Http\Controllers;

class ProdutoController extends Controller
{
    public function index()
    {
        $produtos = Produto::all();
        return view('produtos.index', compact('produtos'));
    }
}

Um controller agrupa a lógica HTTP numa classe. O método retorna uma view, JSON ou redirect. Fica em app/Http/Controllers/.

Métodos do Resource
index()    → GET    /produtos       (listar)
create()   → GET    /produtos/create (formulário)
store()    → POST   /produtos       (guardar)
show()     → GET    /produtos/{id}  (detalhe)
edit()     → GET    /produtos/{id}/edit (form edição)
update()   → PUT    /produtos/{id}  (atualizar)
destroy()  → DELETE /produtos/{id}  (eliminar)

Os 7 métodos de um controller resource seguem a convenção REST. Cada um mapeia para um verbo HTTP e uma URI específica.

Vários middlewares
Route::get('/admin/relatorios', ...)
    ->middleware(['auth', 'admin', 'verificado']);

Aplica múltiplos middlewares em cadeia com um array. São executados pela ordem indicada: primeiro auth, depois admin, etc.

Injeção do Request
public function store(Request $request)
{
    $nome = $request->input('nome');
    $email = $request->email;
    $todos = $request->all();
    $so = $request->only(['nome', 'email']);
}

O Request é injetado automaticamente pelo container. Acede aos dados com input(), propriedade mágica ou all()/only().

Redirect com flash
return redirect()->route('produtos.index')
    ->with('sucesso', 'Produto criado!');

return back()->withInput()
    ->withErrors(['nome' => 'Obrigatório']);

O redirect() com with() guarda uma mensagem flash na sessão. O back() volta ao formulário com os dados e erros.

Dependency Injection
public function show(Produto $produto, LoggerInterface $log)
{
    $log->info("Visto: {$produto->id}");
    return view('produtos.show', compact('produto'));
}

O container resolve automaticamente as dependências type-hinted. O model binding injeta o model e o LoggerInterface é resolvido via container.

Retornar JSON
return response()->json([
    'sucesso' => true,
    'dados' => $produtos,
], 200);

return response()->json(['erro' => 'Não encontrado'], 404);

O response()->json() converte arrays em resposta JSON com o status code indicado. Ideal para APIs REST.

Middleware básico
public function handle(Request $request, Closure $next)
{
    if (!auth()->user()->is_admin) {
        abort(403, 'Acesso negado');
    }

    return $next($request);
}

O middleware intercepta o pedido antes do controller. Se a condição falhar, faz abort(). Se passar, chama $next($request) para continuar.

Respostas HTTP
return response('OK', 200)
    ->header('X-Custom', 'valor')
    ->cookie('token', $valor, 60);

return response()->download($path);
return response()->file($path);

O response() permite definir status, headers e cookies. O download() força o download de um ficheiro e o file() mostra-o inline.

Controller Invokable
class MostrarPerfil
{
    public function __invoke()
    {
        return view('perfil');
    }
}

Route::get('/perfil', MostrarPerfil::class);

Um controller invokable tem um único método __invoke(). Não precisa de indicar o método na rota. Ideal para ações simples.

Registar middleware
// app/Http/Kernel.php
protected $middlewareAliases = [
    'admin' => \App\Http\Middleware\VerificaAdmin::class,
];

// Uso na rota:
Route::get('/admin', ...)->middleware('admin');

Regista o alias em $middlewareAliases no Kernel.php. Depois aplica com middleware('alias') nas rotas ou grupos.

Abort e erros HTTP
abort(404);
abort(403, 'Sem permissão');
abort_if(!$produto, 404);
abort_unless(auth()->check(), 401);

O abort() lança uma exceção HTTP com o código indicado. O abort_if e abort_unless são atalhos condicionais.

Eloquent ORM


18 cards
Model básico
namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Produto extends Model
{
    protected $fillable = ['nome', 'preco', 'descricao'];
}

O $fillable define os campos preenchíveis em massa (create/update). Sem ele, o Laravel bloqueia atribuição em massa por segurança.

Criar registo
$produto = Produto::create([
    'nome' => 'Portátil',
    'preco' => 999.99,
]);

// Alternativa:
$produto = new Produto();
$produto->nome = 'Portátil';
$produto->save();

O create() cria e guarda de uma vez (requer $fillable). A alternativa instancia, define campos e chama save().

Relação HasMany
// No model Produto:
public function comentarios()
{
    return $this->hasMany(Comentario::class);
}

// Uso:
$produto->comentarios;
$produto->comentarios()->create(['texto' => 'Bom!']);

O hasMany() define que um produto tem muitos comentários. A FK produto_id fica na tabela comentarios. Acede como propriedade.

Eager Loading (evitar N+1)
// N+1 (mau):
$pedidos = Pedido::all();
$pedidos[0]->cliente->nome; // query por cada pedido

// Eager loading (bom):
$pedidos = Pedido::with('cliente', 'itens')->get();

O with() carrega as relações numa só consulta extra, evitando o problema N+1. Essencial para performance com listas de registos relacionados.

Selecionar colunas
Produto::select('nome', 'preco')->get();
Produto::distinct()->pluck('categoria');
Produto::selectRaw('categoria, COUNT(*) as total')
    ->groupBy('categoria')->get();

O select() traz apenas as colunas indicadas. O pluck() extrai uma coluna como array. O selectRaw() permite expressões SQL.

Buscar registos
$produtos = Produto::all();
$produto = Produto::find(1);
$produto = Produto::findOrFail(1);
$primeiro = Produto::first();
$ultimo = Produto::latest()->first();

O all() traz tudo, find() busca por ID. O findOrFail() lança 404 se não existir. O latest() ordena por created_at desc.

Atualizar registo
$produto = Produto::find(1);
$produto->update(['preco' => 899.99]);

// Ou em massa:
Produto::where('categoria', 'eletrónica')
    ->update(['desconto' => 10]);

O update() atualiza os campos indicados. Pode ser usado numa instância ou diretamente no query builder para atualizações em massa.

Relação BelongsTo
// No model Comentario:
public function produto()
{
    return $this->belongsTo(Produto::class);
}

// Uso:
$comentario->produto->nome;

O belongsTo() é o inverso do hasMany. Indica que o comentário pertence a um produto. A FK fica no model filho.

Ordenar e limitar
Produto::orderBy('preco', 'desc')
    ->limit(10)
    ->get();

Produto::inRandomOrder()->first();
Produto::skip(20)->take(10)->get(); // offset 20

O orderBy() ordena por campo e direção. O limit()/take() limita resultados e skip()/offset() salta registos.

Soft Deletes
use Illuminate\Database\Eloquent\SoftDeletes;

class Produto extends Model
{
    use SoftDeletes;
}

// Migration:
$table->softDeletes(); // coluna deleted_at

O trait SoftDeletes marca registos como eliminados sem os remover. Usa trashed() para verificar, restore() para recuperar e forceDelete() para apagar definitivamente.

Where com condições
Produto::where('preco', '>', 100)
    ->where('ativo', true)
    ->get();

Produto::where('nome', 'like', '%rato%')->get();
Produto::whereIn('id', [1, 2, 3])->get();

Encadeia condições com where(). Suporta operadores (>, <, like) e variantes como whereIn, whereBetween, whereNull.

Eliminar registo
$produto = Produto::find(1);
$produto->delete();

// Em massa:
Produto::where('ativo', false)->delete();

// Com SoftDeletes:
$produto->restore();
Produto::withTrashed()->get();

O delete() remove o registo. Com SoftDeletes, apenas marca deleted_at. O restore() recupera e withTrashed() inclui eliminados.

Relação ManyToMany
// No model Produto:
public function categorias()
{
    return $this->belongsToMany(Categoria::class);
}

// Uso:
$produto->categorias()->attach($catId);
$produto->categorias()->detach($catId);
$produto->categorias()->sync([1, 2, 3]);

O belongsToMany() requer uma tabela pivô (categoria_produto). O attach/detach adicionam/removem e o sync substitui tudo.

Paginação
$produtos = Produto::paginate(15);

// Na view Blade:
{{ $produtos->links() }}

// API:
return response()->json($produtos);

O paginate() divide resultados em páginas e gera links automaticamente. O simplePaginate() mostra só "anterior/seguinte".

Where com OR
Produto::where('preco', '>', 100)
    ->orWhere('destaque', true)
    ->get();

Produto::where(function ($q) {
    $q->where('preco', '>', 100)
      ->orWhere('destaque', true);
})->where('ativo', true)->get();

O orWhere() adiciona condições alternativas. Para agrupar lógica, usa uma closure no where() para evitar precedência incorreta.

FirstOrCreate / UpdateOrCreate
$produto = Produto::firstOrCreate(
    ['nome' => 'Rato'],
    ['preco' => 25]
);

$produto = Produto::updateOrCreate(
    ['nome' => 'Rato'],
    ['preco' => 30]
);

O firstOrCreate() busca ou cria se não existir. O updateOrCreate() busca e atualiza, ou cria com todos os valores.

Relação HasOne
// No model User:
public function perfil()
{
    return $this->hasOne(Perfil::class);
}

// Uso:
$user->perfil->bio;
$user->perfil()->create(['bio' => 'Olá']);

O hasOne() indica uma relação um-para-um. Cada utilizador tem um único perfil. A FK fica na tabela do model relacionado.

Agregações
$total = Produto::count();
$caros = Produto::where('preco', '>', 100)->count();
$max = Produto::max('preco');
$media = Produto::avg('preco');
$soma = Produto::sum('preco');
$existe = Produto::where('sku', 'X1')->exists();

Métodos de agregação: count(), max(), min(), avg(), sum(). O exists() verifica existência sem trazer dados.

Migrations e Seeders


13 cards
Estrutura de uma migration
Schema::create('produtos', function (Blueprint $table) {
    $table->id();
    $table->string('nome');
    $table->decimal('preco', 8, 2);
    $table->text('descricao')->nullable();
    $table->timestamps();
});

O Schema::create() cria a tabela. O id() é auto-incremento e timestamps() adiciona created_at e updated_at.

Executar migrações
php artisan migrate
php artisan migrate:rollback
php artisan migrate:rollback --step=2
php artisan migrate:fresh
php artisan migrate:fresh --seed

O migrate executa pendentes. O rollback desfaz o último lote. O fresh apaga tudo e recria. Com --seed executa os seeders depois.

Seeder com Factory
// Factory:
public function definition()
{
    return [
        'nome' => fake()->word(),
        'preco' => fake()->randomFloat(2, 5, 500),
    ];
}

// Seeder:
Produto::factory(50)->create();

A factory gera dados falsos realistas com o fake() (Faker). O create() guarda na BD; make() só instancia sem guardar.

Factory com relações
// Produto com 3 comentários:
Produto::factory()
    ->has(Comentario::factory()->count(3))
    ->create();

// Comentário com produto:
Comentario::factory()
    ->for(Produto::factory())
    ->create();

O has() cria registos relacionados (um-para-muitos). O for() define o "pai" da relação. Simplifica a criação de dados interligados.

Tipos de colunas
$table->string('nome');          // VARCHAR(255)
$table->text('descricao');       // TEXT
$table->integer('quantidade');   // INT
$table->unsignedBigInteger('user_id');
$table->boolean('ativo');        // TINYINT(1)
$table->decimal('preco', 8, 2);  // DECIMAL
$table->date('data_nascimento');
$table->timestamp('publicado_em');
$table->json('metadados');
$table->enum('estado', ['ativo', 'inativo']);

O Blueprint oferece métodos para cada tipo SQL. O string() aceita tamanho opcional: string('nome', 100).

Alterar tabela existente
Schema::table('produtos', function (Blueprint $table) {
    $table->string('sku')->after('nome');
    $table->dropColumn('campo_antigo');
});

Usa Schema::table() (não create) para modificar tabelas existentes. O dropColumn() remove colunas.

Executar seeders
php artisan db:seed
php artisan db:seed --class=ProdutoSeeder
php artisan migrate:fresh --seed

O db:seed executa o DatabaseSeeder. Com --class executa um específico. O fresh --seed recria tudo com dados.

Modificadores de coluna
$table->string('estado')->default('ativo');
$table->text('nota')->nullable();
$table->string('slug')->unique();
$table->integer('ordem')->after('nome');
$table->string('codigo')->index();

Modificadores: default() valor padrão, nullable() aceita NULL, unique() valor único, index() cria índice, after() posição.

Renomear e verificar
Schema::rename('produtos', 'artigos');

Schema::hasTable('produtos');
Schema::hasColumn('produtos', 'sku');

O rename() muda o nome da tabela. O hasTable() e hasColumn() verificam existência antes de operações condicionais.

Chamar outros seeders
// DatabaseSeeder.php
public function run()
{
    $this->call([
        UserSeeder::class,
        ProdutoSeeder::class,
        CategoriaSeeder::class,
    ]);
}

O call() no DatabaseSeeder executa seeders em ordem. Ideal para organizar a seed por domínio e controlar dependências.

Chave estrangeira
$table->foreignId('user_id')
    ->constrained()
    ->cascadeOnDelete();

$table->foreignId('categoria_id')
    ->constrained('categorias')
    ->restrictOnDelete();

O foreignId() cria a coluna e o constrained() define a FK. Opções: cascadeOnDelete() apaga em cascata, restrictOnDelete() impede.

Seeder com dados fixos
public function run()
{
    Produto::create([
        'nome' => 'Teclado',
        'preco' => 49.99,
    ]);

    Produto::insert([
        ['nome' => 'Rato', 'preco' => 25],
        ['nome' => 'Monitor', 'preco' => 299],
    ]);
}

O create() usa Eloquent (dispara eventos). O insert() é mais rápido para múltiplos registos mas ignora eventos e timestamps.

Estados de Factory
// Definir estado:
public function inativo()
{
    return $this->state(['ativo' => false]);
}

// Uso:
Produto::factory()->inativo()->create();
Produto::factory()->count(5)->inativo()->create();

Os states modificam a factory para cenários específicos. Combina com count(), has() e relações para dados complexos.

Blade


15 cards
Mostrar variável (escapado)
{{ $nome }}
{{ $produto->preco }}
{{ strtoupper($titulo) }}

As chavetas duplas {{ }} mostram o valor escapando HTML automaticamente (previne XSS). Aceita qualquer expressão PHP.

Forelse (com fallback)
@forelse($produtos as $produto)
    <p>{{ $produto->nome }}</p>
@empty
    <p>Nenhum produto encontrado.</p>
@endforelse

O @forelse combina loop com fallback. Se a coleção estiver vazia, mostra o bloco @empty em vez de não renderizar nada.

Componentes Blade
<x-alerta tipo="sucesso" />

<x-card titulo="Produto">
    <p>Conteúdo do slot</p>
</x-card>

{{-- resources/views/components/alerta.blade.php --}}
<div class="alerta {{ $tipo }}">{{ $slot }}</div>

Os componentes ficam em resources/views/components/. Props são atributos e o conteúdo vai no $slot. Mais limpos que partials.

Valor antigo (old)
<input name="email" value="{{ old('email', '') }}">
<input name="nome" value="{{ old('nome', $produto->nome ?? '') }}">

@error('email')
    <span class="erro">{{ $message }}</span>
@enderror

O old() repõe o valor submetido após erro de validação. O @error mostra a mensagem de erro do campo indicado.

HTML sem escapar
{!! $conteudoHtml !!}
{!! $artigo->corpo !!}

As chavetas com exclamação {!! !!} renderizam HTML puro sem escapar. Usa apenas com conteúdo de confiança (nunca input do utilizador).

Variável $loop
@foreach($itens as $item)
    {{ $loop->iteration }} — {{ $item }}
    @if($loop->first) Primeiro! @endif
    @if($loop->last) Último! @endif
    @if($loop->remaining) Faltam: {{ $loop->remaining }} @endif
@endforeach

A variável $loop dá informações da iteração: iteration (nº), index (base 0), first, last, remaining, count.

Token CSRF
<form method="POST" action="/produtos">
    @csrf
    <input type="text" name="nome">
    <button type="submit">Criar</button>
</form>

O @csrf gera um token oculto obrigatório em formulários POST/PUT/DELETE. Sem ele, o Laravel rejeita com erro 419.

Diretivas de condição
@isset($produto)
    Produto definido
@endisset

@empty($produtos)
    Lista vazia
@endempty

@unless($escondido)
    Visível
@endunless

O @isset verifica se existe, @empty se está vazio e @unless é o inverso de @if (executa se falso).

Condicional If
@if($user->isAdmin())
    <p>Admin</p>
@elseif($user->isEditor())
    <p>Editor</p>
@else
    <p>Utilizador</p>
@endif

As diretivas @if, @elseif, @else e @endif funcionam como o PHP. Aceitam qualquer expressão booleana.

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

@section('content')
    <h1>Produtos</h1>
@endsection

@section('title', 'Lista de Produtos')

O @extends herda do layout e o @section preenche as secções definidas com @yield. A forma curta passa o valor direto.

Métodos PUT/DELETE em forms
<form method="POST" action="/produtos/{{ $produto->id }}">
    @csrf
    @method('PUT')
    <input type="text" name="nome" value="{{ $produto->nome }}">
</form>

HTML só suporta GET/POST. O @method('PUT') adiciona um campo oculto que o Laravel interpreta como o verbo indicado.

Stacks e Push
{{-- No layout: --}}
@stack('scripts')

{{-- Na view: --}}
@push('scripts')
    <script src="/js/produtos.js"></script>
@endpush

O @stack define um ponto de acumulação e o @push adiciona conteúdo. Ideal para scripts e CSS específicos de cada página.

Loop Foreach
@foreach($produtos as $produto)
    <p>{{ $produto->nome }} — {{ $produto->preco }}€</p>
@endforeach

O @foreach percorre coleções ou arrays. Acede a cada elemento como variável. Termina com @endforeach.

Incluir partial
@include('partials.header')
@include('partials.card', ['titulo' => 'Produto X'])

{{-- Com condição: --}}
@includeWhen($user, 'partials.user-bar')

O @include insere outra view. O segundo argumento passa dados. O @includeWhen só inclui se a condição for verdadeira.

Auth e Guest
@auth
    Olá, {{ auth()->user()->name }}
@endauth

@guest
    <a href="/login">Faz login</a>
@endguest

O @auth mostra conteúdo para utilizadores autenticados e o @guest para visitantes. Alternativa ao @if(auth()->check()).

Validação


12 cards
Validação básica
$dados = $request->validate([
    'nome' => 'required|string|max:255',
    'email' => 'required|email|unique:users',
    'idade' => 'required|integer|min:18',
]);

O validate() verifica os dados e redireciona com erros automaticamente se falhar. Se passar, retorna os dados validados.

Mensagens personalizadas
$request->validate($regras, [
    'nome.required' => 'O nome é obrigatório.',
    'email.unique' => 'Este email já existe.',
    'idade.min' => 'Deve ter pelo menos :min anos.',
]);

O segundo argumento do validate() define mensagens por campo.regra. O placeholder :min é substituído pelo valor da regra.

Validação manual
use Illuminate\Support\Facades\Validator;

$validator = Validator::make($dados, $regras);

if ($validator->fails()) {
    return redirect()->back()
        ->withErrors($validator)
        ->withInput();
}

O Validator::make() dá controlo total sobre o fluxo. Verifica com fails() e redireciona manualmente com withErrors() e withInput().

Regras mais comuns
required        → obrigatório
email           → formato de email
min:8           → mínimo 8 caracteres
max:255         → máximo 255
numeric         → apenas números
integer         → número inteiro
boolean         → true/false
url             → URL válida
date            → data válida
unique:tabela   → valor único na tabela
exists:tabela   → valor deve existir

As regras encadeiam-se com |. O unique e exists verificam na base de dados. Aceitam parâmetros: unique:users,email.

FormRequest (classe dedicada)
class StoreProdutoRequest extends FormRequest
{
    public function rules()
    {
        return [
            'nome' => 'required|string|max:255',
            'preco' => 'required|numeric|min:0',
        ];
    }

    public function authorize()
    {
        return true;
    }
}

O FormRequest isola a validação numa classe. O authorize() controla permissão. Injeta no controller: store(StoreProdutoRequest $request).

Mostrar erros na view
@error('email')
    <span class="text-danger">{{ $message }}</span>
@enderror

@if($errors->any())
    <ul>
        @foreach($errors->all() as $erro)
            <li>{{ $erro }}</li>
        @endforeach
    </ul>
@endif

O @error mostra o erro de um campo. O $errors->any() verifica se há erros e $errors->all() lista todos.

Confirmar password
$request->validate([
    'password' => 'required|min:8|confirmed',
]);

// No form: campo password_confirmation

A regra confirmed exige um campo password_confirmation com o mesmo valor. Usada em registo e alteração de password.

Validar arrays
$request->validate([
    'itens' => 'required|array|min:1',
    'itens.*.nome' => 'required|string',
    'itens.*.quantidade' => 'required|integer|min:1',
]);

O * valida cada elemento de um array. O array garante que é um array e o min:1 que tem pelo menos um elemento.

Regras de data
'data_inicio' => 'required|date',
'data_fim' => 'required|date|after:data_inicio',
'nascimento' => 'date|before:today',
'agendamento' => 'date|after_or_equal:now',

Regras de data: date valida formato, after: e before: comparam com outra data ou campo. Aceita today, now ou nome de campo.

Campo opcional (nullable)
'telefone' => 'nullable|string|max:20',
'website' => 'nullable|url',
'data_fim' => 'nullable|date|after:data_inicio',

O nullable permite valor vazio/null. Sem ele, as outras regras implicam obrigatoriedade. Ideal para campos opcionais com formato específico.

Regras condicionais
use Illuminate\Validation\Rule;

'email' => [
    'required',
    Rule::unique('users')->ignore($user->id),
],

'tipo' => Rule::in(['fisica', 'juridica']),

A classe Rule permite regras complexas. O ignore() exclui o registo atual no update. O Rule::in() valida contra uma lista de valores.

Validar ficheiros
$request->validate([
    'foto' => 'required|image|mimes:jpg,png|max:2048',
    'documento' => 'file|mimes:pdf,doc|max:10240',
]);
// max em KB (2048 = 2MB)

A regra image valida que é imagem, mimes restringe tipos e max limita o tamanho em KB. O file garante que é upload.

Autenticação e Segurança


14 cards
Utilizador atual
$user = auth()->user();
$nome = auth()->user()->name;
$id = auth()->id();

// Alternativa:
use Illuminate\Support\Facades\Auth;
$user = Auth::user();

O helper auth() retorna o utilizador autenticado (ou null). O auth()->id() é atalho para o ID. O Auth facade é equivalente.

Proteger rota com auth
Route::get('/painel', ...)->middleware('auth');

Route::middleware('auth')->group(function () {
    Route::get('/perfil', ...);
    Route::get('/definicoes', ...);
});

O middleware auth redireciona para login se não autenticado. Aplica em rotas individuais ou grupos inteiros.

Usar Policy
// No controller:
$this->authorize('update', $produto);

// Condicional:
if ($user->can('update', $produto)) { ... }

// Na rota:
Route::put('/produtos/{produto}', ...)
    ->middleware('can:update,produto');

O authorize() verifica a policy e lança 403 se negado. O can() retorna boolean. O middleware can: protege a rota diretamente.

Encriptação
use Illuminate\Support\Facades\Crypt;

$encriptado = Crypt::encryptString('dado secreto');
$original = Crypt::decryptString($encriptado);

$encriptado = Crypt::encrypt(['chave' => 'valor']);

O Crypt encripta/desencripta dados com a APP_KEY. Diferente do hash, é reversível. Usa para dados sensíveis que precisas de ler depois.

Verificar autenticação
if (auth()->check()) {
    // utilizador autenticado
}

if (auth()->guest()) {
    // não autenticado
}

O check() retorna true se houver sessão ativa. O guest() é o inverso. Prefere check() a comparar user() !== null.

Hash de password
use Illuminate\Support\Facades\Hash;

$hash = Hash::make($request->password);

if (Hash::check($input, $user->password)) {
    // password correta
}

O Hash::make() gera hash seguro com bcrypt. O Hash::check() compara input com o hash guardado. Nunca guardes passwords em texto simples.

Proteção CSRF
// Em formulários:
@csrf

// Em AJAX (header):
headers: {
    'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content
}

O token CSRF protege contra cross-site request forgery. Obrigatório em POST/PUT/DELETE. Em AJAX, envia no header X-CSRF-TOKEN.

Sanitizar input
// Blade escapa automaticamente:
{{ $input }}  // seguro

// Limpar input:
$nome = strip_tags($request->nome);
$limpo = htmlspecialchars($input, ENT_QUOTES);

// Nunca confiar em:
{!! $request->input('html') !!}  // PERIGOSO

O Blade escapa com {{ }} por padrão. Para HTML do utilizador, usa strip_tags() ou bibliotecas de purificação. Nunca uses {!! !!} com input.

Login manual
Auth::login($user);
Auth::login($user, remember: true);

// Por credenciais:
if (Auth::attempt(['email' => $email, 'password' => $pass])) {
    return redirect()->intended('/painel');
}

O login() autentica programaticamente. O attempt() verifica credenciais e cria a sessão. O intended() redireciona para a página original.

Gate (autorização simples)
// AppServiceProvider:
Gate::define('admin', function ($user) {
    return $user->role === 'admin';
});

// Uso:
if (Gate::allows('admin')) { ... }
if (Gate::denies('admin')) { ... }
Gate::authorize('admin');

O Gate define capacidades simples baseadas no utilizador. O allows() retorna boolean e o authorize() lança 403 se negado.

Remember me
Auth::attempt($credentials, $request->remember);

// Verificar:
if (Auth::viaRemember()) {
    // login via cookie "lembrar-me"
}

O segundo argumento do attempt() ativa o "lembrar-me" com cookie persistente. O viaRemember() verifica se o login foi por cookie.

Logout
Auth::logout();
$request->session()->invalidate();
$request->session()->regenerateToken();

return redirect('/');

O logout() termina a sessão. O invalidate() limpa os dados e o regenerateToken() previne fixação de sessão (CSRF).

Policy (autorização por model)
class ProdutoPolicy
{
    public function update(User $user, Produto $produto)
    {
        return $user->id === $produto->user_id;
    }

    public function delete(User $user, Produto $produto)
    {
        return $user->id === $produto->user_id;
    }
}

A policy define regras de autorização ligadas a um model. Cada método recebe o User e a instância do model e retorna boolean.

Rate limiting no login
use Illuminate\Support\Facades\RateLimiter;

$key = 'login:' . $request->ip();

if (RateLimiter::tooManyAttempts($key, 5)) {
    abort(429, 'Tentativas excedidas');
}

RateLimiter::hit($key, 60);

O RateLimiter limita tentativas de login por IP. Após 5 falhas em 60 segundos, bloqueia. Essencial contra brute force.

Helpers


14 cards
dd() e dump()
dd($variavel);       // dump e morre
dump($variavel);     // dump e continua

dd($request->all());
dd(Produto::find(1)->toArray());

O dd() mostra o conteúdo formatado e para a execução (essencial para debug). O dump() mostra mas continua. Aceitam múltiplos argumentos.

now() e Carbon
$agora = now();
$amanha = now()->addDay();
$ontem = now()->subDay();
$formato = now()->format('d/m/Y H:i');
$diff = now()->diffInDays($data);

O now() retorna uma instância Carbon com métodos de data: addDay(), subMonth(), format(), diffInDays(), isWeekend().

old()
<input name="nome" value="{{ old('nome') }}">
<input name="email" value="{{ old('email', $user->email) }}">

O old() recupera o valor submetido após erro de validação. O segundo argumento é o valor padrão (ex.: valor atual do model).

view() e response()
return view('produtos.index', ['produtos' => $dados]);
return response('Texto', 200)->header('X-Custom', 'v');
return response()->json(['ok' => true]);
return response()->download($path);

O view() renderiza um template Blade com dados. O response() cria respostas HTTP com status, headers e conteúdo personalizado.

env() e config()
$debug = env('APP_DEBUG', false);
$nome = config('app.name');

// Definir em runtime:
config(['app.locale' => 'en']);

O env() lê do .env (só em config!). O config() lê dos ficheiros de configuração. Em produção com cache, env() retorna null fora de config.

redirect() e back()
return redirect('/produtos');
return redirect()->route('produtos.index');
return back();
return back()->with('msg', 'Feito!');
return redirect()->away('https://externo.com');

O redirect() redireciona para URL ou rota. O back() volta à página anterior. O away() permite URLs externas.

str() — strings
str('Olá Mundo')->slug();       // ola-mundo
str('abc')->upper();            // ABC
str('texto longo')->limit(10);  // texto lon...
str('nome_completo')->camel();  // nomeCompleto
str('NomeCompleto')->snake();   // nome_completo

O helper str() oferece uma fluent API para strings: slug(), upper(), lower(), limit(), camel(), snake(), plural().

bcrypt() e tap()
$hash = bcrypt('password123');

// tap: executa callback e retorna o objeto
$user = tap(User::find(1), function ($u) {
    $u->nome = 'Novo Nome';
    $u->save();
});

O bcrypt() é atalho para Hash::make(). O tap() executa um callback num objeto e retorna-o, útil para operações inline.

route() e url()
$url = route('produtos.show', $produto);
// http://site.test/produtos/1

$url = url('/produtos');
// http://site.test/produtos

$url = url()->current();

O route() gera URL para rotas com nome (passa parâmetros). O url() gera a partir da raiz. O current() retorna a URL atual.

session()
// Guardar:
session(['chave' => 'valor']);
session()->put('chave', 'valor');

// Ler:
$valor = session('chave', 'padrao');

// Flash (só próximo request):
session()->flash('msg', 'Sucesso!');

O session() lê e guarda valores. O flash() guarda dados só para o próximo pedido (mensagens temporárias).

collect() e data_get()
$colecao = collect([1, 2, 3]);

// Acesso nested seguro:
$nome = data_get($array, 'user.perfil.nome', 'Anónimo');
$valor = data_get($obj, 'a.b.c', null);

O collect() cria coleções. O data_get() acede a valores nested com notação de ponto e valor padrão, sem risco de erro em chaves inexistentes.

asset()
<img src="{{ asset('img/logo.png') }}">
<link href="{{ asset('css/app.css') }}">
<script src="{{ asset('js/app.js') }}"></script>

O asset() gera URL para ficheiros na pasta public/. Respeita o ASSET_URL do .env (útil para CDN).

abort()
abort(404);
abort(403, 'Sem permissão');
abort_if(!$produto, 404);
abort_unless(auth()->check(), 401);

O abort() lança uma exceção HTTP. O abort_if() aborta se a condição for verdadeira e o abort_unless() se for falsa.

app() e resolve()
$servico = app(MinhaClasse::class);
$logger = app('log');

// Equivalente:
$servico = resolve(MinhaClasse::class);

O app() resolve dependências do Service Container. Sem argumentos retorna a instância da aplicação. O resolve() é um alias.

Dicas Avançadas


16 cards
Cache de consultas
$produtos = Cache::remember('produtos', 3600, function () {
    return Produto::all();
});

// Cache permanente:
$dados = Cache::rememberForever('config-site', fn() => Setting::all());

O Cache::remember() guarda o resultado durante N segundos. Se existir em cache, retorna sem executar a closure. O rememberForever() não expira.

Eventos e Listeners
// Disparar:
event(new PedidoCriado($pedido));

// Listener:
class EnviarNotificacao
{
    public function handle(PedidoCriado $event)
    {
        $pedido = $event->pedido;
        // enviar notificação
    }
}

O event() dispara um evento. Os listeners registados reagem a ele. Desacopla a lógica: quem cria o pedido não precisa de saber quem notifica.

Model Observers
class ProdutoObserver
{
    public function created(Produto $produto)
    {
        Log::info("Criado: {$produto->id}");
    }

    public function deleted(Produto $produto)
    {
        Cache::forget('produtos');
    }
}

O Observer reage a eventos do model: created, updated, deleted, saving. Regista no provider com Produto::observe().

Transações de BD
use Illuminate\Support\Facades\DB;

DB::transaction(function () {
    $pedido->save();
    $item->save();
    $stock->decrement('quantidade');
});
// Se algo falhar, tudo é revertido

O DB::transaction() executa operações atomicamente. Se uma exceção ocorrer, faz rollback automático. Essencial para operações interdependentes.

Invalidar cache
Cache::forget('produtos');
Cache::flush();

// Tags (Redis/Memcached):
Cache::tags(['produtos'])->put('lista', $dados, 3600);
Cache::tags(['produtos'])->flush();

O forget() remove uma chave e o flush() limpa tudo. As tags permitem agrupar e invalidar caches relacionadas de uma vez.

Service Container
// Bind no provider:
$this->app->bind(PaymentGateway::class, StripeGateway::class);

// Singleton:
$this->app->singleton(Logger::class, fn() => new FileLogger());

// Resolver:
$gateway = app(PaymentGateway::class);

O container resolve dependências automaticamente. O bind() mapeia interface→implementação. O singleton() cria uma única instância partilhada.

Scheduler (tarefas cron)
// routes/console.php ou Kernel:
Schedule::command('emails:enviar')->daily();
Schedule::command('backup')->weekly();
Schedule::job(new GerarRelatorio())->hourly();
Schedule::call(fn() => Cache::flush())->dailyAt('03:00');

O Scheduler agenda tarefas sem cron externo. Métodos: daily(), hourly(), weekly(), everyMinute(), dailyAt().

Evitar N+1
// Em desenvolvimento (AppServiceProvider):
Model::preventLazyLoading(!app()->isProduction());

// Correto:
$pedidos = Pedido::with('cliente', 'itens.produto')->get();

// Lazy por chunks:
Produto::lazy(200)->each(fn($p) => $p->processar());

O preventLazyLoading() lança erro quando deteta N+1 em desenvolvimento. Usa with() para eager load. O lazy() processa em chunks sem carregar tudo.

Filas (Jobs)
// Dispatch:
EnviarEmail::dispatch($user);
EnviarEmail::dispatch($user)->delay(now()->addMinutes(5));

// Processar:
php artisan queue:work
php artisan queue:work --tries=3

O dispatch() envia o job para a fila. O queue:work processa em background. O delay() agenda para mais tarde. Ideal para emails e tarefas pesadas.

Local Scopes
// No model:
public function scopeAtivos($query)
{
    return $query->where('ativo', true);
}

public function scopeCaros($query, $minimo)
{
    return $query->where('preco', '>', $minimo);
}

// Uso:
Produto::ativos()->caros(100)->get();

Os scopes são filtros reutilizáveis no model. Prefixa com scope e usa sem o prefixo. Aceitam parâmetros adicionais.

Logs
use Illuminate\Support\Facades\Log;

Log::info('Utilizador entrou', ['id' => $user->id]);
Log::error('Falha no pagamento', ['pedido' => $id]);
Log::warning('Stock baixo', ['produto' => $p->nome]);
Log::debug('Query executada', ['sql' => $sql]);

O Log regista em storage/logs/laravel.log. Níveis: debug, info, warning, error, critical. O segundo argumento é contexto.

Upload de ficheiros
$path = $request->file('foto')->store('fotos', 'public');
$url = Storage::url($path);

// Validar:
$request->validate(['foto' => 'image|max:2048']);

// Download:
return Storage::download('ficheiros/doc.pdf');

O store() guarda o upload no disco configurado. O Storage::url() gera a URL pública. Usa o disco public com php artisan storage:link.

Criar um Job
class EnviarEmail implements ShouldQueue
{
    use Dispatchable, Queueable;

    public function __construct(public User $user) {}

    public function handle(Mailer $mailer)
    {
        $mailer->to($this->user)->send(new BemVindo());
    }
}

O ShouldQueue marca o job para filas. O handle() contém a lógica e recebe dependências via container. O construtor recebe os dados.

Accessors e Mutators
use Illuminate\Database\Eloquent\Casts\Attribute;

protected function nomeCompleto(): Attribute
{
    return Attribute::make(
        get: fn() => $this->nome . ' ' . $this->apelido,
        set: fn($v) => ['nome' => strtok($v, ' '), 'apelido' => strtok(' ')],
    );
}

Os Accessors transformam valores ao ler e os Mutators ao escrever. Acede como propriedade: $user->nome_completo.

HTTP Client
use Illuminate\Support\Facades\Http;

$resposta = Http::get('https://api.exemplo.com/dados');
$dados = $resposta->json();

$resposta = Http::withToken($token)
    ->post('https://api.exemplo.com/pedidos', ['item' => 1]);

O Http client faz chamadas externas com API fluente. Suporta get(), post(), headers, tokens, timeout e retry. Substitui o cURL.

Notificações
$user->notify(new PedidoEnviado($pedido));

// Na notificação:
public function via($notifiable)
{
    return ['mail', 'database'];
}

public function toMail($notifiable)
{
    return (new MailMessage)->subject('Enviado!')->line('...');
}

O notify() envia notificações por múltiplos canais: mail, database, sms, slack. Cada canal tem o seu método toX().