leandrocaplan's avatar

Avoiding Redundant Data in an Eloquent Model Instance

How’s it going? Greetings from Argentina!

I’d like to share an issue I encountered while working on a Laravel project of mine, intended for property rentals. In the database, both the property owner’s and the tenant’s contact information are stored in the same table.

The MySQL 'datos_contacto' (contact_data) table looks like this:

id  tipo_usuario  email                   horario_atencion    telefono     telefono_alt    celular      whatsapp          alquiler_id
---------------------------------------------------------------------------------------------------------------------------------------
42  contratador   [email protected]       24x7            11-3352-6425    4235-5532    11-3352-6425  (54) 11-3352-6425      31
43  publicador    [email protected]   24x7            11-3546-5888    4701-1108    11-3546-5888  (54) 11-3546-5888      31

Now, in the Eloquent model that represents a rental property record, I’ve added an attribute that displays the contact information for both parties.

Currently, although the model behaves as expected by the application, when inspecting a rental instance, the datosContacto attribute is displayed like this:

In English it would be like:

Even though it works, it includes unwanted redundant information, and perhaps the model code is not the most optimal.

Ideally, I would like it to look like this:

datosContacto: App\Models\DatosContacto {#6726
  +contratador: App\Models\DatosContacto {#7043
    id: 42,
    tipo_usuario: "contratador",
    email: "[email protected]",
    horario_atencion: "24x7",
    telefono: "11-3352-6425",
    telefono_alt: "4235-5532",
    celular: "11-3352-6425",
    whatsapp: "(54) 11-3352-6425",
    alquiler_id: 31,
  },
  +publicador: App\Models\DatosContacto {#6875
    id: 43,
    tipo_usuario: "publicador",
    email: "[email protected]",
    horario_atencion: "24x7",
    telefono: "11-3546-5888",
    telefono_alt: "4701-1108",
    celular: "11-3546-5888",
    whatsapp: "(54) 11-3546-5888",
    alquiler_id: 31,
  },
},

Here's the code for my DatosContacto.php model:

Here's the code for my Alquiler.php model:

Do you have any suggestions to improve or optimize this code?

While it works, I’m not sure it’s the best approach to achieve this. If there's any more information needed, please tell me and i'll be sharing fast as posible.

Thanks, and best regards!

Leandro

P.S.: Since I'm a pretty newbie user, I'm still unable to upload screenshot, which would help a lot to see the issue more clearly

0 likes
5 replies
Glukinho's avatar

Why DatosContacto::contratador and DatosContacto::publicador are attributes and have the same type DatosContacto?

I believe they instead should be relations with types App\Models\Contratador and App\Models\Publicador respectively, bound with belongsToMany relation, and App\Models\DatosContacto is a pivot model between them: https://laravel.com/docs/12.x/eloquent-relationships#defining-custom-intermediate-table-models

Actually you are recreating Eloquent relations system on your own, based on attributes. This doesn't seem right to me.

leandrocaplan's avatar

The situation is as follows:

When both parties—the publisher (property owner) and the renter—reach an agreement, the application stores the contact information provided by both sides so they can communicate with each other.

This contact information is stored in a single table. The tipo_usuario field indicates which record belongs to the publisher (property owner), and which one belongs to the renter (tenant). For both parties, the system stores their email address, their available hours, and their phone numbers, so that they can get in touch with each other.

In other words, there is no intermediate or pivot table here; the same table stores the contact information for both the renter and the publisher of a given rental agreement.

I could create a model for the Renter and another one for the Publisher, but both would still be querying the same table. The Renter model would fetch only those records from the datos_contacto table where tipo_usuario equals "contratador", and the same logic would apply to the Publisher.

In the datosContacto attribute of a rental, I want to have the information of both parties, nested under the datosContacto property.

This way, I could do:

$alquiler->datosContacto->publicador;

And also:

$alquiler->datosContacto->contratador;

If I created a Renter model and a Publisher model, both would need to fetch records from the same datos_contacto table, but each of them would need to filter based on the role that the user takes on in that transaction.

The attribute:

$alquiler->datosContacto;

Should return null if that rental is not currently active or rented.

The stored contact information does not always match the user’s profile data in the application, because each party may choose different contact details according to their preferences for that particular agreement.

That is, the contact fields are the same for both the publisher and the renter, so it wouldn’t make sense to define two separate tables.

The implementation I have right now works and produces the expected results, but I believe there might be a more ideal way to structure the code.

Best regards! Leandro

leandrocaplan's avatar

After a while trying some stuff, this approach worked for me:

App\Models\DatosContacto.php:

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use App\Models\Alquiler;

class DatosContacto extends Model {
  use HasFactory;

  protected $guarded = [];
  
  public $timestamps = false;
  
  protected $table = 'datos_contacto';
    
  protected $hidden = ['tipo_usuario','alquiler_id'];

  public function alquiler() {
    return $this->belongsTo(Alquiler::class, 'alquiler_id');
  }
}

App\Models\ContratadorContacto.php:

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use App\Models\Alquiler;
use App\Models\DatosContacto;

class ContratadorContacto extends DatosContacto {
}

App\Models\PublicadorContacto.php:

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use App\Models\Alquiler;
use App\Models\DatosContacto;

class PublicadorContacto extends DatosContacto {
}

App\Models\Alquiler.php:

My tinker result, when I do Alquiler::find(37) is like this:

+datosContacto: {#6836
      +"publicadorContacto": App\Models\PublicadorContacto {#6713
        id: 55,
        #tipo_usuario: "publicador",
        email: "[email protected]",
        horario_atencion: "24x7",
        telefono: "11-3546-5888",
        telefono_alt: "4701-1108",
        celular: "11-3546-5888",
        whatsapp: "(54) 11-3546-5888",
        #alquiler_id: 37,
      },
      +"contratadorContacto": App\Models\ContratadorContacto {#6755
        id: 54,
        #tipo_usuario: "contratador",
        email: "[email protected]",
        horario_atencion: "24x7",
        telefono: "11-3233-9668",
        telefono_alt: "5325-1372",
        celular: "11-3233-9668",
        whatsapp: "(54) 11-3233-9668",
        #alquiler_id: 37,
      },
    },

Thank a lot for your suggestions! Best regards

Leandro

Snapey's avatar

Typically this would be a polymorphic relationship.

1 like
leandrocaplan's avatar

@Glukinho

It’s true that in the code I first posted here, the approach wasn’t ideal for what I was trying to accomplish. Even though it did work for the intended purpose, it ended up that way after a long period of trial and error. So now, I’m trying to implement code that achieves the same result but using better practices, something cleaner, easier to follow, and easier to maintain. For now, with the updated code I posted later in this thread, I’m getting the same result but with a more appropriate and tidier approach.

@Snapey

Using a polymorphic relationship in this case seems like a very interesting idea. In my application, when a transaction between the publisher and the tenant of a rental property is finalized, the contact details of both parties are stored in a single table so they can communicate with each other through different channels. In a rental offer that hasn’t been finalized yet, the 'datosContacto' attribute should simply return 'null'.

There’s a possible naming ambiguity here, because if I do:

$alquiler->publicador   // Returns the user instance of the publisher of the rental
$alquiler->contratador  // Returns the user instance of the tenant of the rental

But if I do:

$alquiler->datosContacto->publicador    // Returns an array with the contact data of the publisher
$alquiler->datosContacto->contratador   // Returns an array with the tenant’s contact data

In the latest version, I changed the nested attribute names under 'datosContacto' to 'publicadorContacto' and 'contratadorContacto'.

I also kept this relationship in the 'Alquiler.php' model:

public function datosContacto() {
    return $this->hasMany(DatosContacto::class, 'alquiler_id');
}

This way, in the corresponding LiveWire component, the following code still works:

public function baja(Alquiler $alquiler){
    error_log("Entered baja");
    $alquiler->disponible = true;
    $alquiler->contratador_id = null;
    $alquiler->datosContacto()->delete();
    $alquiler->save();
    $this->mount();
    return $this->render();
}

What I have so far works, but I’m still thinking of better ways to accomplish the same thing and optimize my code.

I’ll try to figure out how to use polymorphic relationships for this, so any hint or suggestion is welcome!

I’d also like to ask how to upload images to a post, since I’m not sure how to do it.

Thanks and best regards!

Leandro

Please or to participate in this conversation.