11: Controladores en Laravel 12 - Guía Práctica y Avanzada

 

Introducción

Los controladores son el corazón de la lógica de negocio en una aplicación Laravel. Actúan como intermediarios entre las rutas (peticiones HTTP) y las vistas/modelos, organizando el código de manera estructurada y mantenible. En este tutorial, exploraremos en profundidad cómo crear y utilizar controladores en Laravel 12, desde los conceptos básicos hasta técnicas avanzadas.

Índice

  1. ¿Qué son los Controladores?

  2. Creación de Controladores

  3. Controladores Resource

  4. Inyección de Dependencias

  5. Controladores con Middleware

  6. Controladores Invocables

  7. Controladores para API

  8. Controladores con Servicios

  9. Manejo de Excepciones

  10. Mejores Prácticas

  11. Ejercicio Práctico: CRUD Completo


1. ¿Qué son los Controladores?

Los controladores son clases que organizan la lógica de las peticiones HTTP. En lugar de definir toda la lógica en el archivo routes.php, los controladores permiten agrupar funcionalidades relacionadas.

Estructura básica de un Controlador

php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class PastelController extends Controller
{
    /**
     * Muestra la lista de pasteles
     */
    public function index()
    {
        // Lógica para mostrar pasteles
        return view('pasteles.index');
    }

    /**
     * Muestra el formulario para crear
     */
    public function create()
    {
        return view('pasteles.create');
    }

    /**
     * Almacena un nuevo pastel
     */
    public function store(Request $request)
    {
        // Lógica para guardar
    }

    /**
     * Muestra un pastel específico
     */
    public function show($id)
    {
        // Lógica para mostrar un pastel
    }

    /**
     * Muestra el formulario para editar
     */
    public function edit($id)
    {
        // Lógica para editar
    }

    /**
     * Actualiza un pastel específico
     */
    public function update(Request $request, $id)
    {
        // Lógica para actualizar
    }

    /**
     * Elimina un pastel
     */
    public function destroy($id)
    {
        // Lógica para eliminar
    }
}

Métodos HTTP y su relación con los Controladores

Método HTTPMétodo del ControladorPropósito
GETindex()Listar recursos
GETcreate()Mostrar formulario de creación
POSTstore()Almacenar nuevo recurso
GETshow($id)Mostrar recurso específico
GETedit($id)Mostrar formulario de edición
PUT/PATCHupdate($id)Actualizar recurso
DELETEdestroy($id)Eliminar recurso

2. Creación de Controladores

Comandos Artisan para crear Controladores

bash
# Controlador básico
php artisan make:controller PastelController

# Controlador Resource (con todos los métodos)
php artisan make:controller PastelController --resource

# Controlador con modelo vinculado
php artisan make:controller PastelController --model=Pastel

# Controlador con modelo y resource
php artisan make:controller PastelController --resource --model=Pastel

# Controlador para API
php artisan make:controller Api/PastelController --api

# Controlador invocable (single action)
php artisan make:controller ListarPastelesController --invokable

Ejemplo de Controlador con Model Binding

bash
php artisan make:controller PastelController --model=Pastel --resource
php
<?php

namespace App\Http\Controllers;

use App\Models\Pastel;
use Illuminate\Http\Request;
use App\Http\Requests\StorePastelRequest;
use App\Http\Requests\UpdatePastelRequest;

class PastelController extends Controller
{
    /**
     * Listar todos los pasteles
     */
    public function index()
    {
        $pasteles = Pastel::withTrashed()->paginate(10);
        return view('pasteles.index', compact('pasteles'));
    }

    /**
     * Mostrar formulario de creación
     */
    public function create()
    {
        $sabores = ['chocolate', 'vainilla', 'cheesecake', 'fresa', 'coco'];
        return view('pasteles.create', compact('sabores'));
    }

    /**
     * Almacenar nuevo pastel
     */
    public function store(StorePastelRequest $request)
    {
        $pastel = Pastel::create($request->validated());
        
        return redirect()
            ->route('pasteles.index')
            ->with('success', 'Pastel creado exitosamente');
    }

    /**
     * Mostrar pastel específico (Model Binding automático)
     */
    public function show(Pastel $pastel)
    {
        return view('pasteles.show', compact('pastel'));
    }

    /**
     * Mostrar formulario de edición
     */
    public function edit(Pastel $pastel)
    {
        $sabores = ['chocolate', 'vainilla', 'cheesecake', 'fresa', 'coco'];
        return view('pasteles.edit', compact('pastel', 'sabores'));
    }

    /**
     * Actualizar pastel
     */
    public function update(UpdatePastelRequest $request, Pastel $pastel)
    {
        $pastel->update($request->validated());
        
        return redirect()
            ->route('pasteles.index')
            ->with('success', 'Pastel actualizado exitosamente');
    }

    /**
     * Eliminar pastel
     */
    public function destroy(Pastel $pastel)
    {
        $pastel->delete();
        
        return redirect()
            ->route('pasteles.index')
            ->with('success', 'Pastel eliminado exitosamente');
    }
}

3. Controladores Resource

Ruta Resource Completa

php
// routes/web.php
Route::resource('pasteles', PastelController::class);

Personalizar Nombres de Rutas Resource

php
Route::resource('pasteles', PastelController::class)
    ->names([
        'index' => 'pasteles.list',
        'create' => 'pasteles.crear',
        'store' => 'pasteles.guardar',
        'show' => 'pasteles.ver',
        'edit' => 'pasteles.editar',
        'update' => 'pasteles.actualizar',
        'destroy' => 'pasteles.eliminar'
    ]);

Especificar Solo Algunos Métodos

php
// Solo permitir lectura y creación
Route::resource('pasteles', PastelController::class)
    ->only(['index', 'show', 'create', 'store']);

// Excluir métodos específicos
Route::resource('pasteles', PastelController::class)
    ->except(['destroy', 'update']);

Recursos Anidados

php
Route::resource('categorias.pasteles', PastelController::class)
    ->shallow();

Recursos con Parámetros Personalizados

php
Route::resource('pasteles', PastelController::class)
    ->parameters(['pasteles' => 'pastel_id']);

Tabla de Rutas Generadas

bash
php artisan route:list
MétodoURINombreAcción
GETpastelespasteles.indexindex
GETpasteles/createpasteles.createcreate
POSTpastelespasteles.storestore
GETpasteles/{pastel}pasteles.showshow
GETpasteles/{pastel}/editpasteles.editedit
PUT/PATCHpasteles/{pastel}pasteles.updateupdate
DELETEpasteles/{pastel}pasteles.destroydestroy

4. Inyección de Dependencias

Inyección en el Constructor

php
<?php

namespace App\Http\Controllers;

use App\Services\PastelService;
use App\Repositories\PastelRepository;

class PastelController extends Controller
{
    protected $pastelService;
    protected $pastelRepository;

    public function __construct(
        PastelService $pastelService,
        PastelRepository $pastelRepository
    ) {
        $this->pastelService = $pastelService;
        $this->pastelRepository = $pastelRepository;
    }

    public function index()
    {
        $pasteles = $this->pastelRepository->getAll();
        return view('pasteles.index', compact('pasteles'));
    }

    public function store(Request $request)
    {
        $pastel = $this->pastelService->create($request->all());
        return redirect()->route('pasteles.index');
    }
}

Inyección en Métodos Específicos

php
<?php

namespace App\Http\Controllers;

use App\Services\PastelService;
use App\Services\ReporteService;
use Illuminate\Http\Request;

class PastelController extends Controller
{
    public function index(PastelService $pastelService)
    {
        $pasteles = $pastelService->obtenerTodos();
        return view('pasteles.index', compact('pasteles'));
    }

    public function reporte(ReporteService $reporteService)
    {
        $reporte = $reporteService->generarReportePasteles();
        return response()->json($reporte);
    }

    public function exportar(Request $request, ExportService $exportService)
    {
        return $exportService->exportarPasteles($request->all());
    }
}

Inyección de Modelos (Route Model Binding)

php
// Sin model binding
public function show($id)
{
    $pastel = Pastel::findOrFail($id);
    return view('pasteles.show', compact('pastel'));
}

// Con model binding automático
public function show(Pastel $pastel)
{
    return view('pasteles.show', compact('pastel'));
}

// Con model binding personalizado
public function show(Pastel $pastel, $id)
{
    // $pastel tiene el modelo
    // $id es el parámetro de la ruta
}

5. Controladores con Middleware

Aplicar Middleware en el Constructor

php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class PastelController extends Controller
{
    public function __construct()
    {
        // Aplicar middleware a todos los métodos
        $this->middleware('auth');
        
        // Aplicar middleware a métodos específicos
        $this->middleware('admin')->only(['create', 'store', 'edit', 'update', 'destroy']);
        
        // Excluir middleware de métodos específicos
        $this->middleware('log')->except(['index', 'show']);
        
        // Middleware con parámetros
        $this->middleware('role:editor')->only(['edit', 'update']);
    }
}

Middleware en Rutas Resource

php
// Aplicar middleware a todo el resource
Route::resource('pasteles', PastelController::class)
    ->middleware('auth');

// Grupo de rutas con middleware
Route::middleware(['auth', 'admin'])->group(function () {
    Route::resource('pasteles', PastelController::class)
        ->except(['index', 'show']);
});

// Middleware específico por método
Route::resource('pasteles', PastelController::class)
    ->only(['create', 'store', 'edit', 'update', 'destroy'])
    ->middleware('admin');

Middleware Personalizado para Autorización

php
// Crear middleware
php artisan make:middleware CheckUserRole

// app/Http/Middleware/CheckUserRole.php
<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;

class CheckUserRole
{
    public function handle(Request $request, Closure $next, ...$roles)
    {
        if (!auth()->check()) {
            return redirect()->route('login');
        }

        foreach ($roles as $role) {
            if (auth()->user()->hasRole($role)) {
                return $next($request);
            }
        }

        abort(403, 'No tienes permisos para acceder a esta sección.');
    }
}

// Registrar en kernel
protected $routeMiddleware = [
    // ...
    'role' => \App\Http\Middleware\CheckUserRole::class,
];

// Usar en controlador
public function __construct()
{
    $this->middleware('role:admin,editor')->only(['edit', 'update']);
    $this->middleware('role:admin')->only(['destroy']);
}

6. Controladores Invocables

Los controladores invocables son útiles cuando un controlador tiene una única acción.

bash
php artisan make:controller ListarPastelesController --invokable
php
<?php

namespace App\Http\Controllers;

use App\Models\Pastel;
use Illuminate\Http\Request;
use Illuminate\View\View;

class ListarPastelesController extends Controller
{
    /**
     * Invoke the controller.
     */
    public function __invoke(Request $request): View
    {
        $query = Pastel::query();

        // Filtrar por sabor
        if ($request->has('sabor') && $request->sabor !== 'todos') {
            $query->where('sabor', $request->sabor);
        }

        // Filtrar por disponibilidad
        if ($request->has('disponible')) {
            $query->where('disponible', $request->disponible === 'si');
        }

        // Buscar por nombre
        if ($request->has('search')) {
            $query->where('nombre', 'like', "%{$request->search}%");
        }

        $pasteles = $query->paginate(15);
        $sabores = ['chocolate', 'vainilla', 'cheesecake', 'fresa', 'coco'];
        
        return view('pasteles.index', compact('pasteles', 'sabores'));
    }
}

Ruta para Controlador Invocable

php
Route::get('/pasteles/listar', ListarPastelesController::class);

Múltiples Controladores Invocables

php
// Crear controladores para cada acción
php artisan make:controller CrearPastelController --invokable
php artisan make:controller EditarPastelController --invokable
php artisan make:controller EliminarPastelController --invokable

// Registrar rutas
Route::get('/pasteles/crear', CrearPastelController::class);
Route::get('/pasteles/{pastel}/editar', EditarPastelController::class);
Route::delete('/pasteles/{pastel}', EliminarPastelController::class);

7. Controladores para API

Crear Controlador API

bash
php artisan make:controller Api/PastelController --api
php
<?php

namespace App\Http\Controllers\Api;

use App\Http\Controllers\Controller;
use App\Models\Pastel;
use App\Http\Resources\PastelResource;
use App\Http\Resources\PastelCollection;
use App\Http\Requests\StorePastelRequest;
use App\Http\Requests\UpdatePastelRequest;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;

class PastelController extends Controller
{
    /**
     * Display a listing of the resource.
     */
    public function index(Request $request): PastelCollection
    {
        $query = Pastel::query();

        // Filtros
        if ($request->has('sabor')) {
            $query->where('sabor', $request->sabor);
        }

        if ($request->has('disponible')) {
            $query->where('disponible', filter_var($request->disponible, FILTER_VALIDATE_BOOLEAN));
        }

        // Ordenamiento
        if ($request->has('sort_by') && in_array($request->sort_by, ['nombre', 'precio', 'created_at'])) {
            $direction = $request->get('direction', 'asc');
            $query->orderBy($request->sort_by, $direction);
        }

        // Paginación
        $perPage = $request->get('per_page', 15);
        $pasteles = $query->paginate($perPage);

        return new PastelCollection($pasteles);
    }

    /**
     * Store a newly created resource in storage.
     */
    public function store(StorePastelRequest $request): PastelResource
    {
        $pastel = Pastel::create($request->validated());
        
        return new PastelResource($pastel);
    }

    /**
     * Display the specified resource.
     */
    public function show(Pastel $pastel): PastelResource
    {
        return new PastelResource($pastel);
    }

    /**
     * Update the specified resource in storage.
     */
    public function update(UpdatePastelRequest $request, Pastel $pastel): PastelResource
    {
        $pastel->update($request->validated());
        
        return new PastelResource($pastel);
    }

    /**
     * Remove the specified resource from storage.
     */
    public function destroy(Pastel $pastel): JsonResponse
    {
        $pastel->delete();
        
        return response()->json([
            'message' => 'Pastel eliminado exitosamente'
        ], 200);
    }

    /**
     * Restore a soft-deleted resource.
     */
    public function restore($id): JsonResponse
    {
        $pastel = Pastel::withTrashed()->findOrFail($id);
        $pastel->restore();

        return response()->json([
            'message' => 'Pastel restaurado exitosamente',
            'pastel' => new PastelResource($pastel)
        ]);
    }

    /**
     * Get available flavors.
     */
    public function sabores(): JsonResponse
    {
        $sabores = Pastel::select('sabor')
            ->distinct()
            ->pluck('sabor')
            ->toArray();

        return response()->json($sabores);
    }
}

Crear Resources para API

bash
php artisan make:resource PastelResource
php artisan make:resource PastelCollection
php
<?php

namespace App\Http\Resources;

use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;

class PastelResource extends JsonResource
{
    /**
     * Transform the resource into an array.
     *
     * @return array<string, mixed>
     */
    public function toArray(Request $request): array
    {
        return [
            'id' => $this->id,
            'nombre' => $this->nombre,
            'sabor' => $this->sabor,
            'precio' => number_format($this->precio, 2),
            'disponible' => $this->disponible,
            'descripcion' => $this->descripcion,
            'creado_en' => $this->created_at->toISOString(),
            'actualizado_en' => $this->updated_at->toISOString(),
            'links' => [
                'self' => route('api.pasteles.show', $this->id),
                'edit' => route('api.pasteles.edit', $this->id),
            ]
        ];
    }
}

Rutas API

php
// routes/api.php
use App\Http\Controllers\Api\PastelController;

Route::apiResource('pasteles', PastelController::class);

// Rutas adicionales
Route::prefix('pasteles')->group(function () {
    Route::get('/sabores', [PastelController::class, 'sabores']);
    Route::post('/{id}/restore', [PastelController::class, 'restore']);
});

8. Controladores con Servicios

Crear Servicios

php
// app/Services/PastelService.php
<?php

namespace App\Services;

use App\Models\Pastel;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Storage;

class PastelService
{
    public function obtenerTodosConCache()
    {
        return Cache::remember('pasteles.todos', 3600, function () {
            return Pastel::withTrashed()->get();
        });
    }

    public function crearPastel(array $data): Pastel
    {
        // Procesar datos
        $data['nombre'] = ucwords(strtolower($data['nombre']));
        $data['disponible'] = $data['disponible'] ?? true;

        // Crear pastel
        $pastel = Pastel::create($data);

        // Limpiar caché
        Cache::forget('pasteles.todos');

        return $pastel;
    }

    public function actualizarPastel(Pastel $pastel, array $data): Pastel
    {
        $pastel->update($data);
        Cache::forget('pasteles.todos');
        
        return $pastel;
    }

    public function eliminarPastel(Pastel $pastel): bool
    {
        $result = $pastel->delete();
        Cache::forget('pasteles.todos');
        
        return $result;
    }

    public function importarDesdeCSV($filePath): array
    {
        $importados = 0;
        $errores = [];

        if (($handle = fopen($filePath, 'r')) !== false) {
            while (($row = fgetcsv($handle, 1000, ',')) !== false) {
                try {
                    Pastel::create([
                        'nombre' => $row[0],
                        'sabor' => $row[1],
                        'precio' => $row[2] ?? 0,
                        'disponible' => $row[3] ?? true,
                    ]);
                    $importados++;
                } catch (\Exception $e) {
                    $errores[] = $e->getMessage();
                }
            }
            fclose($handle);
        }

        return [
            'importados' => $importados,
            'errores' => $errores
        ];
    }
}

Controlador con Servicio

php
<?php

namespace App\Http\Controllers;

use App\Services\PastelService;
use App\Http\Requests\StorePastelRequest;
use App\Http\Requests\UpdatePastelRequest;
use App\Models\Pastel;
use Illuminate\Http\Request;

class PastelController extends Controller
{
    protected $pastelService;

    public function __construct(PastelService $pastelService)
    {
        $this->pastelService = $pastelService;
    }

    public function index()
    {
        $pasteles = $this->pastelService->obtenerTodosConCache();
        return view('pasteles.index', compact('pasteles'));
    }

    public function store(StorePastelRequest $request)
    {
        $pastel = $this->pastelService->crearPastel($request->validated());
        
        return redirect()
            ->route('pasteles.index')
            ->with('success', 'Pastel creado exitosamente');
    }

    public function update(UpdatePastelRequest $request, Pastel $pastel)
    {
        $this->pastelService->actualizarPastel($pastel, $request->validated());
        
        return redirect()
            ->route('pasteles.index')
            ->with('success', 'Pastel actualizado exitosamente');
    }

    public function destroy(Pastel $pastel)
    {
        $this->pastelService->eliminarPastel($pastel);
        
        return redirect()
            ->route('pasteles.index')
            ->with('success', 'Pastel eliminado exitosamente');
    }

    public function importar(Request $request)
    {
        $request->validate([
            'archivo' => 'required|file|mimes:csv,txt'
        ]);

        $path = $request->file('archivo')->store('imports');
        $resultado = $this->pastelService->importarDesdeCSV(storage_path('app/' . $path));

        return redirect()
            ->route('pasteles.index')
            ->with('success', "Se importaron {$resultado['importados']} pasteles");
    }
}

9. Manejo de Excepciones

Controladores con Manejo de Excepciones

php
<?php

namespace App\Http\Controllers;

use App\Models\Pastel;
use Illuminate\Http\Request;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Validation\ValidationException;
use Illuminate\Support\Facades\Log;

class PastelController extends Controller
{
    public function show($id)
    {
        try {
            $pastel = Pastel::findOrFail($id);
            return view('pasteles.show', compact('pastel'));
        } catch (ModelNotFoundException $e) {
            Log::error('Pastel no encontrado', ['id' => $id, 'error' => $e->getMessage()]);
            
            return redirect()
                ->route('pasteles.index')
                ->with('error', 'El pastel solicitado no existe.');
        }
    }

    public function store(Request $request)
    {
        try {
            $validated = $request->validate([
                'nombre' => 'required|string|max:100|unique:pasteles',
                'sabor' => 'required|in:chocolate,vainilla,cheesecake',
                'precio' => 'nullable|numeric|min:0'
            ]);

            $pastel = Pastel::create($validated);
            
            return redirect()
                ->route('pasteles.index')
                ->with('success', 'Pastel creado exitosamente.');

        } catch (ValidationException $e) {
            return redirect()
                ->back()
                ->withErrors($e->validator)
                ->withInput();
                
        } catch (\Exception $e) {
            Log::error('Error al crear pastel', [
                'error' => $e->getMessage(),
                'data' => $request->all()
            ]);
            
            return redirect()
                ->back()
                ->with('error', 'Ocurrió un error al crear el pastel. Intenta nuevamente.')
                ->withInput();
        }
    }

    public function destroy(Pastel $pastel)
    {
        try {
            $pastel->delete();
            
            return redirect()
                ->route('pasteles.index')
                ->with('success', 'Pastel eliminado exitosamente.');
                
        } catch (\Exception $e) {
            Log::error('Error al eliminar pastel', [
                'id' => $pastel->id,
                'error' => $e->getMessage()
            ]);
            
            return redirect()
                ->back()
                ->with('error', 'No se pudo eliminar el pastel. Intenta nuevamente.');
        }
    }
}

Excepciones Personalizadas

php
// app/Exceptions/PastelException.php
<?php

namespace App\Exceptions;

use Exception;

class PastelException extends Exception
{
    public static function noEncontrado($id)
    {
        return new static("Pastel con ID {$id} no encontrado.", 404);
    }

    public static function sinStock($nombre)
    {
        return new static("El pastel {$nombre} no está disponible en stock.", 409);
    }

    public static function precioInvalido($precio)
    {
        return new static("El precio {$precio} no es válido.", 422);
    }
}

// Uso en el controlador
public function show($id)
{
    try {
        $pastel = Pastel::findOrFail($id);
        return view('pasteles.show', compact('pastel'));
    } catch (ModelNotFoundException $e) {
        throw PastelException::noEncontrado($id);
    }
}

10. Mejores Prácticas

1. Mantén tus Controladores Delgados

php
// ❌ Mal: Controlador con mucha lógica
public function store(Request $request)
{
    // Validación
    $this->validate($request, [...]);
    
    // Procesamiento
    $pastel = new Pastel();
    $pastel->nombre = $request->nombre;
    $pastel->sabor = $request->sabor;
    // ... más lógica
    
    // Envío de emails
    Mail::to($request->email)->send(...);
    
    // Log
    Log::info(...);
}

// ✅ Bien: Usar servicios y repositorios
public function store(StorePastelRequest $request, PastelService $service)
{
    $pastel = $service->create($request->validated());
    
    return redirect()->route('pasteles.index')
        ->with('success', 'Pastel creado');
}

2. Usa Route Model Binding

php
// ❌ Mal
public function show($id)
{
    $pastel = Pastel::findOrFail($id);
    return view('pasteles.show', compact('pastel'));
}

// ✅ Bien
public function show(Pastel $pastel)
{
    return view('pasteles.show', compact('pastel'));
}

3. Implementa Principios SOLID

php
// Responsabilidad Única
interface PastelRepositoryInterface
{
    public function getAll();
    public function findById($id);
    public function create(array $data);
    public function update($id, array $data);
    public function delete($id);
}

// Implementación
class PastelRepository implements PastelRepositoryInterface
{
    protected $model;
    
    public function __construct(Pastel $model)
    {
        $this->model = $model;
    }
    
    public function getAll()
    {
        return $this->model->all();
    }
    
    // ... implementar otros métodos
}

// Controlador inyectando la interfaz
class PastelController extends Controller
{
    protected $repository;
    
    public function __construct(PastelRepositoryInterface $repository)
    {
        $this->repository = $repository;
    }
}

4. Usa Inyección de Dependencias

php
// ✅ Bien: Inyectar dependencias
public function __construct(PastelService $service, Logger $logger)
{
    $this->service = $service;
    $this->logger = $logger;
}

// ❌ Mal: Crear dependencias dentro del método
public function index()
{
    $service = new PastelService();
    $logger = new Logger();
}

5. Organiza por Módulos

php
// Estructura de carpetas recomendada
app/
├── Http/
│   ├── Controllers/
│   │   ├── Admin/
│   │   │   └── PastelController.php
│   │   ├── Api/
│   │   │   └── PastelController.php
│   │   └── Web/
│   │       └── PastelController.php
│   ├── Requests/
│   │   ├── StorePastelRequest.php
│   │   └── UpdatePastelRequest.php
│   └── Resources/
│       └── PastelResource.php
├── Services/
│   └── PastelService.php
└── Repositories/
    └── PastelRepository.php

6. Usa Traits para Funcionalidades Compartidas

php
// app/Traits/Paginatable.php
<?php

namespace App\Traits;

trait Paginatable
{
    protected function getPaginationParams(Request $request): array
    {
        return [
            'per_page' => $request->get('per_page', 15),
            'page' => $request->get('page', 1),
            'sort_by' => $request->get('sort_by', 'id'),
            'sort_direction' => $request->get('direction', 'desc')
        ];
    }
}

// Uso en controlador
class PastelController extends Controller
{
    use Paginatable;
    
    public function index(Request $request)
    {
        $params = $this->getPaginationParams($request);
        // Usar parámetros para la paginación
    }
}

7. Implementa Logging y Debug

php
public function store(StorePastelRequest $request)
{
    Log::info('Creando nuevo pastel', [
        'user_id' => auth()->id(),
        'data' => $request->validated()
    ]);
    
    try {
        $pastel = Pastel::create($request->validated());
        
        Log::info('Pastel creado exitosamente', [
            'pastel_id' => $pastel->id
        ]);
        
        return redirect()->route('pasteles.index')
            ->with('success', 'Pastel creado');
            
    } catch (\Exception $e) {
        Log::error('Error al crear pastel', [
            'error' => $e->getMessage(),
            'trace' => $e->getTraceAsString()
        ]);
        
        return back()
            ->with('error', 'Error al crear el pastel')
            ->withInput();
    }
}

8. Validación de Seguridad

php
public function update(UpdatePastelRequest $request, Pastel $pastel)
{
    // Verificar autorización
    if (auth()->user()->cannot('update', $pastel)) {
        abort(403, 'No tienes permiso para editar este pastel.');
    }
    
    // Rate limiting
    $this->rateLimit('update_pastel', 5, 60);
    
    $pastel->update($request->validated());
    
    return redirect()->route('pasteles.index');
}

11. Ejercicio Práctico: CRUD Completo

Paso 1: Crear el Controlador

bash
php artisan make:controller PastelController --resource --model=Pastel

Paso 2: Crear el Modelo con Migración

bash
php artisan make:model Pastel -m
php
// database/migrations/xxxx_create_pasteles_table.php
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    public function up(): void
    {
        Schema::create('pasteles', function (Blueprint $table) {
            $table->id();
            $table->string('nombre', 100);
            $table->enum('sabor', ['chocolate', 'vainilla', 'cheesecake', 'fresa', 'coco']);
            $table->decimal('precio', 8, 2)->nullable();
            $table->boolean('disponible')->default(true);
            $table->text('descripcion')->nullable();
            $table->timestamps();
            $table->softDeletes();
        });
    }

    public function down(): void
    {
        Schema::dropIfExists('pasteles');
    }
};

Paso 3: Crear Form Requests

bash
php artisan make:request StorePastelRequest
php artisan make:request UpdatePastelRequest

Paso 4: Implementar el Controlador

php
<?php

namespace App\Http\Controllers;

use App\Models\Pastel;
use App\Http\Requests\StorePastelRequest;
use App\Http\Requests\UpdatePastelRequest;
use Illuminate\Http\Request;
use Illuminate\View\View;
use Illuminate\Http\RedirectResponse;

class PastelController extends Controller
{
    public function __construct()
    {
        $this->middleware('auth')->except(['index', 'show']);
        $this->middleware('admin')->only(['destroy']);
    }

    public function index(): View
    {
        $pasteles = Pastel::withTrashed()->paginate(10);
        return view('pasteles.index', compact('pasteles'));
    }

    public function create(): View
    {
        $sabores = ['chocolate', 'vainilla', 'cheesecake', 'fresa', 'coco'];
        return view('pasteles.create', compact('sabores'));
    }

    public function store(StorePastelRequest $request): RedirectResponse
    {
        $pastel = Pastel::create($request->validated());
        
        return redirect()
            ->route('pasteles.index')
            ->with('success', 'Pastel creado exitosamente');
    }

    public function show(Pastel $pastel): View
    {
        return view('pasteles.show', compact('pastel'));
    }

    public function edit(Pastel $pastel): View
    {
        $sabores = ['chocolate', 'vainilla', 'cheesecake', 'fresa', 'coco'];
        return view('pasteles.edit', compact('pastel', 'sabores'));
    }

    public function update(UpdatePastelRequest $request, Pastel $pastel): RedirectResponse
    {
        $pastel->update($request->validated());
        
        return redirect()
            ->route('pasteles.index')
            ->with('success', 'Pastel actualizado exitosamente');
    }

    public function destroy(Pastel $pastel): RedirectResponse
    {
        $pastel->delete();
        
        return redirect()
            ->route('pasteles.index')
            ->with('success', 'Pastel eliminado exitosamente');
    }

    public function restore($id): RedirectResponse
    {
        $pastel = Pastel::withTrashed()->findOrFail($id);
        $pastel->restore();
        
        return redirect()
            ->route('pasteles.index')
            ->with('success', 'Pastel restaurado exitosamente');
    }
}

Paso 5: Configurar Rutas

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

Route::resource('pasteles', PastelController::class);
Route::post('/pasteles/{id}/restore', [PastelController::class, 'restore'])->name('pasteles.restore');

Paso 6: Verificar el Funcionamiento

bash
# Ver todas las rutas
php artisan route:list

# Probar el servidor
php artisan serve

Comandos Útiles

bash
# Crear controlador con métodos básicos
php artisan make:controller NombreController

# Crear controlador resource completo
php artisan make:controller NombreController --resource

# Crear controlador con modelo
php artisan make:controller NombreController --model=Modelo

# Crear controlador API
php artisan make:controller Api/NombreController --api

# Crear controlador invocable
php artisan make:controller NombreController --invokable

# Ver todas las rutas registradas
php artisan route:list

# Ver rutas con detalles
php artisan route:list -v

# Ver rutas de un controlador específico
php artisan route:list --path=pasteles

Resumen

ConceptoDescripciónComando
Controlador básicoClase con métodos para HTTPmake:controller
Resource ControllerControlador con 7 métodos CRUD--resource
Model BindingInyección automática de modelos--model
InvocableControlador con una sola acción--invokable
API ControllerControlador sin vistas--api
MiddlewareFiltros para peticiones$this->middleware()
InyecciónDependencias en constructor/métodos__construct()

¡Con este conocimiento de controladores en Laravel 12, estás listo para construir aplicaciones web robustas y bien organizadas

Comentarios

Entradas más populares de este blog

Tutorial CRUD con Laravel 12-Pastel::factory(50)->create();

CRUD Completo con Laravel 12 - Guía Práctica Paso a Paso-productos

CRUD en Laravel 12 con Git - Guía para Principiantes