32

I need to extensively use statuses in mt project. I need them for my users (active, suspended, etc), an entity (active, pending_activation, inactive) and for my subscriptions(active, on_grace_period, not_subscribed, never_subscribed).

So far I thought that the best way is to store them in the DB but i have a feeling it's much easier to have them in the other 3 options.

I also thought that i can store them in my Eloquent Model as constants. For example my subscription model would look like this:

// SubscriptionModel
const SUBSCRIBED_ACTIVE = 1;
const SUBSCRIBED_ON_GRACE_PERIOD = 2;
const NOT_SUBSCRIBED = 3;
const NEVER_SUBSCRIBED = 4;

and retrieving them, for example in a blade view:

// subscription/index.blade.php
@if($user->subscription->status == /App/SubscriptionModel::SUBSCRIBED_ACTIVE)
    <div>You are subscribed. Thank you</div>
@elseif($user->subscription->status == /App/SubscriptionModel::NEVER_SUBSCRIBED)
    <div>You need to create a subscription before being granted full access!</div>
@elseif(...)
    // and so on

How about doing the same but using the config folder and adding a file called status.php. Accessing it in the view would be like:

@if($user->subscription->status == Config::get('status.subscription.SUBSCRIBED_ACTIVE'))
<div>You are subscribed. Thank you</div>
@elseif(...)
// etc

Is there a better way?

Also, how about the other part of the equation, meaning the status stored in the DB. Should I only have a status column for the subscription table and store what the app dictates or even bettter create a separate table subscription_statuses and have a foreign_key subscription_status_id in the subscriptions table?

Shapi
  • 5,493
  • 4
  • 28
  • 39
Cristian
  • 2,390
  • 6
  • 27
  • 40
  • These should certainly be stored in the database, see my answer below – AJReading Oct 03 '15 at 11:45
  • Even if you store it in the database you need to `Model::select('myvar')->where('myvar','=' myvar)->get();`. @Cristian, check my awnser, you can use global vars in `.env` os create a file for that. it depends if you will need more vars or not, if you need only 4 vars you should not create a file just for them, its not elegant. I flagged a comment in my answer bcs the user downvoted and the reason was `probavly* you must create and config file not .env`. its poor that ppl downvote you and dont have a solution or explain why is your answer is not the better. – Shapi Oct 03 '15 at 11:58
  • You can write a scope if convenience is required to select users by their status. Storing in the env file is the worst place. You have env files for different environments, and would have to maintain those lists across env files. They should be stored in a central location, the database. Without that how would you determine the status by just looking at the database? Your answer was down voted because it's bad advice. – AJReading Oct 03 '15 at 12:17

5 Answers5

34

I tend to create a specific model for statuses, that acts as an enum. So if I have an Event model, I may have a corresponding EventStatus model that looks like this:

class EventStatus
{
    public const CANCELLED = 'EventCancelled';
    public const POSTPONED = 'EventPostponed';
    public const RESCHEDULED = 'EventRescheduled';
    public const SCHEDULED = 'EventScheduled';
}

I can then do checks like this:

$event->status === EventStatus::CANCELLED;

And I’ll usually add convenience methods to my models too:

class Event extends Model
{
    public function isCancelled(): bool
    {
        return $this->status === EventStatus::CANCELLED;
    }
}

For the “human-friendly” strings, I’ll then have a language file that has the text strings:

<?php // resources/lang/en/event_status.php

return [
    EventStatus::CANCELLED => 'Cancelled',
    EventStatus::POSTPONED => 'Postponed',
    EventStatus::RESCHEDULED => 'Rescheduled',
    EventStatus::SCHEDULED => 'Scheduled',
];
Martin Bean
  • 38,379
  • 25
  • 128
  • 201
  • Your answer is the closest fit for my needs. I still have a question. Currently most of my statuses are stored in DB as a different table. Like: event_statuses. When I want to retrieve one I do (have a model for that) `EventStatus::findFirstBySlug('cancelled')`. But to compare (using relations) I just do `if( $event->event_status->slug == 'cancelled' ) ... `. To my question. Which option make more sense in making it easier to use? The DB Model of EventStatus or just the simple model you were saying about? I suppose your EventStatus in not linked to the DB. – Cristian Oct 03 '15 at 13:24
  • Is your syntax correct for the shorthand array? Well arrays in general? Should be a comma at the end of each line not semicolon? Otherwise this looks great to me. – johnsnails Dec 29 '18 at 11:44
  • 1
    @johnsnails No, they should indeed be commas. Good spot! – Martin Bean Dec 29 '18 at 14:18
13

In my applications I do similar to @Martin Bean except I don't create separate classes for status, I store that inside the existent class/Model.

I'm going to call user, subscription and entity a entity.

  • Entity have a status that exists in it's Model and table in the database.
  • Each Model have constants of possible values of status like ACTIVE, INACTIVE, PENDING, etc, and those may vary for each Model.
  • Create methods for dealing with it like getStatusLabel(), listStatus(), isActive(), isX(), etc.
  • Those isActive/X() are only created if really necessary, maybe a Model have 4 status but you only do comparisons against one specific, so I'd create only one isX() for that status.

Example

class User
{
    const STATUS_ACTIVE    = 1;
    const STATUS_SUSPENDED = 2;
    const STATUS_INACTIVE  = 3;

    /**
     * Return list of status codes and labels

     * @return array
     */
    public static function listStatus()
    {
        return [
            self::STATUS_ACTIVE    => 'Active',
            self::STATUS_SUSPENDED => 'Suspended',
            self::STATUS_INACTIVE  => 'Inactive'
        ]
    }

    /**
     * Returns label of actual status

     * @param string
     */
    public function statusLabel()
    {
        $list = self::listStatus();

        // little validation here just in case someone mess things
        // up and there's a ghost status saved in DB
        return isset($list[$this->status]) 
            ? $list[$this->status] 
            : $this->status;
    }

    /**
     * Some actions will happen only if it's active, so I have 
     * this method for making things easier.
     * Other status doesn't have a specific method because
     * I usually don't compare agains them
     * @return Boolean
     */
    public function isActive()
    {
        return $this->status == self::STATUS_ACTIVE;
    }
}
Edson Horacio Junior
  • 3,033
  • 2
  • 29
  • 50
12

I do not agree with the other answers. Your status information should be stored in the database. A well designed database should be clear and usable without the application. What happens if you decide to use this database to power something like a mobile application as well? You will be taking some of the information away from the database and storing it only in Laravel, meaning you would have to duplicate that list of statuses in your mobile application too, and maintain it across the two.

This kind of information should be stored in the database.

Option 1

If your users can only ever have one status, then you should use an enum field with the values subscribed, subscribed-grace, not-subscribed, never-subscribed

This is just as simple in your views:

@if($user->subscription->status == 'subscribed'

Option 2

If however, you might have multiple statuses, then you should almost certainly have a separate field for each status, and use a TINYINT to store a 1 or 0.

Separate status table?

I cannot see a good reason to use a separate status table unless you anticipate you might add many more statuses, and even if you are adding more, you can just add new values to the enum or add a new field depending on which option would suit.

A status table would be ideal if you plan to use the statuses for many other tables in the database besides users.

The only other reason for a seperate status table would be if you decided to change the meaning of a particular status. This would mean you could rename the status in the status table, but the users would still be linked to it via it's primary key. Changing the meaning of a status with the previous two methods would involve changes to the structure.

It really comes down to how you anticipate you will use them, but there is no reason not to keep them in the database.

AJReading
  • 1,193
  • 20
  • 35
  • I currently have my statuses stored as separate tables (some are reusable some are not). My issue with ENUM is that once set it's harder to make changes when the website is in production (http://komlenic.com/244/8-reasons-why-mysqls-enum-data-type-is-evil/). However, I feel that storing them in my application (as @Martin Bean pointed out) and linking them to languages files is a much cleaner way. My statuses in my DB have an id, slug and description. However I am stuck with that description in one language only. – Cristian Oct 03 '15 at 13:38
  • Your current approach is ideal then, there's no reason why you can only have them in one language. You can have a table for languages and descriptions instead of storing the description in the status table. Or if you prefer languages in a file you can still do so with that setup – AJReading Oct 03 '15 at 14:03
  • _What happens if you decide to use this database to power something like a mobile application as well_ - This is the reason for an API – ggdx Jan 07 '20 at 10:28
1

There are advantages and disadvantages to each method. It's good to be aware of each.

Table - Pros and cons (AJReading's method):

  • Adding and maintaining a table SEEMS tedious
  • Just having another table and model can make our code feel more cluttered (not saying it's a good reason not to use just saying it's kinda true)
  • It gets awkward when we have application logic dependent upon something in the database (things in the database feel like they should be variable, when we base application logic on them they're required)
  • Now we have migrations, but before them these used to be the bane of developers existence (they would make switching between servers an awful chore because you had to remember to add new statuses or your app would crash)...you would have had to do this with any database change but still these were the ones I'd have to do the most frequently
  • Good for data integrity

Using constants: Pros/cons (Martin Bean's method):

  • Avoids the disadvantages above
  • These are easy to reference in your code and base logic on
  • You don't have to create a new model or table even (he does in his example, but you could also just put them in the Events model)
  • They're great for values that will ONLY be used behind the scenes
  • They reduce the amount of queries
  • They just don't feel like as much work. They seem easier to refactor.
  • Con: they get kinda awkward when you get into labeling them, retrieving all of them, getting descriptions, etc. The translation solution is a good one but if you don't use translations in your app then this is a bit awkward as well.
  • Ultimately they're breaking the ORM flow you have going. If all your other models extend Eloquent then this breaks the mold a bit.
  • There's no real agreement on how to best do this. A lot of people use a different method each time.
  • Like AJReading said, if you need to use the database alone for another aspect of the project it won't work

I use the constant method but sometimes I'd think my code might be cleaner and simpler if I'd used tables. It's a hard call. I'd like there to be a well documented solution for the constant method to at least create consistency but I haven't seen one yet. Either way I don't think there's a right or wrong answer. Pick one and go with it!

Sabrina Leggett
  • 9,079
  • 7
  • 47
  • 50
0

For decisions of this nature, ask yourself this:

"Will there ever be an instance of my application where it would make sense for these constants to have different values?"

e.g. a test environment, some sort of clone, some not yet defined but possible future version...

If the answer to that question is "yes", then it should probably go in application config.

If it is unlikely, (or daft) to have the values change, they belong to, and should go in the model.

I suggest in this case there would be no sensible reason to ever have a version of the application with different values, so I'd put it in the model.

DanSingerman
  • 36,066
  • 13
  • 81
  • 92
  • Well. As statuses, they should always be constants and never change to something else. The only thing that may change is the addition of some statuses. – Cristian Oct 03 '15 at 13:43