Cheatsheet Laravel
Framework PHP para desenvolvimento web
Laravel
Coleções
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'); // 1O 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
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
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
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áticoO 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} → destroyO 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 minutoO 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/usersO 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
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
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 20O 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_atO 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
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
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>
@endforelseO @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>
@enderrorO 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
@endforeachA 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
@endunlessO @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>
@endifAs 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>
@endpushO @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>
@endforeachO @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>
@endguestO @auth mostra conteúdo para utilizadores autenticados e o @guest para visitantes. Alternativa ao @if(auth()->check()).
Validação
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>
@endifO @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_confirmationA 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
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') !!} // PERIGOSOO 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
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_completoO 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
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 é revertidoO 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().