Soluciones Laravel: Patrones Avanzados, Depuración y Preguntas de Entrevista 2026

Dominar las soluciones Laravel con patrones arquitectónicos avanzados, técnicas de depuración y conocimientos esenciales para entrevistas. Clases de servicio, patrón Repository, depuración con Telescope y preguntas frecuentes.

Patrones arquitectónicos y depuración avanzada en Laravel con PHP 8.4

Las soluciones Laravel para aplicaciones en producción requieren mucho más que operaciones CRUD básicas. La diferencia entre un desarrollador Laravel junior y senior se manifiesta en la estructuración del código, los métodos de depuración y la capacidad de responder preguntas arquitectónicas durante las entrevistas técnicas.

Lo que buscan los reclutadores

Los puestos senior de Laravel esperan que los candidatos expliquen el Service Container, demuestren flujos de trabajo de depuración con Telescope o Debugbar, y justifiquen cuándo usar patrones como Repository versus consultas Eloquent directas.

Clases de Servicio: Extraer la Lógica de Negocio de los Controladores

Los controladores en Laravel manejan las preocupaciones HTTP: recibir solicitudes, validar entradas y devolver respuestas. La lógica de negocio pertenece a clases de servicio dedicadas, haciendo el código testeable y reutilizable a través de controladores, comandos y jobs.

app/Services/OrderService.phpphp
// Service class encapsulating order business logic

namespace App\Services;

use App\Models\Order;
use App\Models\User;
use App\Exceptions\InsufficientStockException;
use App\Jobs\SendOrderConfirmationEmail;
use Illuminate\Support\Facades\DB;

class OrderService
{
    public function __construct(
        private InventoryService $inventory,
        private PaymentService $payment
    ) {}

    /**
     * Create an order with full validation and side effects
     *
     * @throws InsufficientStockException
     */
    public function createOrder(User $user, array $items, string $paymentMethod): Order
    {
        // Validate stock availability before starting transaction
        foreach ($items as $item) {
            if (!$this->inventory->hasStock($item['product_id'], $item['quantity'])) {
                throw new InsufficientStockException($item['product_id']);
            }
        }

        return DB::transaction(function () use ($user, $items, $paymentMethod) {
            // Create order record
            $order = $user->orders()->create([
                'status' => 'pending',
                'total' => $this->calculateTotal($items),
            ]);

            // Attach order items
            foreach ($items as $item) {
                $order->items()->create($item);
                $this->inventory->decrementStock($item['product_id'], $item['quantity']);
            }

            // Process payment
            $this->payment->charge($user, $order->total, $paymentMethod);
            $order->update(['status' => 'paid']);

            // Queue confirmation email
            SendOrderConfirmationEmail::dispatch($order);

            return $order;
        });
    }

    private function calculateTotal(array $items): int
    {
        return collect($items)->sum(fn($item) => $item['price'] * $item['quantity']);
    }
}
app/Http/Controllers/OrderController.phpphp
// Controller delegating to service class

namespace App\Http\Controllers;

use App\Http\Requests\CreateOrderRequest;
use App\Services\OrderService;
use Illuminate\Http\JsonResponse;

class OrderController extends Controller
{
    public function __construct(
        private OrderService $orderService
    ) {}

    public function store(CreateOrderRequest $request): JsonResponse
    {
        $order = $this->orderService->createOrder(
            $request->user(),
            $request->validated('items'),
            $request->validated('payment_method')
        );

        return response()->json(['order_id' => $order->id], 201);
    }
}

Las clases de servicio pueden inyectarse en cualquier lugar de Laravel: controladores, comandos, otros servicios o clases de job. La documentación del Service Container cubre la resolución automática y los bindings.

Patrón Repository: Cuando Eloquent Directo No Es Suficiente

El patrón Repository agrega una capa de abstracción entre la lógica de negocio y el acceso a datos. Este patrón resulta valioso cuando las aplicaciones necesitan cambiar fuentes de datos, cachear resultados de consultas de manera transparente, o aislar lógica de consultas complejas.

Contexto de entrevista

A los candidatos se les pregunta frecuentemente si usan el patrón Repository con Laravel. La respuesta correcta depende del contexto: los repositorios aportan valor para aplicaciones grandes con requisitos de datos complejos, pero crean abstracción innecesaria para aplicaciones CRUD simples.

app/Repositories/Contracts/UserRepositoryInterface.phpphp
// Interface defining the contract

namespace App\Repositories\Contracts;

use App\Models\User;
use Illuminate\Support\Collection;

interface UserRepositoryInterface
{
    public function findById(int $id): ?User;
    public function findByEmail(string $email): ?User;
    public function getActiveUsers(): Collection;
    public function getUsersWithRecentOrders(int $days = 30): Collection;
    public function create(array $data): User;
    public function update(User $user, array $data): User;
}
app/Repositories/EloquentUserRepository.phpphp
// Eloquent implementation of the repository

namespace App\Repositories;

use App\Models\User;
use App\Repositories\Contracts\UserRepositoryInterface;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Cache;

class EloquentUserRepository implements UserRepositoryInterface
{
    public function findById(int $id): ?User
    {
        return Cache::remember(
            "user.{$id}",
            now()->addMinutes(10),
            fn() => User::find($id)
        );
    }

    public function findByEmail(string $email): ?User
    {
        return User::where('email', $email)->first();
    }

    public function getActiveUsers(): Collection
    {
        return User::where('status', 'active')
            ->orderBy('name')
            ->get();
    }

    public function getUsersWithRecentOrders(int $days = 30): Collection
    {
        return User::whereHas('orders', function ($query) use ($days) {
            $query->where('created_at', '>=', now()->subDays($days));
        })
        ->with(['orders' => fn($q) => $q->latest()->limit(5)])
        ->get();
    }

    public function create(array $data): User
    {
        $user = User::create($data);
        Cache::forget("user.{$user->id}");
        return $user;
    }

    public function update(User $user, array $data): User
    {
        $user->update($data);
        Cache::forget("user.{$user->id}");
        return $user->fresh();
    }
}
app/Providers/RepositoryServiceProvider.phpphp
// Binding interface to implementation

namespace App\Providers;

use App\Repositories\Contracts\UserRepositoryInterface;
use App\Repositories\EloquentUserRepository;
use Illuminate\Support\ServiceProvider;

class RepositoryServiceProvider extends ServiceProvider
{
    public function register(): void
    {
        $this->app->bind(
            UserRepositoryInterface::class,
            EloquentUserRepository::class
        );
    }
}

La implementación del repositorio maneja el caché internamente, permitiendo que las clases de servicio se enfoquen en las reglas de negocio en lugar de la invalidación de caché.

¿Listo para aprobar tus entrevistas de Laravel?

Practica con nuestros simuladores interactivos, flashcards y tests técnicos.

Depuración de Aplicaciones Laravel con Telescope

Laravel Telescope proporciona un asistente de depuración que registra solicitudes, excepciones, consultas de base de datos, jobs y más. La depuración en producción requiere entender qué métricas importan y cómo filtrar el ruido.

config/telescope.phpphp
// Telescope configuration for different environments

return [
    'enabled' => env('TELESCOPE_ENABLED', true),

    // Only record slow queries in production
    'query' => [
        'slow' => env('TELESCOPE_SLOW_QUERY_THRESHOLD', 100), // milliseconds
    ],

    // Prune old entries to manage database size
    'storage' => [
        'database' => [
            'connection' => env('DB_CONNECTION', 'mysql'),
            'chunk' => 1000,
        ],
    ],
];
app/Providers/TelescopeServiceProvider.phpphp
// Custom filtering for Telescope entries

namespace App\Providers;

use Laravel\Telescope\IncomingEntry;
use Laravel\Telescope\Telescope;
use Laravel\Telescope\TelescopeApplicationServiceProvider;

class TelescopeServiceProvider extends TelescopeApplicationServiceProvider
{
    public function register(): void
    {
        Telescope::night();

        $this->hideSensitiveRequestDetails();

        // Filter out noise in production
        Telescope::filter(function (IncomingEntry $entry) {
            // Always record exceptions and slow queries
            if ($entry->isException()) {
                return true;
            }

            if ($entry->isSlowQuery()) {
                return true;
            }

            // Skip health check endpoints
            if ($entry->isRequest() && str_starts_with($entry->content['uri'] ?? '', '/health')) {
                return false;
            }

            // Skip static asset requests
            if ($entry->isRequest() && preg_match('/\.(css|js|png|jpg|svg)$/', $entry->content['uri'] ?? '')) {
                return false;
            }

            // Record everything in local environment
            if ($this->app->environment('local')) {
                return true;
            }

            // Production: only record failures and slow operations
            return $entry->isFailedRequest()
                || $entry->isFailedJob()
                || $entry->hasMonitoredTag();
        });
    }

    protected function hideSensitiveRequestDetails(): void
    {
        // Hide sensitive data from Telescope UI
        Telescope::hideRequestParameters(['password', 'password_confirmation', 'token']);
        Telescope::hideRequestHeaders(['authorization', 'cookie']);
    }
}

El método isSlowQuery() marca las consultas de base de datos que exceden el umbral configurado. El análisis de consultas lentas revela índices faltantes y problemas N+1 que herramientas de profiling como Debugbar también detectan.

Preguntas de Entrevista Comunes y Respuestas Sólidas

Las entrevistas técnicas para puestos de Laravel siguen patrones. Las preguntas a continuación aparecen frecuentemente, y las respuestas demuestran la profundidad que los entrevistadores esperan.

¿Qué Es el Service Container y Por Qué Es Importante?

El Service Container es el contenedor de inyección de dependencias de Laravel. Gestiona las dependencias de las clases y realiza la inyección de dependencias automáticamente. Cuando un constructor de controlador tiene un type-hint de OrderService, el contenedor resuelve e inyecta una instancia.

php
// Automatic resolution: container reads type hints and builds dependencies
public function __construct(OrderService $service)
{
    // $service is automatically instantiated and injected
}

// Manual binding for interfaces or complex setup
$this->app->bind(PaymentGateway::class, function ($app) {
    return new StripeGateway(
        config('services.stripe.key'),
        config('services.stripe.secret')
    );
});

// Singleton: same instance throughout the request
$this->app->singleton(MetricsCollector::class, function ($app) {
    return new MetricsCollector(
        $app->make(Cache::class)
    );
});

El contenedor permite un acoplamiento débil: las clases dependen de abstracciones (interfaces) en lugar de implementaciones concretas, haciendo que las pruebas y el intercambio de implementaciones sean sencillos.

¿Cómo Maneja Laravel las Transacciones de Base de Datos?

Laravel envuelve las operaciones de base de datos en transacciones usando el método DB::transaction(). Las transacciones garantizan la atomicidad: o todas las operaciones tienen éxito, o todo se revierte.

php
// Simple transaction with automatic rollback on exception
DB::transaction(function () {
    $user = User::create(['email' => 'test@example.com']);
    $user->profile()->create(['bio' => 'New user']);
    // If profile creation fails, user creation rolls back
});

// Manual transaction control for complex flows
DB::beginTransaction();

try {
    $order = Order::create($data);
    PaymentGateway::charge($order->total);

    DB::commit();
} catch (PaymentFailedException $e) {
    DB::rollBack();
    throw $e;
}

// Nested transactions use savepoints
DB::transaction(function () {
    User::create(['email' => 'outer@test.com']);

    DB::transaction(function () {
        // This creates a savepoint
        Profile::create(['user_id' => 1]);
    });
    // Inner failure only rolls back to savepoint
});

Los entrevistadores frecuentemente preguntan después sobre deadlocks. La respuesta: Laravel reintenta las transacciones que fallan debido a deadlocks (configurable a través del segundo argumento de DB::transaction()).

Explique los Middleware y Proporcione un Caso de Uso Real

Los Middleware filtran las solicitudes HTTP que entran en la aplicación. Cada middleware puede inspeccionar, modificar o rechazar solicitudes antes de que lleguen a los controladores.

app/Http/Middleware/EnsureTeamMember.phpphp
// Custom middleware checking team membership

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;

class EnsureTeamMember
{
    public function handle(Request $request, Closure $next, string $role = 'member'): Response
    {
        $team = $request->route('team');

        if (!$request->user()->belongsToTeam($team)) {
            abort(403, 'You are not a member of this team.');
        }

        if ($role === 'admin' && !$request->user()->isTeamAdmin($team)) {
            abort(403, 'Admin access required.');
        }

        return $next($request);
    }
}
routes/web.phpphp
// Applying middleware to routes

Route::middleware(['auth', 'team.member:admin'])->group(function () {
    Route::get('/teams/{team}/settings', [TeamController::class, 'settings']);
    Route::put('/teams/{team}/settings', [TeamController::class, 'updateSettings']);
});

Los middleware se ejecutan en orden. El middleware de autenticación debe ejecutarse antes del middleware de autorización, y el middleware de logging típicamente envuelve todo.

Resolver el Problema de Consultas N+1

El problema N+1 genera una consulta para la colección inicial más una consulta por elemento al acceder a las relaciones. Una lista de 100 artículos con autores produce 101 consultas en lugar de 2.

php
// Problem: 101 queries for 100 articles
$articles = Article::all();
foreach ($articles as $article) {
    echo $article->author->name; // Each access triggers a query
}

// Solution: eager load with 2 queries total
$articles = Article::with('author')->get();
foreach ($articles as $article) {
    echo $article->author->name; // No additional queries
}

// Nested eager loading for complex relationships
$articles = Article::with([
    'author.profile',
    'comments' => fn($query) => $query->latest()->limit(5),
    'comments.user',
    'tags',
])->get();

Laravel puede prevenir el lazy loading en desarrollo para detectar problemas N+1 tempranamente:

app/Providers/AppServiceProvider.phpphp
use Illuminate\Database\Eloquent\Model;

public function boot(): void
{
    Model::preventLazyLoading(!$this->app->isProduction());
}

Con esta configuración, acceder a una relación no cargada lanza una excepción en desarrollo, forzando un eager loading explícito.

¿Listo para aprobar tus entrevistas de Laravel?

Practica con nuestros simuladores interactivos, flashcards y tests técnicos.

Clases Action para Operaciones de Propósito Único

Las clases Action encapsulan operaciones únicas que pueden invocarse desde múltiples puntos de entrada. A diferencia de las clases de servicio que agrupan métodos relacionados, las acciones manejan una sola tarea con entradas y salidas claras.

app/Actions/CreateUserAction.phpphp
// Single-purpose action class

namespace App\Actions;

use App\Models\User;
use App\Notifications\WelcomeNotification;
use Illuminate\Support\Facades\Hash;

class CreateUserAction
{
    public function execute(array $data): User
    {
        $user = User::create([
            'name' => $data['name'],
            'email' => $data['email'],
            'password' => Hash::make($data['password']),
        ]);

        $user->notify(new WelcomeNotification());

        return $user;
    }
}
php
// Usage in controller
public function store(RegisterRequest $request, CreateUserAction $action)
{
    $user = $action->execute($request->validated());
    return redirect()->route('dashboard');
}

// Usage in command
public function handle(CreateUserAction $action)
{
    $user = $action->execute([
        'name' => $this->argument('name'),
        'email' => $this->argument('email'),
        'password' => $this->argument('password'),
    ]);
    $this->info("Created user: {$user->id}");
}

// Usage in test
public function test_user_creation(): void
{
    $action = new CreateUserAction();
    $user = $action->execute([
        'name' => 'Test User',
        'email' => 'test@example.com',
        'password' => 'password',
    ]);

    $this->assertDatabaseHas('users', ['email' => 'test@example.com']);
}

Las clases Action funcionan bien con el sistema de jobs de Laravel: la acción contiene la lógica, y el job maneja el encolamiento y el comportamiento de reintento.

Arquitectura Basada en Eventos con Events y Listeners

Los eventos desacoplan el momento en que algo sucede de las reacciones a ese evento. Cuando se realiza un pedido, el evento OrderPlaced se dispara. Los listeners manejan el envío de emails, la actualización de analytics y la notificación a los almacenes independientemente.

app/Events/OrderPlaced.phpphp
// Event class carrying order data

namespace App\Events;

use App\Models\Order;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;

class OrderPlaced
{
    use Dispatchable, InteractsWithSockets, SerializesModels;

    public function __construct(
        public Order $order
    ) {}
}
app/Listeners/SendOrderConfirmation.phpphp
// Queued listener for email

namespace App\Listeners;

use App\Events\OrderPlaced;
use App\Notifications\OrderConfirmationNotification;
use Illuminate\Contracts\Queue\ShouldQueue;

class SendOrderConfirmation implements ShouldQueue
{
    public function handle(OrderPlaced $event): void
    {
        $event->order->user->notify(
            new OrderConfirmationNotification($event->order)
        );
    }
}
app/Listeners/UpdateInventoryAnalytics.phpphp
// Another listener for the same event

namespace App\Listeners;

use App\Events\OrderPlaced;
use App\Services\AnalyticsService;
use Illuminate\Contracts\Queue\ShouldQueue;

class UpdateInventoryAnalytics implements ShouldQueue
{
    public function __construct(
        private AnalyticsService $analytics
    ) {}

    public function handle(OrderPlaced $event): void
    {
        foreach ($event->order->items as $item) {
            $this->analytics->recordSale(
                $item->product_id,
                $item->quantity,
                $event->order->created_at
            );
        }
    }
}
php
// Dispatching the event
OrderPlaced::dispatch($order);

// Or using the event helper
event(new OrderPlaced($order));

Los listeners que implementan ShouldQueue se ejecutan de manera asíncrona, evitando que las operaciones lentas bloqueen la respuesta HTTP.

Puntos Clave para Desarrolladores Laravel

  • Las clases de servicio extraen la lógica de negocio de los controladores, mejorando la testeabilidad y permitiendo la reutilización a través de contextos HTTP, CLI y de cola
  • El patrón Repository aporta valor cuando las aplicaciones necesitan caché, abstracción de fuente de datos o encapsulación de consultas complejas, pero crea sobrecarga para CRUD simple
  • Los filtros de Telescope reducen el ruido en producción al registrar únicamente excepciones, consultas lentas y operaciones fallidas
  • El Service Container gestiona la inyección de dependencias automáticamente a través de type hints, con bindings explícitos para interfaces y configuraciones complejas
  • Los problemas N+1 desaparecen con eager loading mediante with(), y Model::preventLazyLoading() detecta eager loads faltantes durante el desarrollo
  • Las clases Action manejan operaciones de propósito único invocables desde controladores, comandos, tests y jobs
  • Los eventos desacoplan "lo que sucedió" de "lo que debería suceder después", con listeners en cola manejando los efectos secundarios de manera asíncrona
  • Las respuestas en entrevistas deben demostrar comprensión de los trade-offs: cuándo los patrones ayudan versus cuándo añaden complejidad innecesaria

¡Empieza a practicar!

Pon a prueba tu conocimiento con nuestros simuladores de entrevista y tests técnicos.

Reto diario

¿Sabrías detectar el bug en Laravel?

Un fragmento real, un bug oculto, un intento al día. Sin cuenta para probar.

Anthony Fillion-Maillet

Escrito por

Anthony Fillion-Maillet

Fundador de SharpSkill

Desarrollador fullstack desde hace más de 10 años. Dirige SharpSkill y responde por todo lo que se publica aquí.

Actualizado el 25 de agosto de 2026

Etiquetas

#laravel
#php
#debugging
#design-patterns
#interview

Compartir

Artículos relacionados