6.7 Relaciones Uno a Muchos en Laravel

6.7.1 Introducción

En el tema anterior aprendimos a crear una relación uno a uno entre un modelo User y un modelo Phone. Cada usuario tenía un único teléfono asociado.

Sin embargo, en muchos casos reales esto no es suficiente. Por ejemplo:

  • Un usuario puede tener varios teléfonos: personal, trabajo, casa.
  • Un cliente puede tener varios pedidos.
  • Un autor puede escribir muchos artículos.

Para cubrir estos casos, Laravel permite definir relaciones uno a muchos.

Comparativa Visual

erDiagram
  User ||--o{ Phone : has
  User {
    int id
    string name
  }
  Phone {
    int id
    int user_id
    string prefix
    string number
  }

Diferencia clave

En una relación uno a muchos:

  • Un usuario puede tener muchos teléfonos.
  • Cada teléfono pertenece a un solo usuario.

Este tema se basa en el código del tema anterior. Iremos modificando progresivamente el proyecto.


6.7.2 Actualizar los Modelos

Antes de comenzar, vamos a eliminar los datos que creamos en la base de datos para poder modificar los seedersy volver a ejecutarlos.

php artisan migrate:reset 

6.7.2.1 En el modelo User

Sustituimos hasOne por hasMany:

Relación actualizada en User.php

1
2
3
4
public function phones()
{
    return $this->hasMany(Phone::class);
}

6.7.2.2 En el modelo Phone

belongsTo se mantiene igual:

Modelo Phone.php

1
2
3
4
public function user()
{
    return $this->belongsTo(User::class);
}

6.7.3 Modificar Seeder

En UserSeeder.php, En este caso vamos a crear dos usuarios adminy NoPhone:

Seeder con varios teléfonos

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
public function run(): void
{
    User::create([
        'name' => 'Admin',
        'email' => 'admin@example.com',
        'password' => Hash::make('password'),
    ]);

    User::create([
        'name' => 'NoPhone',
        'email' => 'nophone@example.com',
        'password' => Hash::make('password'),
    ]);
}

Ahora vamos a modificar el PhoneSeeder.php para crear varios teléfonos,los telefonos selos asginamos todas a admin para utilizar la realción uno a muchos:

Seeder con varios teléfonos

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
public function run(): void
{
    Phone::create([
        'prefix' => '34',
        'number' => '1234567890',
        'user_id' => User::where('name', 'Admin')->first()->id,
    ]);

    Phone::create([
        'prefix' => '34',
        'number' => '0987654321',
        'user_id' =>  User::where('name', 'Admin')->first()->id,
    ]);

    Phone::create([
        'prefix' => '34',
        'number' => '1122334455',
        'user_id' =>  User::where('name', 'Admin')->first()->id,
    ]);
}

Ahora volvemos a generar las tablas con las migraciones y los datos de prueba:

php artisan migrate:fresh --seed

Perfecto, ya lo tenesmos todo lista para poder utilizar las relaciones uno a muchos. Para ello vamos a modificar los controladores y las vistas.


6.7.4 Vista Web

6.7.4.1 Controlador

En UserController@index, cargamos los teléfonos:

A simple vista, el método index no cambia mucho. La diferencia es que ahora estamos cargando una colección de teléfonos para cada usuario. Ahora usuuario me devuelve un objeto hasMany y no un objeto hasOne.

Controlador web UserController

1
2
3
4
5
public function index()
{
    $users = User::with('phones')->get();
    return view('users.index', compact('users'));
}

6.7.4.2 Vista

Modificamos las dos vistas, la que muestra todos los usuarios con sus teléfonos y el detalle de un usuario.

Vista con teléfonos en bucle

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
<body>
    <h1>Usuarios</h1>
    @forelse($users as $user)
        <div>
            <h2>{{ $user->name }}</h2>
            <p>Email: {{ $user->email }}</p>
            @forelse($user->phones as $phone)
                <h3>Teléfono: {{ $loop->index }}   </h3>
                <p>Teléfono: {{ $phone->number }}</p>
                <p>Prefijo: {{ $phone->prefix }}</p>
            @empty
                <p>No hay teléfonos disponibles.</p>
        </div>
    @empty
        <p>No hay usuarios disponibles.</p>
    @endforelse
</body>

Y la modificación del detalle de un usuario:

Vista detalle de usuario con teléfonos

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
<body>
    <h1>Detalles del Usuario</h1>
    <h2>{{ $user->name }}</h2>
    <p>Email: {{ $user->email }}</p>
    @forelse($user->phones as $phone)
        <h3>Teléfono: {{ $loop->index }}   </h3>
        <p>Teléfono: {{ $phone->number }}</p>
        <p>Prefijo: {{ $phone->prefix }}</p>
    @empty
        <p>No hay teléfonos disponibles.</p>
    @endforelse
</body>

6.7.5 API con Relación Uno a Muchos

6.7.5.1 Controlador API

Modificamos el modelo, ahora será whith('phones') para cargar los teléfonos de cada usuario.

API UserController\@index

1
2
3
4
5
6
public function index(): JsonResponse
{
    return response()->json(UserResource::collection(
        User::with('phones')->get()
    ));
}

6.7.5.2 Resource

También modificamos el UserResource para devolver un array de teléfonos. En este caso, la relación phones devuelve una colección de objetos Phone, por lo que debemos mapearla a un array.

UserResource con array de teléfonos

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
public function toArray($request)
{
    return [
        'name' => $this->name,
        'email' => $this->email,
        'phones' => $this->phones->map(function ($phone) {
            return [
                'prefix' => $phone->prefix,
                'number' => $phone->number
            ];
        })
    ];
}

6.7.6 Comparativa final con Uno a Uno

Elemento Uno a Uno Uno a Muchos
Relación User hasOne(Phone::class) hasMany(Phone::class)
Vista Blade $user->phone $user->phones as $phone
JSON API telefono objeto phones[] array de objetos
Seeder 1 teléfono por user varios teléfonos por user

6.7.7 Ejercicio Final: Varias Direcciones

Objetivo: Modificar el ejercicio del tema anterior para que un usuario pueda tener múltiples direcciones.

Pasos sugeridos:

  1. Verifica que la tabla addresses tiene la clave user_id.

  2. Cambia en el modelo User:

public function addresses() {
    return $this->hasMany(Address::class);
}
  1. En el modelo Address, asegúrate de tener belongsTo(User::class).

  2. En el seeder, genera varias direcciones por usuario.

  3. En la vista, muestra la lista de direcciones para cada usuario.

  4. En el API, actualiza el UserResource para devolver addresses.

Ver solución propuesta
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// Seeder
User::factory()->count(3)->create()->each(function (\$user) {
Address::factory()->count(2)->create(\[
'user\_id' => \$user->id
]);
});

/   / UserResource
'addresses' => $this->addresses->map(fn($a) => [
    'street' => $a->street,
    'city' => $a->city
])