leandrocaplan wrote a reply+100 XP
5mos ago
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.
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
leandrocaplan liked a comment+100 XP
5mos ago
leandrocaplan wrote a reply+100 XP
5mos ago
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 App\Models\DatosContacto;
use App\Models\PublicadorContacto;
use App\Models\ContratadorContacto;
(...)
class Alquiler extends Model {
protected $appends = ['reputacion', 'ambientes', 'datosContacto'];
(...)
public function datosContacto() {
return $this->hasMany(DatosContacto::class, 'alquiler_id');
}
public function publicadorContacto() {
return $this->hasOne(PublicadorContacto::class, 'alquiler_id')
->where('tipo_usuario', 'publicador');
}
public function contratadorContacto() {
return $this->hasOne(ContratadorContacto::class, 'alquiler_id')
->where('tipo_usuario', 'contratador');
}
public function getDatosContactoAttribute() {
if ($this->disponible) return null;
if (!$this->publicadorContacto() || !$this->contratadorContacto()) return null;
return (object)[
'publicadorContacto' => $this->publicadorContacto,
'contratadorContacto' => $this->contratadorContacto,
];
}
(...)
}
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
leandrocaplan wrote a reply+100 XP
5mos ago
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 started a new conversation+100 XP
5mos ago
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:
leandro@Leandro:~/Proyectos/infoalquiler$ php artisan tinker
Psy Shell v0.12.12 (PHP 8.4.11 — cli) by Justin Hileman
> use App\Models\Alquiler;
> $alquiler=Alquiler::find(31);
= App\Models\Alquiler {#6018
(....)
datosContacto: App\Models\DatosContacto {#6726
#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,
+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,
},
},
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:
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:
<?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');
}
//(...)
}
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