Tutorial Laravel 13 #3: la estructura MVC con Blade
verificado el 7 septiembre 2026 · 6 min
MVC separa los datos (modelo), la presentación (vista) y la lógica (controlador). Crea un controlador con php artisan make:controller, devuelve una vista con view('nom', [...]) y muestra los valores en Blade con {{ $variable }}, que escapa el HTML automáticamente.
MVC reparte una aplicación en tres responsabilidades: el modelo describe los datos, la vista los muestra y el controlador hace de enlace entre ambos. Este capítulo construye un controlador y unas vistas Blade. Los modelos llegan en el capítulo 6, cuando haya una base de datos que consultar.
Los controladores
Un controlador recibe una petición, reúne lo que la página necesita y devuelve una vista. Crea uno:
php artisan make:controller IndexControllerINFO Controller [app/Http/Controllers/IndexController.php] created successfully.El archivo generado está casi vacío. Laravel 13 importa Request de oficio, aunque esa clase todavía no se use:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class IndexController extends Controller
{
//
}App\Http\Controllers se escribe con barras invertidas, no con barras normales, y nunca pegado en una sola palabra. Una línea como namespace AppHttpControllers; no apunta al espacio de nombres correcto y el autocargado de Composer no encontrará la clase. Si copias código de un sitio que se ha comido las barras invertidas, es lo primero que hay que comprobar.
Añade un método que devuelva texto:
<?php
namespace App\Http\Controllers;
class IndexController extends Controller
{
public function index(): string
{
return 'Ceci vient de IndexController';
}
}Y después apunta hacia él la ruta de inicio:
<?php
use App\Http\Controllers\IndexController;
use Illuminate\Support\Facades\Route;
Route::get('/', [IndexController::class, 'index']);Recarga http://127.0.0.1:8000: la frase aparece. La ruta ya no contiene lógica, solo indica dónde encontrarla.
Devolver una vista en lugar de una cadena
use Illuminate\View\View;
public function index(): View
{
return view('welcome');
}La función view() recibe el nombre del archivo sin la extensión: welcome corresponde a resources/views/welcome.blade.php. El punto hace de separador de carpetas: view('partials.sidebar') carga resources/views/partials/sidebar.blade.php.
Los controladores de acción única
Cuando un controlador solo tiene una cosa que hacer, el método __invoke evita tener que nombrarla. El archivo ya existe desde el principio del capítulo: --force lo sobrescribe.
php artisan make:controller IndexController --invokable --forceEl archivo generado contiene un método __invoke(Request $request) vacío, precedido de un comentario. Sustitúyalo por este:
<?php
namespace App\Http\Controllers;
use Illuminate\View\View;
class IndexController extends Controller
{
public function __invoke(): View
{
return view('welcome');
}
}La ruta se simplifica otro tanto: ya no hace falta indicar el método.
Route::get('/', IndexController::class);Este tutorial usa esta forma para la portada, las categorías y las etiquetas, y la forma clásica para los artículos, que tendrán dos métodos.
Pasar datos a la vista
El segundo argumento de view() es un array. Cada clave se convierte en una variable dentro de la plantilla:
public function __invoke(): View
{
return view('accueil', [
'nom' => 'Damien',
'articles' => ['Premier article', 'Deuxième article'],
]);
}En la vista ya dispones de $nom y $articles.
Las vistas Blade
Blade es el motor de plantillas de Laravel. Un archivo .blade.php es HTML al que se añaden unas cuantas directivas. Se compila a PHP una sola vez y luego queda en caché.
Mostrar un valor
<p>Bonjour, {{ $nom }}.</p>Las llaves dobles escapan el contenido automáticamente: si $nom contiene <script>, el navegador mostrará el texto en lugar de ejecutarlo. Es la protección por defecto frente a las inyecciones de código.
Cuando quieras mostrar HTML a propósito, por ejemplo el contenido de un artículo redactado desde el panel de administración, usa la forma sin escapar:
<div>{!! $post->content !!}</div>Empléala solo con valores cuyo origen controlas. Sobre lo que escribe un visitante, es un agujero de seguridad.
Condiciones y bucles
@if (count($articles) === 1)
Un seul article.
@elseif (count($articles) > 1)
{{ count($articles) }} articles.
@else
Aucun article.
@endifEl bucle @foreach tiene una variante muy práctica, @forelse, que resuelve el caso de la lista vacía sin necesidad de una condición aparte:
@forelse ($articles as $article)
<h2>{{ $article }}</h2>
@empty
<p>Aucun article pour le moment.</p>
@endforelseLos bucles
@foreach es el más habitual, pero Blade recoge todos los bucles de PHP:
@for ($i = 1; $i <= 3; $i++)
Passage numéro {{ $i }}
@endfor
@while ($compteur > 0)
Il reste {{ $compteur-- }}
@endwhileDentro de un @foreach, Blade ofrece una variable $loop que evita llevar la cuenta a mano:
@foreach ($articles as $article)
{{ $loop->iteration }} sur {{ $loop->count }} :
{{ $article }}
@if ($loop->first) (le premier) @endif
@if ($loop->last) (le dernier) @endif
@endforeachCon una lista de dos artículos, esta plantilla produce:
1 sur 2 : A (le premier)
2 sur 2 : B (le dernier)$loop expone además index (empieza en cero), remaining, even, odd y parent, para subir al bucle que lo engloba.
La elección múltiple
@switch($niveau)
@case('debutant')
Pour débuter
@break
@case('avance')
Pour aller plus loin
@break
@default
Niveau non précisé
@endswitchEl @break es obligatorio al final de cada @case, igual que en PHP: sin él, la ejecución sigue en el caso siguiente.
La herencia de plantillas
El interés principal de Blade es escribir el esqueleto del sitio una sola vez. Crea resources/views/layouts/app.blade.php:
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>@yield('title', config('app.name'))</title>
</head>
<body>
<header>
<a href="https://www.gekkode.com/es/">{{ config('app.name') }}</a>
</header>
<main>
@yield('content')
</main>
<footer>
<p>{{ config('app.name') }}</p>
</footer>
</body>
</html>@yield('content') reserva un hueco. El segundo argumento de @yield es el valor por defecto cuando la sección no está definida.
Una página concreta extiende esa plantilla y rellena los huecos:
@extends('layouts.app')
@section('title', 'Accueil')
@section('content')
<h1>Bonjour, {{ $nom }}.</h1>
@forelse ($articles as $article)
<h2>{{ $article }}</h2>
@empty
<p>Aucun article pour le moment.</p>
@endforelse
@endsectionPara una sección corta, @section('title', 'Accueil') cabe en una línea. Para un bloque, se abre con @section y se cierra con @endsection.
Incluir un fragmento reutilizable
@include inserta otra vista en el punto que quieras, con acceso a las mismas variables:
@include('partials.sidebar')Este tutorial lo usa para la barra lateral y la lista de artículos, reutilizadas en cinco páginas.
¿Componentes o herencia?
Laravel propone un segundo enfoque, los componentes Blade, con una sintaxis de etiquetas:
<x-layout title="Accueil">
<h1>Bonjour</h1>
</x-layout>Los dos mecanismos conviven en Laravel 13 y ninguno está obsoleto. Los componentes se imponen cuando las piezas reciben parámetros y se combinan entre sí, @extends resulta más directo para un simple esqueleto de página. Este tutorial se queda con @extends, más legible para empezar. La documentación de Blade detalla los componentes.
Una nota sobre la caché de vistas
Blade compila cada vista a PHP en storage/framework/views. En desarrollo, la recompilación es automática en cuanto cambia el archivo. Si una modificación no aparece, vacía la caché:
php artisan view:clearEl capítulo siguiente instala el panel de administración con el que redactarás los artículos.