Capítulo 3 de 9

Tutorial Laravel 13 #3: la estructura MVC con Blade

verificado el 7 septiembre 2026 · 6 min

Respuesta rápida

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.

Al final de este capítulo, sabrás crear un controlador, hacer que devuelva una vista Blade y construir una plantilla de página reutilizable.

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:

bash
php artisan make:controller IndexController
code
INFO  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:

app/Http/Controllers/IndexController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class IndexController extends Controller
{
    //
}
Las barras invertidas cuentan

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:

app/Http/Controllers/IndexController.php
<?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:

routes/web.php
<?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

app/Http/Controllers/IndexController.php
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.

bash
php artisan make:controller IndexController --invokable --force

El archivo generado contiene un método __invoke(Request $request) vacío, precedido de un comentario. Sustitúyalo por este:

app/Http/Controllers/IndexController.php
<?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.

routes/web.php
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:

app/Http/Controllers/IndexController.php
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

markup
<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:

markup
<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

markup
@if (count($articles) === 1)
    Un seul article.
@elseif (count($articles) > 1)
    {{ count($articles) }} articles.
@else
    Aucun article.
@endif

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

markup
@forelse ($articles as $article)
    <h2>{{ $article }}</h2>
@empty
    <p>Aucun article pour le moment.</p>
@endforelse

Los bucles

@foreach es el más habitual, pero Blade recoge todos los bucles de PHP:

markup
@for ($i = 1; $i <= 3; $i++)
    Passage numéro {{ $i }}
@endfor

@while ($compteur > 0)
    Il reste {{ $compteur-- }}
@endwhile

Dentro de un @foreach, Blade ofrece una variable $loop que evita llevar la cuenta a mano:

markup
@foreach ($articles as $article)
    {{ $loop->iteration }} sur {{ $loop->count }} :
    {{ $article }}
    @if ($loop->first) (le premier) @endif
    @if ($loop->last) (le dernier) @endif
@endforeach

Con una lista de dos artículos, esta plantilla produce:

code
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

markup
@switch($niveau)
    @case('debutant')
        Pour débuter
        @break
    @case('avance')
        Pour aller plus loin
        @break
    @default
        Niveau non précisé
@endswitch

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

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:

resources/views/accueil.blade.php
@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
@endsection

Para 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:

markup
@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:

markup
<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é:

bash
php artisan view:clear

El capítulo siguiente instala el panel de administración con el que redactarás los artículos.

Errores frecuentes

Las barras invertidas del espacio de nombres desaparecen Una línea que se lee «namespace AppHttpControllers» en lugar de «namespace App\Http\Controllers» no apunta al espacio de nombres correcto, y el autocargado de Composer no encuentra la clase. Es el defecto más frecuente en el código copiado de la web.
{{ }} muestra el HTML como texto Es lo previsto: las llaves dobles escapan el contenido. Para mostrar HTML a propósito, usa {!! !!}, y únicamente sobre una fuente de confianza.
@break olvidado en un @switch Como en PHP, sin @break la ejecución sigue en el caso siguiente.
Un cambio en una vista no aparece Vacía la caché de vistas compiladas con php artisan view:clear.
Newsletter

Las nuevas pruebas, tutoriales y proyectos, por correo.

Pruebas reproducibles, código versionado, resultados fechados. Nunca spam.