Cheatsheet Laravel
Framework PHP para desenvolvimento web
Laravel
Colecciones
Crear colección
$colección = collect([1, 2, 3, 4]); $colección = new Collection([1, 2, 3]); // Métodos encadenables: collect([1, 2, 3])->map(...)->filter(...)->values();
El collect() transforma un array en una Collection con decenas de métodos útiles. Todos son encadenables y no mutan el original.
Where en colección
$ativos = $productos->where('activo', true);
$caros = $productos->where('precio', '>', 100);
$primeiros = $productos->whereIn('id', [1, 2, 3]);El where() filtra por valor de campo (comparación loose). Soporta operadores y variantes: whereIn, whereBetween, whereNotNull.
Sum, Avg, Max, Min
$total = $productos->sum('precio');
$media = $productos->avg('precio');
$max = $productos->max('precio');
$min = $productos->min('precio');
$conteo = $productos->count();Métodos de agregación sobre un campo: sum(), avg(), max(), min(), count(). Funcionan en colecciones de models o arrays.
Reduce y Each
$total = collect([1, 2, 3])->reduce(
fn($carry, $n) => $carry + $n, 0
); // 6
$productos->each(function ($p) {
$p->update(['visto' => true]);
});El reduce() acumula un valor al iterar. El each() ejecuta una acción en cada elemento (sin transformar). Retorna la colección original.
Map (transformar)
$doble = collect([1, 2, 3])
->map(fn($n) => $n * 2);
// [2, 4, 6]
$nombres = $productos->map(fn($p) => $p->nombre);El map() aplica una función a cada elemento y retorna una nueva colección con los resultados transformados. No altera el original.
GroupBy (agrupar)
$porCategoria = $productos->groupBy('categoria');
// ['electrónica' => [...], 'accesorios' => [...]]
$porAno = $pedidos->groupBy(fn($p) => $p->created_at->year);El groupBy() agrupa elementos por el valor de un campo o callback. Retorna una colección de colecciones indexada por la clave.
Contains y Search
$tem = $productos->contains('nombre', 'Portátil');
$tem = collect([1, 2, 3])->contains(2);
$índice = collect(['a', 'b'])->search('b'); // 1El contains() verifica si existe un elemento con el valor. El search() retorna el índice de la primera ocurrencia.
Filter (filtrar)
$pares = collect([1, 2, 3, 4])
->filter(fn($n) => $n % 2 === 0);
// [2, 4]
$ativos = $productos->filter(fn($p) => $p->activo);El filter() mantiene solo los elementos que pasan el test. Sin callback, elimina valores falsy. Usa values() para reindexar.
SortBy (ordenar)
$ordenado = $productos->sortBy('precio');
$desc = $productos->sortByDesc('precio');
$multi = $productos->sortBy([['nombre', 'asc'], ['precio', 'desc']]);El sortBy() ordena por un campo (ascendente). El sortByDesc() es descendente. Acepta un array para ordenación múltiple.
First, Last y Find
$primero = $colección->first(); $último = $colección->last(); $mayor = $colección->first(fn($n) => $n > 10); $padrao = $colección->first(fn($n) => $n > 100, 0);
El first() y last() obtienen los extremos. Con callback, retorna el primero que satisface la condición. El segundo argumento es el valor por defecto.
Pluck (extraer columna)
$nombres = $productos->pluck('nombre');
// ['Portátil', 'Ratón', 'Teclado']
$pares = $productos->pluck('nombre', 'id');
// [1 => 'Portátil', 2 => 'Ratón']El pluck() extrae un campo de todos los elementos. El segundo argumento define la clave del array resultante.
Unique y Values
$unicos = collect([1, 2, 2, 3])->unique();
// [1, 2, 3]
$cats = $productos->unique('categoria');
$reindex = $cats->values();El unique() elimina duplicados (por valor o campo). El values() reindexa las claves numéricas tras filtrar.
Chunk y 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]]
El chunk() divide en bloques de N elementos. El split() divide en N grupos de tamaño aproximadamente igual.
Instalación y configuración
Crear un nuevo proyecto
composer create-project laravel/laravel mi-proyecto cd mi-proyecto php artisan serve
Crea un nuevo proyecto Laravel con todas las dependencias. El composer descarga el framework y el artisan serve inicia el servidor en http://127.0.0.1:8000.
Variables de entorno
# .env
APP_NAME=MeuSite
APP_ENV=local
APP_DEBUG=true
APP_URL=http://localhost
# Acceso en el código:
$nombre = env('APP_NAME');
$debug = env('APP_DEBUG', false);Los valores sensibles van en el .env (nunca en el repositorio). Accede con env(), pasando un valor por defecto como segundo argumento. En producción usa APP_DEBUG=false.
Instalar Breeze (auth)
composer require laravel/breeze --dev php artisan breeze:install blade npm install && npm run dev
El Breeze instala login, registro y recuperación de contraseña listos. Elige el stack (blade, react o vue) y ejecuta npm install para los assets.
Laravel Installer
composer global require laravel/installer laravel new mi-proyecto
El laravel/installer es una alternativa más rápida a create-project. Después de instalarlo globalmente, usa laravel new para crear proyectos.
Estructura de carpetas
app/ → Models, Controllers, Services bootstrap/ → Inicialización del framework config/ → Archivos de configuración database/ → Migrations, seeders, factories public/ → index.php, CSS, JS, imágenes resources/ → Vistas Blade, assets routes/ → web.php, api.php, console.php storage/ → Logs, cache, uploads vendor/ → Dependencias Composer
La estructura por defecto de Laravel separa responsabilidades. El app/ contiene la lógica, routes/ las rutas y resources/views/ las plantillas Blade.
Configuración por archivos
// config/app.php
'name' => env('APP_NAME', 'Laravel'),
'timezone' => 'Europe/Lisbon',
'locale' => 'pt',
// Acceso:
$nombre = config('app.name');
config(['app.locale' => 'en']);Cada archivo en config/ agrupa definiciones por área. Accede con config('archivo.clave') y define en runtime con config(['clave' => valor]).
Generar APP_KEY
php artisan key:generate
Genera la APP_KEY en el archivo .env. Es obligatoria para el cifrado de sesiones, cookies y datos sensibles. Sin ella la aplicación no funciona.
Modo de mantenimiento
php artisan down php artisan down --secret=abc123 php artisan up
El down pone el sitio en mantenimiento (devuelve 503). Con --secret puedes acceder vía una URL secreta. El up quita el mantenimiento.
Archivo de rutas web.php
// routes/web.php
use Illuminate\Support\Facades\Route;
Route::get('/', function () {
return view('welcome');
});El archivo routes/web.php define las rutas web con sesión y CSRF. Para APIs usa routes/api.php (sin sesión, prefijo /api).
Configurar la base de datos
# .env DB_CONNECTION=mysql DB_HOST=127.0.0.1 DB_PORT=3306 DB_DATABASE=mi_banco DB_USERNAME=root DB_PASSWORD=
Edita el archivo .env en la raíz del proyecto con las credenciales de la base de datos. Laravel soporta mysql, pgsql, sqlite y sqlsrv.
Ver versión e info
php artisan --version php artisan about
El --version muestra la versión del framework. El about (Laravel 9+) exhibe un resumen completo: versión PHP, drivers, entorno y configuración.
Aliases y Facades
// config/app.php → aliases
'Cache' => Illuminate\Support\Facades\Cache::class,
'DB' => Illuminate\Support\Facades\DB::class,
// Uso sin import:
Cache::get('clave');
DB::table('users')->get();Los Facades dan acceso estático a servicios del container. Los aliases en config/app.php permiten usarlos sin use (ej.: Cache::, DB::).
Artesano
Listar comandos
php artisan list php artisan list --raw php artisan help make:model
El list muestra todos los comandos Artisan disponibles. El help explica la sintaxis y opciones de un comando específico.
Tinker (REPL)
php artisan tinker >>> App\Models\Producto::count() >>> $p = App\Models\Producto::find(1) >>> $p->nombre
El tinker es un terminal interactivo (REPL) para probar código y consultar models directamente. Ideal para debug rápido y exploración de la base de datos.
Crear Seeder y Factory
php artisan make:seeder ProdutoSeeder php artisan make:factory ProdutoFactory --model=Producto
El seeder inserta datos fijos en la BD. La factory genera datos falsos realistas para pruebas. Ambos van en database/.
Crear Job y Command
php artisan make:job EnviarEmail php artisan make:command LimparLogs
El job es una tarea asíncrona para colas. El command es un comando Artisan personalizado. Ambos van en app/.
Crear Model
php artisan make:model Producto php artisan make:model Producto -mcrf # -m migration, -c controller, -r resource, -f factory
Crea el Model en app/Models/. Las flags combinan la creación de una migration, resource controller y factory en un solo comando.
Listar rutas
php artisan route:list php artisan route:list --name=productos php artisan route:list --method=GET
Muestra todas las rutas registradas con método HTTP, URI, nombre y middleware. Los filtros --name y --method ayudan a encontrar rutas específicas.
Crear FormRequest
php artisan make:request StoreProdutoRequest
Crea una clase de validación dedicada en app/Http/Requests/. Separa las reglas del controller, haciéndolo más limpio y reutilizable.
Agendar y ejecutar tareas
php artisan schedule:run # ejecuta tareas agendadas php artisan queue:work # procesa la cola php artisan queue:listen # procesa con reload php artisan test # corre pruebas PHPUnit
El schedule:run ejecuta tareas cron. El queue:work procesa jobs en cola. El test corre la suite de pruebas PHPUnit.
Crear Controller
php artisan make:controller ProdutoController php artisan make:controller ProdutoController --resource php artisan make:controller Api/ProdutoController --api
Crea un controller en app/Http/Controllers/. El --resource genera los 7 métodos CRUD y el --api genera 5 (sin create/edit).
Limpiar cachés
php artisan config:clear php artisan cache:clear php artisan view:clear php artisan route:clear php artisan optimize:clear # todos de una vez
Limpia las cachés de configuración, datos, vistas compiladas y rutas. El optimize:clear limpia todo en un solo comando. Esencial tras cambiar configuraciones.
Crear Middleware
php artisan make:middleware VerificaAdmin
Crea un middleware en app/Http/Middleware/. Regístralo en app/Http/Kernel.php o aplícalo directamente en la ruta.
Crear Migration
php artisan make:migration create_productos_table php artisan make:migration add_sku_to_productos_table --table=productos
Crea una migration con timestamp en database/migrations/. Si el nombre contiene create_x_table, Laravel genera el esqueleto con Schema::create.
Optimizar para producción
php artisan config:cache php artisan route:cache php artisan view:cache php artisan optimize
Genera cachés de configuración, rutas y vistas para mejor performance en producción. El optimize hace config + route cache de una vez.
Crear Policy y Event
php artisan make:policy ProdutoPolicy --model=Producto php artisan make:event PedidoCriado php artisan make:listener EnviarNotificacao --event=PedidoCriado
La policy define reglas de autorización por model. El event/listener implementan el patrón observador para reaccionar a acciones.
Rutas
Ruta GET básica
Route::get('/productos', [ProdutoController::class, 'index']);
Route::get('/hola', function () {
return 'Hola Mundo';
});Define una ruta GET que llama un método del controller o una closure. El primer argumento es la URI y el segundo el handler.
Ruta con nombre
Route::get('/productos', [ProdutoController::class, 'index'])
->name('productos.index');
// Generar URL:
$url = route('productos.index');
$link = route('productos.show', $producto);El name() da un nombre a la ruta, permitiendo generar URLs con route(). Si la URI cambia, los links siguen válidos.
Restringir parámetros
Route::get('/productos/{id}', ...)
->whereNumber('id');
Route::get('/users/{nombre}', ...)
->where('nombre', '[A-Za-z]+');
Route::get('/files/{path}', ...)
->where('path', '.*');El where() aplica regex a los parámetros. Atajos: whereNumber, whereAlpha, whereAlphaNumeric, whereUuid.
Route model binding
Route::get('/productos/{producto}', function (Producto $producto) {
return $producto->nombre;
});
// /productos/1 → Producto::find(1) automáticoEl model binding inyecta automáticamente la instancia del Model a partir del ID en la URL. Si no existe, retorna 404 automáticamente.
Rutas POST, PUT, DELETE
Route::post('/productos', [ProdutoController::class, 'store']);
Route::put('/productos/{id}', [ProdutoController::class, 'update']);
Route::delete('/productos/{id}', [ProdutoController::class, 'destroy']);
Route::patch('/productos/{id}', [ProdutoController::class, 'patch']);Cada verbo HTTP tiene su método: post, put, patch, delete. El PUT reemplaza todo, el PATCH actualiza parcialmente.
Route::resource
Route::resource('productos', ProdutoController::class);
// Genera 7 rutas:
// GET /productos → index
// GET /productos/create → create
// POST /productos → store
// GET /productos/{id} → show
// GET /productos/{id}/edit → edit
// PUT /productos/{id} → update
// DELETE /productos/{id} → destroyEl resource registra las 7 rutas RESTful de una vez. Usa apiResource para APIs (excluye create y edit).
Redireccionamiento
Route::redirect('/antiguo', '/nuevo');
Route::redirect('/antiguo', '/nuevo', 301);
Route::permanentRedirect('/viejo', '/nuevo');El redirect() redirige una URI a otra (302 por defecto). El permanentRedirect() usa 301 (permanente, bueno para SEO).
Rate limiting
Route::middleware('throttle:60,1')->group(function () {
Route::get('/api/datos', ...);
});
// 60 peticiones por minutoEl throttle limita peticiones por usuario. El formato es throttle:max,minutos. Esencial para proteger APIs de abuso.
Ruta con parámetros
Route::get('/productos/{id}', function ($id) {
return "Producto: " . $id;
});
Route::get('/users/{user}/posts/{post}', function ($user, $post) {
return [$user, $post];
});Los parámetros entre llaves se pasan a la closure o método. Puedes tener múltiples parámetros en la misma URI.
Grupo con middleware
Route::middleware('auth')->group(function () {
Route::get('/painel', [PainelController::class, 'index']);
Route::get('/perfil', [PerfilController::class, 'show']);
});El middleware() aplica protección a todas las rutas del grupo. En este caso, solo usuarios autenticados (auth) acceden.
Ruta que retorna una view
Route::view('/sobre', 'pages.sobre');
Route::view('/faq', 'pages.faq', ['título' => 'FAQ']);El Route::view() retorna una view sin necesitar un controller. El tercer argumento opcional pasa datos a la view.
Parámetro opcional
Route::get('/users/{id?}', function ($id = null) {
return $id ?? 'Todos os users';
});El ? hace el parámetro opcional. La variable debe tener un valor por defecto (= null) para funcionar cuando el parámetro no se proporciona.
Grupo con prefijo
Route::prefix('admin')->name('admin.')->group(function () {
Route::get('/users', [AdminController::class, 'users'])
->name('users'); // admin.users
});
// URL: /admin/usersEl prefix() añade un segmento a todas las URIs del grupo. El name() prefija los nombres de las rutas (ej.: admin.users).
Ruta fallback (404)
Route::fallback(function () {
return view('errores.404');
});El fallback se ejecuta cuando ninguna ruta coincide con la petición. Debe ser la última ruta registrada en el archivo.
Controladores y Middleware
Controller básico
namespace App\Http\Controllers;
class ProdutoController extends Controller
{
public function index()
{
$productos = Producto::all();
return view('productos.index', compact('productos'));
}
}Un controller agrupa la lógica HTTP en una clase. El método retorna una view, JSON o redirect. Vive en app/Http/Controllers/.
Métodos del Resource
index() → GET /productos (listar)
create() → GET /productos/create (formulario)
store() → POST /productos (guardar)
show() → GET /productos/{id} (detalle)
edit() → GET /productos/{id}/edit (form edición)
update() → PUT /productos/{id} (actualizar)
destroy() → DELETE /productos/{id} (eliminar)Los 7 métodos de un resource controller siguen la convención REST. Cada uno mapea a un verbo HTTP y una URI específica.
Varios middlewares
Route::get('/admin/relatorios', ...)
->middleware(['auth', 'admin', 'verificado']);Aplica múltiples middlewares en cadena con un array. Se ejecutan en el orden indicado: primero auth, luego admin, etc.
Inyección del Request
public function store(Request $request)
{
$nombre = $request->input('nombre');
$email = $request->email;
$todos = $request->all();
$so = $request->only(['nombre', 'email']);
}El Request se inyecta automáticamente por el container. Accede a los datos con input(), la propiedad mágica o all()/only().
Redirect con flash
return redirect()->route('productos.index')
->with('éxito', 'Producto creado!');
return back()->withInput()
->withErrors(['nombre' => 'Obligatorio']);El redirect() con with() guarda un mensaje flash en la sesión. El back() vuelve al formulario con los datos y errores.
Dependency Injection
public function show(Producto $producto, LoggerInterface $log)
{
$log->info("Visto: {$producto->id}");
return view('productos.show', compact('producto'));
}El container resuelve automáticamente las dependencias type-hinted. El model binding inyecta el model y el LoggerInterface se resuelve vía el container.
Retornar JSON
return response()->json([
'éxito' => true,
'datos' => $productos,
], 200);
return response()->json(['error' => 'No encontrado'], 404);El response()->json() convierte arrays en una respuesta JSON con el 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);
}El middleware intercepta la petición antes del controller. Si la condición falla, llama a abort(). Si pasa, llama a $next($request) para continuar.
Respuestas HTTP
return response('OK', 200)
->header('X-Custom', 'valor')
->cookie('token', $valor, 60);
return response()->download($path);
return response()->file($path);El response() permite definir status, headers y cookies. El download() fuerza la descarga de un archivo y el file() lo muestra inline.
Controller Invokable
class MostrarPerfil
{
public function __invoke()
{
return view('perfil');
}
}
Route::get('/perfil', MostrarPerfil::class);Un controller invokable tiene un único método __invoke(). No necesitas indicar el método en la ruta. Ideal para acciones simples.
Registrar middleware
// app/Http/Kernel.php
protected $middlewareAliases = [
'admin' => \App\Http\Middleware\VerificaAdmin::class,
];
// Uso en la ruta:
Route::get('/admin', ...)->middleware('admin');Registra el alias en $middlewareAliases en el Kernel.php. Luego aplícalo con middleware('alias') en rutas o grupos.
Abort y errores HTTP
abort(404); abort(403, 'Sem permiso'); abort_if(!$producto, 404); abort_unless(auth()->check(), 401);
El abort() lanza una excepción HTTP con el código indicado. El abort_if y abort_unless son atajos condicionales.
ORM elocuente
Model básico
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Producto extends Model
{
protected $fillable = ['nombre', 'precio', 'descripcion'];
}El $fillable define los campos rellenables en masa (create/update). Sin él, Laravel bloquea la asignación en masa por seguridad.
Crear registro
$producto = Producto::create([
'nombre' => 'Portátil',
'precio' => 999.99,
]);
// Alternativa:
$producto = new Producto();
$producto->nombre = 'Portátil';
$producto->save();El create() crea y guarda de una vez (requiere $fillable). La alternativa instancia, define campos y llama a save().
Relación HasMany
// En el model Producto:
public function comentarios()
{
return $this->hasMany(Comentario::class);
}
// Uso:
$producto->comentarios;
$producto->comentarios()->create(['texto' => 'Bom!']);El hasMany() define que un producto tiene muchos comentarios. La FK producto_id vive en la tabla comentarios. Accede como propiedad.
Eager Loading (evitar N+1)
// N+1 (malo):
$pedidos = Pedido::all();
$pedidos[0]->cliente->nombre; // query por cada pedido
// Eager loading (bueno):
$pedidos = Pedido::with('cliente', 'ítems')->get();El with() carga las relaciones en una sola consulta extra, evitando el problema N+1. Esencial para performance con listas de registros relacionados.
Seleccionar columnas
Producto::select('nombre', 'precio')->get();
Producto::distinct()->pluck('categoria');
Producto::selectRaw('categoria, COUNT(*) as total')
->groupBy('categoria')->get();El select() trae solo las columnas indicadas. El pluck() extrae una columna como array. El selectRaw() permite expresiones SQL.
Buscar registros
$productos = Producto::all(); $producto = Producto::find(1); $producto = Producto::findOrFail(1); $primero = Producto::first(); $último = Producto::latest()->first();
El all() trae todo, find() búsqueda por ID. El findOrFail() lanza 404 si no existe. El latest() ordena por created_at desc.
Actualizar registro
$producto = Producto::find(1);
$producto->update(['precio' => 899.99]);
// O en masa:
Producto::where('categoria', 'electrónica')
->update(['descuento' => 10]);El update() actualiza los campos indicados. Puede usarse en una instancia o directamente en el query builder para actualizaciones en masa.
Relación BelongsTo
// En el model Comentario:
public function producto()
{
return $this->belongsTo(Producto::class);
}
// Uso:
$comentario->producto->nombre;El belongsTo() es el inverso de hasMany. Indica que el comentario pertenece a un producto. La FK vive en el model hijo.
Ordenar y limitar
Producto::orderBy('precio', 'desc')
->limit(10)
->get();
Producto::inRandomOrder()->first();
Producto::skip(20)->take(10)->get(); // offset 20El orderBy() ordena por campo y dirección. El limit()/take() limita resultados y skip()/offset() salta registros.
Soft Deletes
use Illuminate\Database\Eloquent\SoftDeletes;
class Producto extends Model
{
use SoftDeletes;
}
// Migration:
$table->softDeletes(); // columna deleted_atEl trait SoftDeletes marca registros como eliminados sin quitarlos. Usa trashed() para verificar, restore() para recuperar y forceDelete() para borrar definitivamente.
Where con condiciones
Producto::where('precio', '>', 100)
->where('activo', true)
->get();
Producto::where('nombre', 'like', '%ratón%')->get();
Producto::whereIn('id', [1, 2, 3])->get();Encadena condiciones con where(). Soporta operadores (>, <, like) y variantes como whereIn, whereBetween, whereNull.
Eliminar registro
$producto = Producto::find(1);
$producto->delete();
// En masa:
Producto::where('activo', false)->delete();
// Con SoftDeletes:
$producto->restore();
Producto::withTrashed()->get();El delete() elimina el registro. Con SoftDeletes, solo marca deleted_at. El restore() lo recupera y withTrashed() incluye los eliminados.
Relación ManyToMany
// En el model Producto:
public function categorias()
{
return $this->belongsToMany(Categoria::class);
}
// Uso:
$producto->categorias()->attach($catId);
$producto->categorias()->detach($catId);
$producto->categorias()->sync([1, 2, 3]);El belongsToMany() requiere una tabla pivote (categoria_producto). El attach/detach añaden/quitan y el sync reemplaza todo.
Paginación
$productos = Producto::paginate(15);
// En la view Blade:
{{ $productos->links() }}
// API:
return response()->json($productos);El paginate() divide resultados en páginas y genera links automáticamente. El simplePaginate() muestra solo "anterior/siguiente".
Where con OR
Producto::where('precio', '>', 100)
->orWhere('highlight', true)
->get();
Producto::where(function ($q) {
$q->where('precio', '>', 100)
->orWhere('highlight', true);
})->where('activo', true)->get();El orWhere() añade condiciones alternativas. Para agrupar lógica, usa una closure en el where() para evitar precedencia incorrecta.
FirstOrCreate / UpdateOrCreate
$producto = Producto::firstOrCreate(
['nombre' => 'Ratón'],
['precio' => 25]
);
$producto = Producto::updateOrCreate(
['nombre' => 'Ratón'],
['precio' => 30]
);El firstOrCreate() búsqueda o crea si no existe. El updateOrCreate() búsqueda y actualiza, o crea con todos los valores.
Relación HasOne
// En el model User:
public function perfil()
{
return $this->hasOne(Perfil::class);
}
// Uso:
$user->perfil->bio;
$user->perfil()->create(['bio' => 'Hola']);El hasOne() indica una relación uno-a-uno. Cada usuario tiene un único perfil. La FK vive en la tabla del model relacionado.
Agregaciones
$total = Producto::count();
$caros = Producto::where('precio', '>', 100)->count();
$max = Producto::max('precio');
$media = Producto::avg('precio');
$suma = Producto::sum('precio');
$existe = Producto::where('sku', 'X1')->exists();Métodos de agregación: count(), max(), min(), avg(), sum(). El exists() verifica existencia sin traer datos.
Migraciones y Sembradoras
Estructura de una migration
Schema::create('productos', function (Blueprint $table) {
$table->id();
$table->string('nombre');
$table->decimal('precio', 8, 2);
$table->text('descripcion')->nullable();
$table->timestamps();
});El Schema::create() crea la tabla. El id() es auto-incremento y timestamps() añade created_at y updated_at.
Ejecutar migraciones
php artisan migrate php artisan migrate:rollback php artisan migrate:rollback --step=2 php artisan migrate:fresh php artisan migrate:fresh --seed
El migrate ejecuta las pendientes. El rollback deshace el último lote. El fresh borra todo y recrea. Con --seed ejecuta los seeders después.
Seeder con Factory
// Factory:
public function definition()
{
return [
'nombre' => fake()->word(),
'precio' => fake()->randomFloat(2, 5, 500),
];
}
// Seeder:
Producto::factory(50)->create();La factory genera datos falsos realistas con fake() (Faker). El create() guarda en la BD; make() solo instancia sin guardar.
Factory con relaciones
// Producto con 3 comentarios:
Producto::factory()
->has(Comentario::factory()->count(3))
->create();
// Comentario con producto:
Comentario::factory()
->for(Producto::factory())
->create();El has() crea registros relacionados (uno-a-muchos). El for() define el "padre" de la relación. Simplifica la creación de datos interrelacionados.
Tipos de columnas
$table->string('nombre'); // VARCHAR(255)
$table->text('descripcion'); // TEXT
$table->integer('cantidad'); // INT
$table->unsignedBigInteger('user_id');
$table->boolean('activo'); // TINYINT(1)
$table->decimal('precio', 8, 2); // DECIMAL
$table->date('data_nascimento');
$table->timestamp('publicado_em');
$table->json('metadados');
$table->enum('estado', ['activo', 'inactivo']);El Blueprint ofrece métodos para cada tipo SQL. El string() acepta un tamaño opcional: string('nombre', 100).
Alterar tabla existente
Schema::table('productos', function (Blueprint $table) {
$table->string('sku')->after('nombre');
$table->dropColumn('campo_antiguo');
});Usa Schema::table() (no create) para modificar tablas existentes. El dropColumn() elimina columnas.
Ejecutar seeders
php artisan db:seed php artisan db:seed --class=ProdutoSeeder php artisan migrate:fresh --seed
El db:seed ejecuta el DatabaseSeeder. Con --class ejecuta uno específico. El fresh --seed recrea todo con datos.
Modificadores de columna
$table->string('estado')->default('activo');
$table->text('nota')->nullable();
$table->string('slug')->unique();
$table->integer('ordem')->after('nombre');
$table->string('código')->index();Modificadores: default() valor por defecto, nullable() acepta NULL, unique() valor único, index() crea índice, after() posición.
Renombrar y verificar
Schema::rename('productos', 'artigos');
Schema::hasTable('productos');
Schema::hasColumn('productos', 'sku');El rename() cambia el nombre de la tabla. El hasTable() y hasColumn() verifican existencia antes de operaciones condicionales.
Llamar otros seeders
// DatabaseSeeder.php
public function run()
{
$this->call([
UserSeeder::class,
ProdutoSeeder::class,
CategoriaSeeder::class,
]);
}El call() en el DatabaseSeeder ejecuta seeders en orden. Ideal para organizar la seed por dominio y controlar dependencias.
Clave foránea
$table->foreignId('user_id')
->constrained()
->cascadeOnDelete();
$table->foreignId('categoria_id')
->constrained('categorias')
->restrictOnDelete();El foreignId() crea la columna y el constrained() define la FK. Opciones: cascadeOnDelete() borra en cascada, restrictOnDelete() lo impide.
Seeder con datos fijos
public function run()
{
Producto::create([
'nombre' => 'Teclado',
'precio' => 49.99,
]);
Producto::insert([
['nombre' => 'Ratón', 'precio' => 25],
['nombre' => 'Monitor', 'precio' => 299],
]);
}El create() usa Eloquent (dispara eventos). El insert() es más rápido para múltiples registros pero ignora eventos y timestamps.
Estados de Factory
// Definir estado:
public function inactivo()
{
return $this->state(['activo' => false]);
}
// Uso:
Producto::factory()->inactivo()->create();
Producto::factory()->count(5)->inactivo()->create();Los states modifican la factory para escenarios específicos. Combina con count(), has() y relaciones para datos complejos.
Cuchilla
Mostrar variable (escapado)
{{ $nombre }}
{{ $producto->precio }}
{{ strtoupper($título) }}Las llaves dobles {{ }} muestran el valor escapando HTML automáticamente (previene XSS). Acepta cualquier expresión PHP.
Forelse (con fallback)
@forelse($productos as $producto)
<p>{{ $producto->nombre }}</p>
@empty
<p>Nenhum producto encontrado.</p>
@endforelseEl @forelse combina loop con fallback. Si la colección está vacía, muestra el bloque @empty en vez de no renderizar nada.
Componentes Blade
<x-alerta tipo="éxito" />
<x-card título="Producto">
<p>Contenido do slot</p>
</x-card>
{{-- resources/views/components/alerta.blade.php --}}
<div class="alerta {{ $tipo }}">{{ $slot }}</div>Los componentes viven en resources/views/components/. Las props son atributos y el contenido va en el $slot. Más limpios que los partials.
Valor anterior (old)
<input name="email" value="{{ old('email', '') }}">
<input name="nombre" value="{{ old('nombre', $producto->nombre ?? '') }}">
@error('email')
<span class="error">{{ $message }}</span>
@enderrorEl old() repone el valor enviado tras un error de validación. El @error muestra el mensaje de error del campo indicado.
HTML sin escapar
{!! $conteudoHtml !!}
{!! $artigo->corpo !!}Las llaves con exclamación {!! !!} renderizan HTML puro sin escapar. Usa solo con contenido de confianza (nunca input del usuario).
Variable $loop
@foreach($ítems as $item)
{{ $loop->iteration }} — {{ $item }}
@if($loop->first) Primero! @endif
@if($loop->last) Último! @endif
@if($loop->remaining) Faltam: {{ $loop->remaining }} @endif
@endforeachLa variable $loop da información de la iteración: iteration (numero), index (base 0), first, last, remaining, count.
Token CSRF
<form method="POST" action="/productos">
@csrf
<input type="text" name="nombre">
<button type="submit">Crear</button>
</form>El @csrf genera un token oculto obligatorio en formularios POST/PUT/DELETE. Sin él, Laravel rechaza con error 419.
Directivas de condición
@isset($producto)
Producto definido
@endisset
@empty($productos)
Lista vacia
@endempty
@unless($escondido)
Visible
@endunlessEl @isset verifica si existe, @empty si está vacío y @unless es el inverso de @if (se ejecuta si es falso).
Condicional If
@if($user->isAdmin())
<p>Admin</p>
@elseif($user->isEditor())
<p>Editor</p>
@else
<p>Usuario</p>
@endifLas directivas @if, @elseif, @else y @endif funcionan como PHP. Aceptan cualquier expresión booleana.
Heredar layout
{{-- resources/views/productos/index.blade.php --}}
@extends('layouts.app')
@section('content')
<h1>Productos</h1>
@endsection
@section('title', 'Lista de Productos')El @extends hereda del layout y el @section rellena las secciones definidas con @yield. La forma corta pasa el valor directo.
Métodos PUT/DELETE en forms
<form method="POST" action="/productos/{{ $producto->id }}">
@csrf
@method('PUT')
<input type="text" name="nombre" value="{{ $producto->nombre }}">
</form>HTML solo soporta GET/POST. El @method('PUT') añade un campo oculto que Laravel interpreta como el verbo indicado.
Stacks y Push
{{-- En el layout: --}}
@stack('scripts')
{{-- En la view: --}}
@push('scripts')
<script src="/js/productos.js"></script>
@endpushEl @stack define un punto de acumulación y el @push añade contenido. Ideal para scripts y CSS específicos de cada página.
Loop Foreach
@foreach($productos as $producto)
<p>{{ $producto->nombre }} — {{ $producto->precio }}€</p>
@endforeachEl @foreach recorre colecciones o arrays. Accede a cada elemento como variable. Termina con @endforeach.
Incluir partial
@include('partials.header')
@include('partials.card', ['título' => 'Producto X'])
{{-- Con condición: --}}
@includeWhen($user, 'partials.user-bar')El @include inserta otra view. El segundo argumento pasa datos. El @includeWhen solo incluye si la condición es verdadera.
Auth y Guest
@auth
Hola, {{ auth()->user()->name }}
@endauth
@guest
<a href="/login">Faz login</a>
@endguestEl @auth muestra contenido para usuarios autenticados y el @guest para visitantes. Una alternativa a @if(auth()->check()).
Validación
Validación básica
$datos = $request->validate([
'nombre' => 'required|string|max:255',
'email' => 'required|email|unique:users',
'edad' => 'required|integer|min:18',
]);El validate() verifica los datos y redirige con errores automáticamente si falla. Si pasa, retorna los datos validados.
Mensajes personalizados
$request->validate($reglas, [
'nombre.required' => 'O nombre es obligatorio.',
'email.unique' => 'Este email ya existe.',
'edad.min' => 'Deve ter pelo menos :min anios.',
]);El segundo argumento de validate() define mensajes por campo.regla. El placeholder :min se reemplaza por el valor de la regla.
Validación manual
use Illuminate\Support\Facades\Validator;
$validator = Validator::make($datos, $reglas);
if ($validator->fails()) {
return redirect()->back()
->withErrors($validator)
->withInput();
}El Validator::make() da control total sobre el flujo. Verifica con fails() y redirige manualmente con withErrors() y withInput().
Reglas más comunes
required → obligatorio email → formato de email min:8 → mínimo 8 caracteres max:255 → máximo 255 numeric → apenas numeros integer → numero entero boolean → true/false url → URL válida date → data válida unique:tabla → valor único na tabla exists:tabla → valor debe existir
Las reglas se encadenan con |. El unique y exists verifican contra la base de datos. Aceptan parámetros: unique:users,email.
FormRequest (clase dedicada)
class StoreProdutoRequest extends FormRequest
{
public function rules()
{
return [
'nombre' => 'required|string|max:255',
'precio' => 'required|numeric|min:0',
];
}
public function authorize()
{
return true;
}
}El FormRequest aísla la validación en una clase. El authorize() controla el permiso. Inyéctalo en el controller: store(StoreProdutoRequest $request).
Mostrar errores en la view
@error('email')
<span class="text-danger">{{ $message }}</span>
@enderror
@if($errors->any())
<ul>
@foreach($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
@endifEl @error muestra el error de un campo. El $errors->any() verifica si hay errores y $errors->all() los lista todos.
Confirmar password
$request->validate([
'password' => 'required|min:8|confirmed',
]);
// En el form: campo password_confirmationLa regla confirmed exige un campo password_confirmation con el mismo valor. Usada en registro y cambio de password.
Validar arrays
$request->validate([
'ítems' => 'required|array|min:1',
'ítems.*.nombre' => 'required|string',
'ítems.*.cantidad' => 'required|integer|min:1',
]);El * valida cada elemento de un array. El array garantiza que es un array y el min:1 que tiene al menos un elemento.
Reglas de fecha
'data_inicio' => 'required|date', 'data_fin' => 'required|date|after:data_inicio', 'nascimento' => 'date|before:today', 'agendamento' => 'date|after_or_equal:now',
Reglas de fecha: date valida el formato, after: y before: comparan con otra fecha o campo. Acepta today, now o nombre de campo.
Campo opcional (nullable)
'telefono' => 'nullable|string|max:20', 'website' => 'nullable|url', 'data_fin' => 'nullable|date|after:data_inicio',
El nullable permite valor vacío/null. Sin él, las otras reglas implican obligatoriedad. Ideal para campos opcionales con formato específico.
Reglas condicionales
use Illuminate\Validation\Rule;
'email' => [
'required',
Rule::unique('users')->ignore($user->id),
],
'tipo' => Rule::in(['fisica', 'juridica']),La clase Rule permite reglas complejas. El ignore() excluye el registro actual en el update. El Rule::in() valida contra una lista de valores.
Validar archivos
$request->validate([
'foto' => 'required|image|mimes:jpg,png|max:2048',
'documento' => 'file|mimes:pdf,doc|max:10240',
]);
// max en KB (2048 = 2MB)La regla image valida que es una imagen, mimes restringe tipos y max limita el tamaño en KB. El file garantiza que es un upload.
Autenticación y seguridad
Usuario actual
$user = auth()->user(); $nombre = auth()->user()->name; $id = auth()->id(); // Alternativa: use Illuminate\Support\Facades\Auth; $user = Auth::user();
El helper auth() retorna el usuario autenticado (o null). El auth()->id() es atajo para el ID. El Auth facade es equivalente.
Proteger ruta con auth
Route::get('/painel', ...)->middleware('auth');
Route::middleware('auth')->group(function () {
Route::get('/perfil', ...);
Route::get('/configuraciones', ...);
});El middleware auth redirige a login si no está autenticado. Aplícalo en rutas individuales o grupos enteros.
Usar Policy
// En el controller:
$this->authorize('update', $producto);
// Condicional:
if ($user->can('update', $producto)) { ... }
// En la ruta:
Route::put('/productos/{producto}', ...)
->middleware('can:update,producto');El authorize() verifica la policy y lanza 403 si se deniega. El can() retorna un boolean. El middleware can: protege la ruta directamente.
Encriptación
use Illuminate\Support\Facades\Crypt;
$encriptado = Crypt::encryptString('dado secreto');
$original = Crypt::decryptString($encriptado);
$encriptado = Crypt::encrypt(['clave' => 'valor']);El Crypt encripta/desencripta datos con la APP_KEY. A diferencia del hash, es reversible. Úsalo para datos sensibles que necesitas leer después.
Verificar autenticación
if (auth()->check()) {
// usuario autenticado
}
if (auth()->guest()) {
// no autenticado
}El check() retorna true si hay una sesión activa. El guest() es el inverso. Prefiere check() antes que comparar user() !== null.
Hash de password
use Illuminate\Support\Facades\Hash;
$hash = Hash::make($request->password);
if (Hash::check($input, $user->password)) {
// password correta
}El Hash::make() genera un hash seguro con bcrypt. El Hash::check() compara el input con el hash guardado. Nunca guardes passwords en texto plano.
Protección CSRF
// En formularios:
@csrf
// En AJAX (header):
headers: {
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content
}El token CSRF protege contra cross-site request forgery. Obligatorio en POST/PUT/DELETE. En AJAX, envíalo en el header X-CSRF-TOKEN.
Sanitizar input
// Blade escapa automáticamente:
{{ $input }} // seguro
// Limpiar input:
$nombre = strip_tags($request->nombre);
$limpo = htmlspecialchars($input, ENT_QUOTES);
// Nunca confiar en:
{!! $request->input('html') !!} // PERIGOSOBlade escapa con {{ }} por defecto. Para HTML del usuario, usa strip_tags() o librerías de purificación. Nunca uses {!! !!} con input.
Login manual
Auth::login($user);
Auth::login($user, remember: true);
// Por credenciales:
if (Auth::attempt(['email' => $email, 'password' => $pass])) {
return redirect()->intended('/painel');
}El login() autentica programáticamente. El attempt() verifica credenciales y crea la sesión. El intended() redirige a la página original.
Gate (autorización simple)
// AppServiceProvider:
Gate::define('admin', function ($user) {
return $user->role === 'admin';
});
// Uso:
if (Gate::allows('admin')) { ... }
if (Gate::denies('admin')) { ... }
Gate::authorize('admin');El Gate define capacidades simples basadas en el usuario. El allows() retorna un boolean y el authorize() lanza 403 si se deniega.
Remember me
Auth::attempt($credentials, $request->remember);
// Verificar:
if (Auth::viaRemember()) {
// login via cookie "lembrar-me"
}El segundo argumento de attempt() activa el "recordarme" con una cookie persistente. El viaRemember() verifica si el login fue por cookie.
Logout
Auth::logout();
$request->session()->invalidate();
$request->session()->regenerateToken();
return redirect('/');El logout() termina la sesión. El invalidate() limpia los datos y el regenerateToken() previene la fijación de sesión (CSRF).
Policy (autorización por model)
class ProdutoPolicy
{
public function update(User $user, Producto $producto)
{
return $user->id === $producto->user_id;
}
public function delete(User $user, Producto $producto)
{
return $user->id === $producto->user_id;
}
}La policy define reglas de autorización ligadas a un model. Cada método recibe el User y la instancia del model y retorna un boolean.
Rate limiting en el login
use Illuminate\Support\Facades\RateLimiter;
$key = 'login:' . $request->ip();
if (RateLimiter::tooManyAttempts($key, 5)) {
abort(429, 'Tentativas excedidas');
}
RateLimiter::hit($key, 60);El RateLimiter limita los intentos de login por IP. Tras 5 fallos en 60 segundos, bloquea. Esencial contra brute force.
Ayudantes
dd() y dump()
dd($variable); // dump e morre dump($variable); // dump e continua dd($request->all()); dd(Producto::find(1)->toArray());
El dd() muestra el contenido formateado y para la ejecución (esencial para debug). El dump() muestra pero continúa. Aceptan múltiples argumentos.
now() y Carbon
$ahora = now();
$mañana = now()->addDay();
$ayer = now()->subDay();
$formato = now()->format('d/m/Y H:i');
$diff = now()->diffInDays($data);El now() retorna una instancia Carbon con métodos de fecha: addDay(), subMonth(), format(), diffInDays(), isWeekend().
old()
<input name="nombre" value="{{ old('nombre') }}">
<input name="email" value="{{ old('email', $user->email) }}">El old() recupera el valor enviado tras un error de validación. El segundo argumento es el valor por defecto (ej.: valor actual del model).
view() y response()
return view('productos.index', ['productos' => $datos]);
return response('Texto', 200)->header('X-Custom', 'v');
return response()->json(['ok' => true]);
return response()->download($path);El view() renderiza un template Blade con datos. El response() crea respuestas HTTP con un status, headers y contenido personalizado.
env() y config()
$debug = env('APP_DEBUG', false);
$nombre = config('app.name');
// Definir en runtime:
config(['app.locale' => 'en']);El env() lee del .env (¡solo en config!). El config() lee de los archivos de configuración. En producción con cache, env() retorna null fuera de config.
redirect() y back()
return redirect('/productos');
return redirect()->route('productos.index');
return back();
return back()->with('msg', 'Hecho!');
return redirect()->away('https://externo.com');El redirect() redirige a una URL o ruta. El back() vuelve a la página anterior. El away() permite URLs externas.
str() — strings
str('Hola Mundo')->slug(); // hola-mundo
str('abc')->upper(); // ABC
str('texto largo')->limit(10); // texto lon...
str('nombre_completo')->camel(); // nomeCompleto
str('NomeCompleto')->snake(); // nombre_completoEl helper str() ofrece una fluent API para strings: slug(), upper(), lower(), limit(), camel(), snake(), plural().
bcrypt() y tap()
$hash = bcrypt('password123');
// tap: ejecuta un callback y retorna el objeto
$user = tap(User::find(1), function ($u) {
$u->nombre = 'Nuevo Nombre';
$u->save();
});El bcrypt() es atajo para Hash::make(). El tap() ejecuta un callback en un objeto y lo retorna, útil para operaciones inline.
route() y url()
$url = route('productos.show', $producto);
// http://site.test/productos/1
$url = url('/productos');
// http://site.test/productos
$url = url()->current();El route() genera una URL para rutas con nombre (pasando parámetros). El url() genera desde la raíz. El current() retorna la URL actual.
session()
// Guardar:
session(['clave' => 'valor']);
session()->put('clave', 'valor');
// Leer:
$valor = session('clave', 'padrao');
// Flash (solo próximo request):
session()->flash('msg', 'Éxito!');El session() lee y guarda valores. El flash() guarda datos solo para el próximo request (mensajes temporales).
collect() y data_get()
$colección = collect([1, 2, 3]); // Acceso nested seguro: $nombre = data_get($array, 'user.perfil.nombre', 'Anónimo'); $valor = data_get($obj, 'a.b.c', null);
El collect() crea colecciones. El data_get() accede a valores nested con notación de punto y un valor por defecto, sin riesgo de error en claves inexistentes.
asset()
<img src="{{ asset('img/logo.png') }}">
<link href="{{ asset('css/app.css') }}">
<script src="{{ asset('js/app.js') }}"></script>El asset() genera una URL para archivos en la carpeta public/. Respeta el ASSET_URL del .env (útil para CDN).
abort()
abort(404); abort(403, 'Sem permiso'); abort_if(!$producto, 404); abort_unless(auth()->check(), 401);
El abort() lanza una excepción HTTP. El abort_if() aborta si la condición es verdadera y el abort_unless() si es falsa.
app() y resolve()
$servico = app(MiClase::class);
$logger = app('log');
// Equivalente:
$servico = resolve(MiClase::class);El app() resuelve dependencias del Service Container. Sin argumentos retorna la instancia de la aplicación. El resolve() es un alias.
Consejos avanzados
Cache de consultas
$productos = Cache::remember('productos', 3600, function () {
return Producto::all();
});
// Cache permanente:
$datos = Cache::rememberForever('config-site', fn() => Setting::all());El Cache::remember() guarda el resultado durante N segundos. Si existe en cache, retorna sin ejecutar la closure. El rememberForever() no expira.
Eventos y Listeners
// Disparar:
event(new PedidoCriado($pedido));
// Listener:
class EnviarNotificacao
{
public function handle(PedidoCriado $event)
{
$pedido = $event->pedido;
// enviar notificación
}
}El event() dispara un evento. Los listeners registrados reaccionan a él. Desacopla la lógica: quien crea el pedido no necesita saber quién notifica.
Model Observers
class ProdutoObserver
{
public function created(Producto $producto)
{
Log::info("Creado: {$producto->id}");
}
public function deleted(Producto $producto)
{
Cache::forget('productos');
}
}El Observer reacciona a eventos del model: created, updated, deleted, saving. Regístralo en el provider con Producto::observe().
Transacciones de BD
use Illuminate\Support\Facades\DB;
DB::transaction(function () {
$pedido->save();
$item->save();
$stock->decrement('cantidad');
});
// Si algo falla, todo se revierteEl DB::transaction() ejecuta operaciones atómicamente. Si ocurre una excepción, hace rollback automático. Esencial para operaciones interdependientes.
Invalidar cache
Cache::forget('productos');
Cache::flush();
// Tags (Redis/Memcached):
Cache::tags(['productos'])->put('lista', $datos, 3600);
Cache::tags(['productos'])->flush();El forget() elimina una clave y el flush() limpia todo. Las tags permiten agrupar e invalidar caches relacionadas de una vez.
Service Container
// Bind en el provider: $this->app->bind(PaymentGateway::class, StripeGateway::class); // Singleton: $this->app->singleton(Logger::class, fn() => new FileLogger()); // Resolver: $gateway = app(PaymentGateway::class);
El container resuelve dependencias automáticamente. El bind() mapea interface→implementación. El singleton() crea una única instancia compartida.
Scheduler (tareas cron)
// routes/console.php o Kernel:
Schedule::command('emails:enviar')->daily();
Schedule::command('backup')->weekly();
Schedule::job(new GerarRelatorio())->hourly();
Schedule::call(fn() => Cache::flush())->dailyAt('03:00');El Scheduler agenda tareas sin cron externo. Métodos: daily(), hourly(), weekly(), everyMinute(), dailyAt().
Evitar N+1
// En desarrollo (AppServiceProvider):
Model::preventLazyLoading(!app()->isProduction());
// Correcto:
$pedidos = Pedido::with('cliente', 'ítems.producto')->get();
// Lazy por chunks:
Producto::lazy(200)->each(fn($p) => $p->procesar());El preventLazyLoading() lanza un error cuando detecta N+1 en desarrollo. Usa with() para eager load. El lazy() procesa en chunks sin cargar todo.
Colas (Jobs)
// Dispatch: EnviarEmail::dispatch($user); EnviarEmail::dispatch($user)->delay(now()->addMinutes(5)); // Procesar: php artisan queue:work php artisan queue:work --tries=3
El dispatch() envía el job a la cola. El queue:work procesa en background. El delay() lo agenda para más tarde. Ideal para emails y tareas pesadas.
Local Scopes
// En el model:
public function scopeAtivos($query)
{
return $query->where('activo', true);
}
public function scopeCaros($query, $mínimo)
{
return $query->where('precio', '>', $mínimo);
}
// Uso:
Producto::ativos()->caros(100)->get();Los scopes son filtros reutilizables en el model. Prefija con scope y úsalo sin el prefijo. Aceptan parámetros adicionales.
Logs
use Illuminate\Support\Facades\Log;
Log::info('Usuario entrou', ['id' => $user->id]);
Log::error('Fallo no pagamento', ['pedido' => $id]);
Log::warning('Stock baixo', ['producto' => $p->nombre]);
Log::debug('Query executada', ['sql' => $sql]);El Log escribe en storage/logs/laravel.log. Niveles: debug, info, warning, error, critical. El segundo argumento es contexto.
Subida de archivos
$path = $request->file('foto')->store('fotos', 'public');
$url = Storage::url($path);
// Validar:
$request->validate(['foto' => 'image|max:2048']);
// Download:
return Storage::download('ficheros/doc.pdf');El store() guarda el upload en el disco configurado. El Storage::url() genera la URL pública. Usa el disco public con php artisan storage:link.
Crear un 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 Bienvenido());
}
}El ShouldQueue marca el job para colas. El handle() contiene la lógica y recibe dependencias vía el container. El constructor recibe los datos.
Accessors y Mutators
use Illuminate\Database\Eloquent\Casts\Attribute;
protected function nomeCompleto(): Attribute
{
return Attribute::make(
get: fn() => $this->nombre . ' ' . $this->apellido,
set: fn($v) => ['nombre' => strtok($v, ' '), 'apellido' => strtok(' ')],
);
}Los Accessors transforman valores al leer y los Mutators al escribir. Accede como propiedad: $user->nombre_completo.
HTTP Client
use Illuminate\Support\Facades\Http;
$respuesta = Http::get('https://api.ejemplo.com/datos');
$datos = $respuesta->json();
$respuesta = Http::withToken($token)
->post('https://api.ejemplo.com/pedidos', ['item' => 1]);El cliente Http hace llamadas externas con una API fluida. Soporta get(), post(), headers, tokens, timeout y retry. Reemplaza a cURL.
Notificaciones
$user->notify(new PedidoEnviado($pedido));
// En la notificación:
public function via($notifiable)
{
return ['mail', 'database'];
}
public function toMail($notifiable)
{
return (new MailMessage)->subject('Enviado!')->line('...');
}El notify() envía notificaciones a través de múltiples canales: mail, database, sms, slack. Cada canal tiene su propio método toX().