Skip to content

5.12 Relaciones Uno a Uno en Laravel

5.12.1 Introducción

Hasta ahora, hemos trabajado con modelos independientes, es decir, cada tabla y modelo ha estado aislado del resto. Por ejemplo: una tabla de productos, una tabla de usuarios o una tabla de notas.

Sin embargo, en una aplicación real es habitual que las tablas estén relacionadas entre sí. Laravel ofrece un sistema muy potente y sencillo para definir estas relaciones directamente desde los modelos.

Los tres tipos principales de relaciones son:

Tipo de Relación Descripción
Uno a Uno Un usuario tiene un solo teléfono
Uno a Muchos Un usuario tiene muchas notas
Muchos a Muchos Un usuario puede tener muchos roles y cada rol muchos usuarios

Importante

En este tema nos centraremos en el primer tipo de relación, la relación Uno a Uno.

Diagrama Visual

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

En este caso:

  • Cada usuario tiene un teléfono asociado.
  • Cada teléfono pertenece a un solo usuario.

5.12.2 Preparar Modelos y Migraciones

5.12.2.1 Crear modelos y migraciones

Vamos a preparar el modelo de Phone y su migración. El modelo User (viene con Laravel) y la migración también. Podemos reutilizar estos ficheros modificando los campos que no se necesitan.

php artisan make:model Phone -m

Nota

En las relaciones uno a uno la relación puede ir en cualquiera de los dos modelos. En este caso la relación irá en el modelo Phone.

Ahora vamos a modificar la migración de usuarios eliminando algunos de sus campos. En la migración de users:

Migración users

1
2
3
4
5
6
7
Schema::create('users', function (Blueprint $table) {
    $table->id();
    $table->string('name');
    $table->string('email')->unique();
    $table->string('password');
    $table->timestamps();
});

Ahora vamos a crear la tabla de teléfonos con su migración. El teléfono tendrá un prefijo y un número. El prefijo será el mismo para todos los teléfonos, por lo que no es necesario guardarlo en la base de datos. En este caso lo guardaremos como un campo de la tabla. Como hemos anticipado la relación irá en el modelo Phone, por lo que el campo user_id será la clave foránea que relaciona ambos modelos.

El campo user_id será el que relaciona ambos modelos. En la migración de phones:

En la migración de phones:

Migración phones

1
2
3
4
5
6
7
8
Schema::create('phones', function (Blueprint $table) {
    $table->id();
    $table->integer('prefix')->default(34);
    $table->unsignedBigInteger('number')->unique();
    $table->unsignedBigInteger('user_id')->nullable();
    $table->foreign('user_id')->references('id')->on('users');
    $table->timestamps();
});
  • foreignId('user_id') define la clave foránea que enlaza con users.id.

Ejecutamos las migraciones, en caso de que ya existan las tablas, primero eliminamos las tablas:

php artisan migrate:reset
php artisan migrate

5.12.3 Definir la Relación en los Modelos

  • hasOne: Se usa en el modelo que posee la relación.
  • belongsTo: Se usa en el modelo que pertenece a la relación.

En nuestro caso user_id está definido en phones, por lo que:

  • User tiene un teléfono ➞ hasOne
  • Phone pertenece a un usuario ➞ belongsTo

Vamos a crear en los modelos las funciones que definen las relaciones. Cuidado!! hay que crear las funciones en los modelos correctos. HasOne en el modelo User y BelongsTo en el modelo Phone.

En User.php:

Relación en el modelo User

1
2
3
4
public function phone(): HasOne
{
    return $this->hasOne(Phone::class);
}

En Phone.php:

Relación en el modelo Phone

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

5.12.4 Crear Datos con Seeder

Ahora vamos a crear datos de ejemplo, como solo vamos a crear unos cuantos usuarios y sus teléfonos no implementaremos factories para crear los datos. Simplemente crearemos un seeder que cree los datos.

Seeder:

php artisan make:seeder UserSeeder

UserSeeder

<?php
namespace Database\Seeders;

use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Support\Facades\Hash;
use Illuminate\Database\Seeder;
use App\Models\User;

use function Laravel\Prompts\password;

class UserSeeder extends Seeder
{
    /**
    * Run the database seeds.
    */
    public function run(): void
    {
        User::create([
            'name' => 'Admin',
            'email' => 'admin@example.com',
            'password' => Hash::make('password'),
        ]);

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

Hemos creado 4 usuarios, de los cuales 3 tienen teléfono y uno no. Ahora vamos a crear los teléfonos. Para ello creamos un nuevo seeder:

php artisan make:seeder PhoneSeeder

PhoneSeeder

<?php
namespace Database\Seeders;

use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
use App\Models\Phone;
use App\Models\User;

class PhoneSeeder extends Seeder
{
    /**
    * Run the database seeds.
    */
    public function run(): void
    {
        Phone::create([
            'prefix' => 34,
            'number' => 123456789,
            'user_id' => User::where('name', 'Admin')->first()->id,
        ]);

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

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

En este caso hemos creado 3 teléfonos, uno para cada usuario. El usuario NoPhone no tiene teléfono, como ya avanzamos antes.

Ahora vamos a registrar los seeders en el DatabaseSeeder para que se ejecuten al ejecutar el comando de migración.

1
2
3
4
5
public function run(): void
    {
        $this->call(UserSeeder::class);
        $this->call(PhoneSeeder::class);
    }

Y ya por último vamos a ejecutar las migraciones y los seeders. Por si ya teníamos datos en la base de datos, primero eliminamos las tablas fresh y luego ejecutamos las migraciones y los seeders.

Ejecutar:

php artisan migrate:fresh --seed

5.12.5 Mostrar Datos Relacionados en Vista (index)

Creamos controlador:

php artisan make:controller UserController

Vamos a comenzar por la ruta users.index y el controlador UserController@index. En este caso vamos a listar los usuarios y sus teléfonos. Para ello vamos a cargar los usuarios y sus teléfonos en el controlador y luego los pasamos a la vista.

En el archivo de rutas web.php, añadimos la ruta:

Route::get('/users', [UserController::class, 'index'])->name('users.index');

Y ahora en el controlador definimos el método index que cargará los usuarios y sus teléfonos. En este caso vamos a cargar todos los usuarios y sus teléfonos.

Eager Loading

En Laravel, para cargar relaciones de forma eficiente, utilizamos el método with(). Esto nos permite cargar la relación phone al mismo tiempo que cargamos los usuarios. Si no lo hacemos, Laravel hará una consulta a la base de datos por cada usuario para cargar su teléfono, lo que puede ser muy ineficiente si tenemos muchos usuarios. Este método se llama Eager Loading.

Controlador:

UserController\@index

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

Ahora vamos a crear la vista users.index que mostrará los usuarios y sus teléfonos. En este caso vamos a mostrar el nombre y el teléfono de cada usuario.

Vista: resources/views/users/index.blade.php

Vista con teléfono

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Usuarios y teléfonos</title>
</head>
<body>
    <h1>Usuarios</h1>
    @forelse($users as $user)
        <div>
            <h2>{{ $user->name }}</h2>
            <p>Email: {{ $user->email }}</p>
            <p>Prefijo: {{ $user->phone->prefix ?? 'No disponible' }}</p>
            <p>Teléfono: {{ $user->phone->number ?? 'No disponible' }}</p>
        </div>
    @empty
        <p>No hay usuarios disponibles.</p>
    @endforelse
</body>
</html>

En este caso deberíamos haber creado primero un layout para la vista, pero como es un ejemplo sencillo no lo hemos hecho. En este caso hemos utilizado el operador ?? para mostrar un mensaje si el usuario no tiene teléfono.

Ahora vamos a comprobar que la vista funciona correctamente. Para ello vamos la siguiente URL:

http://localhost:8000/users

Uso de belongsTo

Ahora vamos a utiulizar la relación en sentido contrario. Ahora teníamos un usuario y con su relación phone de tipo hasOne accedíamos al teléfono. Ahora vamos a hacer lo contrario, desde el teléfono acceder al usuario.

Para ello vamos a crear una nueva ruta:

Route::get('/users/phone/{phone}', [UserController::class, 'showPhone'])->name('users.showPhone');

En este caso vamos a crear un nuevo método en el controlador UserController que mostrará el usuario al que pertenece el teléfono. Comenzamos por el controlador:

UserController\@showPhone

function showPhone($phone)
{
    // Find the user by phone number
    $phone = Phone::where('number', $phone)->first();
    if (!$phone) {
        return redirect()->route('users.index')->with('error', 'Phone number not found.');
    } else {
        $user = $phone->user;
        if (!$user) {
            return redirect()->route('users.index')->with('error', 'User not found.');
        } else {
            // Return the user details view
            return view('users.show', compact('user'));
        }
    }
}

El siguiente paso es crear la vista users.show que mostrará el usuario al que pertenece el teléfono. En este caso vamos a mostrar el nombre y el teléfono de cada usuario. Vista: resources/views/users/show.blade.php

Vista con teléfono

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Usuario</title>
</head>
<body>
    <h1>Usuario</h1>
    <div>
        <h2>{{ $user->name }}</h2>
        <p>Email: {{ $user->email }}</p>
        <p>Prefijo: {{ $user->phone->prefix ?? 'No disponible' }}</p>
        <p>Teléfono: {{ $user->phone->number ?? 'No disponible' }}</p>
    </div>
</body>
</html>

En este caso deberíamos haber creado primero un layout para la vista, pero como es un ejemplo sencillo no lo hemos hecho. En este caso hemos utilizado el operador ?? para mostrar un mensaje si el usuario no tiene teléfono.

Ahora vamos a comprobar que la vista funciona correctamente. Para ello vamos la siguiente URL:

http://localhost:8000/users/phone/123456789

5.12.6 API para Usuarios con Teléfono

Comenzamos por dar soporte a la API. Para ello recordemos que en las nuevas versiones de LAravel hay que instalar el soporte para API ya que por defecto no viene instalado. Para ello ejecutamos el siguiente comando:

php artisan install:api

Una vez instalado el soporte para API vamos a modificar el prefijo para las rutas de la API. Para ello y modifiamos el fichero; bootstrap/app.php y añadimos el siguiente código:

api: __DIR__.'/../routes/api.php',
apiPrefix: '/api',

Ahora vamos a añadir la ruta para la API. En este caso vamos a crear una nueva ruta para la API que devolverá los usuarios y sus teléfonos en formato JSON.

Route::get('/users', [UserController::class, 'index'])->name('api.users.index');

Ahora creamos un nuevo controlador UserController para la api. Luego vamos a crear un nuevo método en el controlador UserController que devolverá los usuarios y sus teléfonos en formato JSON.

php artisan make:controller Api/UserController --api

Y el recurso (resource) que nos ayudará a formatear la respuesta de la API:

php artisan make:resource UserResource
En este caso vamos a crear un nuevo método en el controlador UserController que devolverá los usuarios y sus teléfonos en formato JSON.

UserController\@index

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

Ahora vamos a implementar el recurso UserResource que nos ayudará a formatear la respuesta de la API. En este caso vamos a crear un nuevo método en el recurso UserResource que devolverá los usuarios y sus teléfonos en formato JSON.

UserResource

public function toArray($request)
{
    return [
        'name' => $this->name,
        'email' => $this->email,
        'telefono' => [
            'prefix' => $this->phone->prefix ?? null,
            'number' => $this->phone->number ?? null,
        ]
    ];
}

Ahora utilizando el recurso UserResource podemos formatear la respuesta de la API de forma sencilla. En este caso hemos utilizado el operador ?? para mostrar un mensaje si el usuario no tiene teléfono.

Vamos a comprobar que la API funciona correctamente. Para ello vamos la siguiente URL:

http://localhost:8000/api/users

Nos tiene que devolver un JSON con los usuarios y sus teléfonos:

JSON devuelto por la API
[[
    {
        "name": "Admin",
        "email": "admin@example.com",
        "telefono": {
            "prefix": 34,
            "number": 1234567890
        }
    },
    {
        "name": "User",
        "email": "user@example.com",
        "telefono": {
            "prefix": 34,
            "number": 987654321
        }
    },
    {
        "name": "Backup",
        "email": "backup@example.com",
        "telefono": {
            "prefix": 34,
            "number": 1122334455
        }
    },
    {
        "name": "NoPhone",
        "email": "nophone@example.com",
        "telefono": {
            "prefix": null,
            "number": null
        }
    }
]]

5.12.7 Nueva ruta para obtener datos a partir de un teléfono

Ahora vamos a crear una nueva ruta para la API que devolverá el usuario al que pertenece el teléfono en formato JSON.

Route::get('/users/phone/{phone}', [UserController::class, 'show'])->name('api.users.show');

Ahora vamos a crear un nuevo método en el controlador UserController que devolverá el usuario al que pertenece el teléfono en formato JSON.

UserController\@show

public function show($phoneNumber): JsonResponse
{
    $phone = Phone::where('number', $phoneNumber)->first();
    if (!$phone) {
        return response()->json([
            'status' => 'error',
            'message' => 'Phone number not found.'], 404);
    } else {
        $user = $phone->user;
        if (!$user) {
            return response()->json([
                'status' => 'error',
                'message' => 'User not found.'], 404);
        } else {
            return response()->json([
                'status' => 'success',
                'message' => 'User found.',
                'data' => new UserResource($user)
                ], 200);
        }
    }
}

Ahora devolvemos el cóigo 404 si no se encuentra el teléfono y el código 200 si se encuentra.

Para comprobar que la API funciona correctamente, vamos a probar la siguiente URL:

http://localhost:8000/api/users/phone/123456789

Un ejemplo de la respuesta con error sería:

JSON devuelto por la API
1
2
3
4
{
    "status": "error",
    "message": "User found."
}
y en caso de encontrar el teléfono y al propietario:

JSON devuelto por la API
{
    "status": "success",
    "message": "User found.",
    "data": [
        "name": "Admin",
        "email": "admin@empresa.es",
        "telefono": [
            "prefix": 34,
            "number": 123456789
        ]
    ]
}

5.12.8 Comprobaciones Finales

  • Vista carga los usuarios y sus teléfonos correctamente.
  • API devuelve los datos anidados como JSON.

5.12.9 Ejercicio

  1. Crea un nuevo modelo Address que tenga una relación uno a uno con el modelo User.
  2. Crea la migración y el seeder para el modelo Address.
  3. Crea la relación en los modelos User y Address.
  4. Crea la vista para mostrar los usuarios y sus direcciones.
  5. Crea la API para mostrar los usuarios y sus direcciones.
  6. Comprueba que todo funciona correctamente.