-2

I'm not entirely sure the best way to handle a status field that relates to integers in a database table within a class.

Say we have the following:

$status = array(1 => 'Active', 2 => 'Inactive', 3 => 'Cancelled');

I'm thinking for clarity in the system, I want class constants and a getter for retrieving the status in an associative array, and so the class uses something better defined than "1" or "2".

 class User {
  const USER_STATUS_ACTIVE = 1;
  const USER_STATUS_INACTIVE = 2;
  const USER_STATUS_CANCELLED = 3;

  public function getStatusList() {
   return array(User::USER_STATUS_ACTIVE => 'Active',User::USER_STATUS_INACTIVE => 'Inactive',User::USER_STATUS_ACTIVE => 'USER_STATUS_CANCELLED');
  }
 }

This then allows setting the status using the constants with:

class User {
 private $status_id;

 const USER_STATUS_ACTIVE = 1;
 const USER_STATUS_INACTIVE = 2;
 const USER_STATUS_CANCELLED = 3;

 static public function statusList() {
  return array(User::USER_STATUS_ACTIVE => 'Active',User::USER_STATUS_INACTIVE => 'Inactive',User::USER_STATUS_CANCELLED => 'Cancelled');
 }

 public function setStatus($status_id) {
  try {
   if (!key_exists($status_id, User::statusList())) throw new Exception('Invalid status ID');
   $this->status_id = $status_id;
  }
  catch (Exception $e) {
   die($e);
  }
 }
}

$a = new User();
$a->setStatus(User::USER_STATUS_ACTIVE);

Alternately methods can be created for setActive(), setInactive(), setCancelled().

But what I'm trying to figure out is how to best handle the actual status values.

Would it be better to break the statuses out to a static class?

class User {
 private $status_id;

 public function setStatus($status_id) {
  try {
   if (!key_exists($status_id, UserStatuses::statusList())) throw new Exception('Invalid status ID');
   $this->status_id = $status_id;
  }
  catch (Exception $e) {
   die($e);
  }
 }
}

class UserStatuses {
 const USER_STATUS_ACTIVE = 1;
 const USER_STATUS_INACTIVE = 2;
 const USER_STATUS_CANCELLED = 3;

 static public function statusList() {
  return array(UserStatuses::USER_STATUS_ACTIVE => 'Active',UserStatuses::USER_STATUS_INACTIVE => 'Inactive',UserStatuses::USER_STATUS_CANCELLED => 'Cancelled');
 }
}

Or is there something entirely different that would be better?

Cody Gray - on strike
  • 239,200
  • 50
  • 490
  • 574
Exit
  • 973
  • 2
  • 13
  • 28

2 Answers2

0

Using the ReflectionClass

I like you second example; it's a very simple and effective way to make an enum type (sort of). Actually, some of the pretty big php ORMs out there, like Doctrine operate using a similar pattern. The one thing I like to do to improve scalability is to use the ReflectionClass. This will allow you to add new values in the form of constants without having to change the function that returns the list of values. I would also refactor the code to check if the value is valid in the enum class as well to keep a better separation of concerns between the two classes

class User {
    private $status_id;

    public function setStatus($status_id) {
        if (UserStatuses::isStatus($status_id)) {
            $this->status_id = $status_id;
        }
        else {
            throw new Exception('invalid status');
        }
    }
}

class UserStatuses {
    const USER_STATUS_ACTIVE = 1;
    const USER_STATUS_INACTIVE = 2;
    const USER_STATUS_CANCELLED = 3;

    public static function statusList() {
        $oClass = new ReflectionClass(__CLASS__);
        return $oClass->getConstants();
    }

    public static function isUserStatus($int) {
        return in_array($int, self::statusList());
    }
}
William Perron
  • 1,288
  • 13
  • 18
  • I like moving the validation to the UserStatuses class, it truly belongs there. I'm not a fan of dependencies, so when I looked up ReflectionClass, I saw it was built-in and is awesome. This is the perfect answer, I believe. – Exit Sep 14 '17 at 16:58
  • Although, I do have to add that ultimately I couldn't use ReflectionClass since it doesn't return the label values ("Active", "Inactive", etc...), I will certainly be using it for other things in the future. – Exit Sep 14 '17 at 19:19
  • @Exit I would simply change the value of the constants. In the end that's really the purpose of an Enum class, especially when you consider that some DBMS don't natively support them. In the end it really just depends on your database implementation – William Perron Sep 14 '17 at 19:25
-1

Referencing database IDs is not a good practice, in that the values of IDs are generally controlled by the database itself. It's true to say that no database column value is static, but the ID column on a database table is generally an internal ID that is auto-incremented and whose FK references are maintained via cascades in UPDATE and DELETE operations. In other words, the ID is the domain of the database, not your code.

A better practice is to include a custom unique field for your code, like this (I'm assuming MySQL here):

ALTER TABLE `my_reference_table` ADD COLUMN `internalCode` VARCHAR(256);
UPDATE `my_reference_table` SET `internalCode` = 'Active' WHERE `id` = 1;
UPDATE `my_reference_table` SET `internalCode` = 'Inactive' WHERE `id` = 2;
UPDATE `my_reference_table` SET `internalCode` = 'Cancelled' WHERE `id` = 3;
ALTER TABLE `my_reference_table` ALTER COLUMN `internalCode` VARCHAR(256) NOT NULL UNIQUE;

Once you've got a database data setup like this, then you can treat the column internalCode as a static element, have PHP constants that match those internal codes, and be sure that you're referring to the same row no matter if the ID changes.

In terms of storing these internal codes in PHP, I generally use an abstract class with a final private constructor so that it's very very clear that the class is not to be extended, and to only be referenced only in the static context, like so:

class UserStatusConstants {
    const _ACTIVE = 'Active';
    const _CANCELLED = 'Cancelled';
    const _INACTIVE = 'Inactive';

    final private function __construct() {}
}

You might be asking at this point why the constant names are prefixed with an underscore - that's to avoid the issue of having constant names that clash with reserved words in PHP.

Anyhow, once you've got this setup, there's various techniques you can use to set user_status_id values in your user table. Here's three I can think of:

UPDATE with JOIN queries Doing an UPDATE with a JOIN against the user status table, and filtering on the internal code (see How to do 3 table JOIN in UPDATE query?)

SELECT then UPDATE Start with a SELECT query against the user status table, filtering on internal code, then using that result to feed the user status id to the UPDATE query against the user table. This technique incurs an extra query, but if you store the results of the first query in a cache (e.g. Memcached, or a third party library) then this can speed up all queries using that data in the long run.

Stored procedure You could create a stored procedure that takes the internal code as a parameter, as well as any other parameters you need to pass to update other fields in the user table

Cody Gray - on strike
  • 239,200
  • 50
  • 490
  • 574
e_i_pi
  • 4,590
  • 4
  • 27
  • 45
  • Thanks for the reply. In the actual system, the integer IDs are not controlled by the database, only used by the database. There isn't a table that defines them in the database. So, ultimately, whatever is hardcoded in the system is what will be used in the database. These statuses do no change, if one is added, then the code would have to be changed anyway to make use of it. – Exit Sep 14 '17 at 03:05
  • As for the abstract class, I was thinking about that as an option, but didn't want to extend to use it. Hadn't considered setting it set up with a final private constructor to prevent instantiating. I'll consider it and do some research. Actively using the second code that I posted. – Exit Sep 14 '17 at 03:10
  • Ah righto, well I posted the idea in context of auto-incremented IDs, which is the usual use case. If you are fully controlling the IDs, then you might as well use them for your unique cross-application identifier – e_i_pi Sep 14 '17 at 03:32