8

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.

1 Answer 1

12

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 {

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

3 Comments

Thank you very much, it's very clear. But how do I cast a customer to a VIPCustomer then?
Glad it helped, you can mark this as answer if you feel it is. You should not cast customer to VIP, as mostly in programming, parent class should never know its children. What they do, doing, are, etc. I gave you more example to just make a property that hold a state to tell whether it's VIP or not.
It seems that whenever I need to change a customer to a VIP, I need to delete that customer and remember his ID & info, and explicitly set the ID to the new VIP; since I need different controllers for VIP and normal customers as there are features exclusive only for VIP customers. Thank you anyway.

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.