DevTools

Cheatsheet Laravel

Framework PHP para desenvolvimento web

Volver a los lenguajes
Laravel
167 tarjetas encontradas
Categorías:
Versiones:

Colecciones


13 cards
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'); // 1

El 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


12 cards
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


14 cards
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


14 cards
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ático

El 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}   → destroy

El 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 minuto

El 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/users

El 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


12 cards
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


18 cards
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 20

El 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_at

El 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


13 cards
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


15 cards
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>
@endforelse

El @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>
@enderror

El 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
@endforeach

La 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
@endunless

El @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>
@endif

Las 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>
@endpush

El @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>
@endforeach

El @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>
@endguest

El @auth muestra contenido para usuarios autenticados y el @guest para visitantes. Una alternativa a @if(auth()->check()).

Validación


12 cards
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>
@endif

El @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_confirmation

La 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


14 cards
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') !!}  // PERIGOSO

Blade 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


14 cards
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_completo

El 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


16 cards
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 revierte

El 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().