2

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:

phpMyAdmin screenshot

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:

Current model result

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:

Desired result (edited screenshot)

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?

5
  • You already have publicador and contratador on Alquiler but that relates to User. How does this relate to the relationship between DatosContacto and Alquiler ? Commented Nov 26 at 8:00
  • I wouldn't try to append contratador and publicador because 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 use groupBy('alquiler_id') To achieves the same result, you can then map the results into an object that would return the contractor or the publicador. You can also use a custom Collection that would provide few helpers. Commented Nov 26 at 8:00
  • Why do you need DatosContacto to contains both DatosContacto in the first place. What are you trying to solve before improving or optimize in the first place ? Commented Nov 26 at 8:07
  • Without any more information i suspect your issue here may be that DatosContacto should be part of the User entity and tipo_usario just metadata for the relationship between User and Alquiler unless it's possible here to have a different users in Alquiler than you have contact data for. Commented Nov 26 at 8:22
  • 4
    I hope that data you're showing is not real customer data... Commented 2 days ago

2 Answers 2

0

you can start by using the correct relation type for your case:

class Alquiler extends Model {

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

// now alquiler->datosContacto will give you both contact types
/*
{
  id: 31,
  ...
  datosContacto: {
    {id: 42, ...},
    {id: 43, ...}
  }
}
*/
// but this is still not the structure you want
// bc you don't know what entry in datosContacto is what contact without checking
// this is bc datosContacto is a generic eloquent collection, not specific to your use case
// if you want to further manipulate this structure, you wound need to change how DatosContacto model creates a new collection
// bellow is an example of that

use Illuminate\Database\Eloquent\Collection;

class DatosContactoCollection extends Collection
{
  //
}

class DatosContacto extends Model {

  public function newCollection(array $models = [])
  {
    // or use Eloquent\Collection
    $collection = new DatosContactoCollection($models);

    if (count($models) <= 2) {
      return $collection->keyBy('tipo_usuario');
    }

    return $collection;    
  }

}
Sign up to request clarification or add additional context in comments.

Comments

0

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:

<?php

namespace App\Models;

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

class PublicadorContacto extends DatosContacto {
}

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

3 Comments

Please format code blocks correctly,not as quoted text
I've just done it, at first I was getting errors when trying to format code in the proper way.
if you are going to use 2 queries to load them, maybe keep them in separate tables in the first place. Also your models that extend the base model can implement global scopes on their type.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.