PHP中位标志的最佳实践

14

我正在使用PHP + MySQL编写一个小应用程序,现在遇到了一个对象,它有几个(目前为止有8个,但不会增加)与其相关的标志。虽然这些标志没有什么关联,但有一些组合是无意义的。该对象表示DB中的一行(也有一些保存/加载它的方法),因此选择存储方法也适用于此问题。

问题是 - 如何在代码和数据库中最好地表示它们? 我可以想到几种方式:

一种将它们存储在DB中的方法是作为按位标志的单个整数字段。然后在PHP端,我可以想象几种表示它们的方式:

  • 只导出整数值并定义几个标志常量;让需要使用这些标志的每个地方执行自己的按位操作;
  • 定义类方法GetFlag()SetFlag()UnsetFlag(),对私有整数变量执行按位操作;然后将其中一个标志常量作为参数传递给这些方法。
  • 定义方法GetFlagA()GetFlagB()等(以及对应的Set和Unset方法);
  • 定义一堆成员变量,每个变量表示一个标志;在从DB加载时设置它们,并在保存时收集它们。
  • 创建一个成员变量,它是所有标志值的数组。使用预定义的常量作为数组索引访问每个标志。同时,在加载/保存时填充/收集该数组。

另一种方法是将它们作为单独的BIT字段存储在DB中。在PHP中,这将转换为几个成员变量。我认为这会使查询变得复杂。

最后一种方法是为所有标志定义另一个表,并为标志和原始对象之间的多对多关系定义一个中间表。在我的看法中,这是最混乱的解决方案,考虑到否则只有大约3个表。

我没有做过很多PHP开发,所以不知道最佳实践是什么。在C#中,我可能会将它们存储为位标志,并创建对私有整数执行位操作的属性。但PHP没有属性(我正在使用最新的稳定版本)...

7个回答

39
在您的model中,该对象有8个布尔属性。这意味着在您的数据库表中有8个布尔(MySQL的TINYINT)列和8个getter/setter方法在您的对象中。简单而传统。
重新思考您当前的方法。想象一下下一个需要维护此代码的人会说什么。
CREATE TABLE mytable (myfield BIT(8));

好的,看起来我们将会有一些二进制数据出现。

INSERT INTO mytable VALUES (b'00101000');

等一下,有人能再告诉我每个1和0代表什么吗。

SELECT * FROM mytable;
+------------+
| mybitfield |
+------------+
| (          | 
+------------+

什么?
SELECT * FROM mytable WHERE myfield & b'00101000' = b'00100000';

我去!我去!

自刎


-- 同时,在一个仙女和独角兽玩耍,程序员不讨厌数据库管理员的另一个宇宙中... --

SELECT * FROM mytable WHERE field3 = 1 AND field5 = 0;

幸福和阳光!


实际上是这样的... SELECT * FROM mytable WHERE field3 AND !field5; - Christian
6
这是我最近看到的对这个主题最有趣的回答。谢谢你让我笑了,点个赞支持一下。 - damianb
HAHAHA... WTF--- 很好的答案! - Philip F
1
标志没有问题...它们是可扩展的。我使用普通的int字段而不是位。在程序员讨厌数据库管理员的宇宙中,您永远不必为新标志向表添加另一个字段。与此同时,在程序员喜欢数据库管理员的宇宙中,每当一些新客户或公司想要添加新标志时,您都必须去找数据库管理员并让他向表添加另一个字段。 - Mick
1
@Christian 我建议避免在MySQL中使用!,因为它们有一些...奇怪的问题。 - Brian Leishman

2
如果你必须使用比特标志,那么请使用一个SET列将它们存储在数据库中,使用关联数组在代码中操作它们,并编写相应的方法来打开/关闭这些标志。如果需要以单个整数的格式使用,请添加单独的方法将数组转换为整数或者从整数转换回数组。
使用低级比特运算符并不能带来任何好处,所以最好让代码易于阅读。

没错。问题就在于 - 什么更易读?此外,我想避免在数据库端定义值,因为这样任何更改都必须在数据库和 PHP 中反映。位标志的更改只会在 PHP 中进行。 - Vilx-
如果你已经查看了两种方法,但无法确定哪种更易读,那么决定似乎是随意的。尝试向其他开发人员展示示例,或想象一下在很长时间之后需要重新熟悉代码的情况,以更好地了解可读性。 - Phantom Watson
Vilx:不是这样的。无论哪种方法,都需要在数据库中反映出所做的更改。 - Preston

1
我编写了这个简单的函数来填补PHP和MySQL ENUM之间的差距:
function Enum($id)
{
    static $enum = array();

    if (func_num_args() > 1)
    {
        $result = 0;

        if (empty($enum[$id]) === true)
        {
            $enum[$id] = array();
        }

        foreach (array_unique(array_map('strtoupper', array_slice(func_get_args(), 1))) as $argument)
        {
            if (empty($enum[$id][$argument]) === true)
            {
                $enum[$id][$argument] = pow(2, count($enum[$id]));
            }

            $result += $enum[$id][$argument];
        }

        return $result;
    }

    return false;
}

使用方法:

// sets the bit flags for the "user" namespace and returns the sum of all flags (15)
Enum('user', 'anonymous', 'registed', 'active', 'banned');

Enum('user', 'anonymous'); // 1
Enum('user', 'registed', 'active'); // 2 + 4 = 6
Enum('user', 'registed', 'active', 'banned'); // 2 + 4 + 8 = 14
Enum('user', 'registed', 'banned'); // 2 + 8 = 10

你只需要确保在定义枚举列表时与 MySQL ENUM 中的顺序相同即可。


0
假设您使用的是5.0.5之后的MySQL版本,您可以将列定义为BIT[这里是位数]。至于PHP方面,我可能会采用Get/SetFlagA、Get/SetFlagB方法,因为不需要取消设置方法,您只需在设置方法中输入布尔值即可。

0

我建议避免使用位运算,因为仅通过查看数据库很难知道设置了哪些标志(除非您出于某种原因想要超级高效)。在另外两个选项之间,我认为这取决于每个标志的含义有多重要。考虑到已经有8个标志,我倾向于使用另一个表格,并在它们之间建立多对多的关系(抱歉选择了你最不喜欢的方法)。


0
将它们存储在数据库中的一种方法是使用单个整数字段作为位标志。如果您想这样做,可以使用__get和__set 重载方法,以便您可以简单地检索字段,然后根据需要执行位运算。

是的,我忘了提到这种可能性。这是另一种我不喜欢的方式,因为它几乎会破坏IDE中的代码完成功能,并且在我看来使代码更难读懂。 - Vilx-

0

好的,经过思考后,我决定每个标记使用一个成员变量。虽然我本可以采用getter/setter方法,但我在我的代码中没有使用它们,这将不符合规范。此外,通过这种方式,我还可以抽象出DB存储方法,并且如果需要的话,以后可以轻松更改。

至于DB - 我现在会继续使用位整数 - 主要是因为我快完成软件了,不想再更改了。这根本不影响可读性。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接