表示PHP类中的状态数组

表示PHP类中的状态数组

问题描述:

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?

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());
    }
}

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