I encountered an issue 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 table looks like this:
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:
contactData: App\Models\ContactData {#6726
#id: 42,
#user_type: "contractor",
#email: "[email protected]",
#support_hours: "24x7",
#phone: "11-3352-6425",
#alt_phone: "4235-5532",
#mobile: "11-3352-6425",
#whatsapp: "(54) 11-3352-6425",
#rental_id: 31,
+contractor: App\Models\ContactData {#7043
id: 42,
user_type: "contractor",
email: "[email protected]",
support_hours: "24x7",
phone: "11-3352-6425",
alt_phone: "4235-5532",
mobile: "11-3352-6425",
whatsapp: "(54) 11-3352-6425",
rental_id: 31,
},
+publisher: App\Models\ContactData {#6875
id: 43,
user_type: "publisher",
email: "[email protected]",
support_hours: "24x7",
phone: "11-3546-5888",
alt_phone: "4701-1108",
mobile: "11-3546-5888",
whatsapp: "(54) 11-3546-5888",
rental_id: 31,
},
},
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:
Here's the code for my DatosContacto.php model:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use App\Models\Alquiler;
use Illuminate\Database\Eloquent\Casts\Attribute;
class DatosContacto extends Model {
use HasFactory;
protected $guarded = [];
public $timestamps = false;
protected $table = 'datos_contacto';
protected $appends = ['contratador', 'publicador'];
protected $hidden = ['id','alquiler','tipo_usuario','email','horario_atencion',
'telefono','telefono_alt','celular','whatsapp','alquiler_id'];
public function alquiler() {
return $this->belongsTo(Alquiler::class, 'alquiler_id');
}
protected function contratador(): Attribute {
return new Attribute(
get: function ($value) {
$contratador = Self::where(['tipo_usuario' => 'contratador', 'alquiler_id' => $this->alquiler->id])->get()->first();
if ($contratador) $contratador->setHidden(['alquiler'])->setAppends([]);
return $contratador;
}
);
}
protected function publicador(): Attribute {
return new Attribute(
get: function ($value) {
$publicador = Self::where(['tipo_usuario' => 'publicador', 'alquiler_id' => $this->alquiler->id])->get()->first();
if ($publicador) $publicador->setHidden(['alquiler'])->setAppends([]);
return $publicador;
}
);
}
}
Here's the code for my Alquiler.php model:
<?php
namespace App\Models;
//(...)
use App\Models\DatosContacto;
class Alquiler extends Model {
use HasFactory;
use HasSpatial;
public function getRouteKeyName() {
return 'slug';
}
//Habilitamos la asignación masiva:
//Podríamos hacerlo mediante $fillable -> protected $fillable = ['barrio','tipo','direccion','descripcion'];
//Pero nos conviene hacerlo mediante $guarded:
protected $guarded = [];
protected $casts = ['disponible' => 'boolean'];
//protected $hidden = ['coordenadas'];
protected $appends = ['reputacion','ambientes'];
protected $with = ['direccion','caracteristicas','fotos','servicios','aviso','datosContacto'];//'ambientes'
protected $table = 'alquileres';
private $obj = null;
public function recibirObj($obj) {
$this->obj = $obj;
return $this;
}
public function resetear() {
$this->obj = null;
return $this;
}
//Relaciones a nivel de modelo
//Asocio el campo 'user_id' (FK de esta tabla) con el campo 'id' (PK) de la tabla 'users'
public function publicador() {
return $this->belongsTo(User::class, 'user_id');
}
//Asocio el campo 'contratador_id' (FK de esta tabla) con el campo 'id' (PK) de la tabla 'users'
public function contratador() {
return $this->belongsTo(User::class, 'contratador_id');
}
public function direccion() {
return $this->hasOne(Direccion::class, 'alquiler_id');
}
public function datosContacto() {
return $this->hasOne(DatosContacto::class, 'alquiler_id');
}
public function multimedia() {
return $this->hasOne(Multimedia::class, 'alquiler_id');
}
public function solicitudes() {
return $this->hasMany(Solicitud::class, 'alquiler_id');
}
public function caracteristicas() {
return $this->hasOne(Caracteristicas::class, 'alquiler_id');
}
public function aviso() {
return $this->hasOne(Aviso::class, 'alquiler_id');
}
public function servicios() {
return $this->belongsToMany(Servicio::class);
}
public function fotos() {
return $this->hasMany(Foto::class, 'alquiler_id');
}
public function calificaciones() {
return $this->morphMany(Calificacion::class, 'calificacionable');
}
public function ambientes() {
return $this->hasMany(Ambiente::class, 'alquiler_id');
}
//(...)
}
This works, but is there a better approach to achieve this? How can I improve or optimize this code?



publicadorandcontratadoronAlquilerbut that relates toUser. How does this relate to the relationship betweenDatosContactoandAlquiler?contratadorandpublicadorbecause you will have a lot of n+1 issues when querying your contacts. Not to mention you're forcing a behavior on your base model to do a shortcut. Instead I would simply usegroupBy('alquiler_id')To achieves the same result, you can then map the results into an object that would return thecontractoror thepublicador. You can also use a custom Collection that would provide few helpers.DatosContactoto contains bothDatosContactoin the first place. What are you trying to solve before improving or optimize in the first place ?DatosContactoshould be part of theUserentity andtipo_usariojust metadata for the relationship betweenUserandAlquilerunless it's possible here to have a different users inAlquilerthan you have contact data for.