gingyan
gingyan

Reputation: 83

Laravel Eloquent Model inheritance

I'm a new bit who is trying to build an app using Laravel 5.5, and the Eloquent model.

I have two classes: (1) Customer and (2) VIPCustomer which extends Customer.

You may immediately tell VIPCustomer contains all attributes that a customer has, and other extra attributes.

Just to be clear, a customer may not be a VIP, and a VIP must be a customer; The customer may immediately opt-in to be a VIP the first time he shops.

Therefore, I am attempting to do something like this in the database:

Customer:

+------------------------+
|id|name|gender|join_date|
+------------------------+

VIPCustomer:

+----------------------------------+
|customer_id|valid_until|type|point|
+----------------------------------+
(customer_id is a foriegn key referencing Customer.id)

And accordingly, in the model php file:

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Customer extends Model
{

}

.

namespace App;

use Illuminate\Database\Eloquent\Model;

class VIPCustomer extends Customer
{
    public $incrementing = false;
}

And that's it? I saw there are others saying I should using polymorphic relationship but I don't understand what it means.

In addition, is it possible to do instantiate a new VIP Customer something like this?

$customer = new VIPCustomer;
$customer->name   = 'Alice';
$customer->gender = 'F';
$customer->type   = 'gold';
$customer->point  =  0;
$customer->save();

On top of that, say when the VIP membership ends, is it possible to preserve that person as Customer? Because I'm afraid deleting that person will delete him from both Customer and VIPCustomer tables.

Thank you very much in advance.

Upvotes: 8

Views: 9371

Answers (1)

Chay22
Chay22

Reputation: 2894

Your current VIPCustomer class looks like a class that holds a VIP data, not a subject (a customer). Then so, I would rename it as VIPCustomerData here and make a new VIPCustomer to inherit Customer class instead.

class Customer extends Model
{
    protected $table = 'customers';
}

Make sure you define the table name to avoid it being guessed by inheritance. Then tell VIPCustomer to has a relation to VIPCustomerData.

class VIPCustomer extends Customer
{
    public function vipData()
    {
        return $this->hasOne(VIPCustomerData::class, 'customer_id', 'id');
    }
}

Now, the problem is whenever you're going to retrieve VIP customers like VIPCustomer::get(), you'll get whole customers instead. So, applying global scope is needed.

class VIPCustomer extends Customer
{
    protected static function boot()
    {
        parent::boot();

        static::addGlobalScope('weareviptypeofcustomer', function ($q) {
            $q->has('vipData'); // only customers with vip data
        });
    }

    public function vipData()
    {
        return $this->hasOne(VIPCustomerData::class, 'customer_id', 'id');
    }
}

To create a new Customer as VIP, of course 2 queries is needed to insert here. Example,

$vipCustomer = new VIPCustomer;
$vipCustomer->name   = 'Alice';
$vipCustomer->gender = 'F';
$vipCustomer->save();

$vipCustomerData = new VIPCustomerData;
$vipCustomerData->type   = 'gold';
$vipCustomerData->point  =  0;

$vipCustomer->vipData()->save($vipCustomerData);

Example of updating point.

$vipCustomerData = $vipCustomer->vipData; // or $vipCustomer->vipData()->first();
$vipCustomerData->point  =  10;
$vipCustomerData->save();

Example of removing VIP status from customer. Of course just delete VIPCustomerData from its table.

$vipCustomer->vipData()->delete();

However, it's better to maintain these subjects as one class if there is no special column to treat each subject differently.

class Customer extends Model
{
    protected $table = 'customers';

    protected $with = ['vipData']; // always eager load related 'vipData'

    protected $appends = ['is_vip']; // append 'is_vip' accessor

    public function vipData()
    {
        return $this->hasOne(static::class, 'customer_id', 'id');
    }

    public function getIsVipAttribute()
    {
        return (bool) $this->vipData;
    }
}

$customers = Customer::all();

foreach($customers as $customer) {
    if ($customer->is_vip) {
        // is VIP
    } else {

    }
}

Upvotes: 12

Related Questions