如何为查找数据库表定义ActiveRecord,以便它与枚举常量一样有用?

时间:2012-07-03 14:28:03

标签: php database-design activerecord yii

我们有一个基于Yii的PHP应用程序,可以在不同类型的对象上执行一些通用操作(如“与社交网络共享”或在内部网店中“购买”)。

现在,开发人员使用类常量来分隔不同类型的对象。像这样:

模型中的

class Referral extends CActiveRecord {
    //... a lot more stuff here...
    const ITEM_TYPE_PRODUCT = 'product';
    const ITEM_TYPE_DESIGN = 'design';
    const ITEM_TYPE_BRAND = 'brand';
    const ITEM_TYPE_INVITE = 'invite';
    const ITEM_TYPE_DESIGNER = 'designer';
    //... a lot more stuff here...
}

然后在某些控制器

// calling static method of Referral and pass a type IDs to change it's behavior
$referral_params = Referral::buildReferallParams(
    Referral::TYPE_PINTEREST, 
    Referral::ITEM_TYPE_PRODUCT
)

这种类常量的使用导致了我想要移除的主要代码重复。

我个人认为我们应该为类型添加一个查找表,如下所示:

CREATE TABLE `item_type` (
    id int primary key auto_increment,
    name varchar(255)
);

创建一个名为ItemType的Yii模型类,使用。

从表中提取ID

但它导致另一个问题:我们需要在代码中使用这种类型ID,并且需要长时间这样做。我们需要以某种方式为ID制作符号名称。现在像Referral::ITEM_TYPE_PRODUCT这样的调用非常方便,将其更改为Yii风格的ItemType::model()->findByAttributes(array('name' => 'Product'))是完全不可接受的。

我想避免在ItemType中维护相同的类常量列表(现在通过我们的代码库重复),因为每次添加新类型都需要添加新的常量,而且它只是一个不合适的同步问题等待发生。

所以,问题如下:如何使查找表的ActiveRecord与'enum'一样的数值常量同样有用?

我使用ItemType的子类:

来提供以下有点漂亮的解决方案
ProductItemType::id(); // returns lazy-loaded ID for 'Product' item type
BrandItemType::id();   // the same for 'Brand' item type
// ... and so on

但是,如果现在需要五个单一基类的子类,可能还需要另外一半。有没有可行的架构解决方案?

修改

重复问题是这样的:声明类型ID 的类常量在每个类中被重新定义,这需要以某种方式区分不同的项类型。我知道最“真实”的解决方案是将内部反转并将依赖于对象类型的特征移动到对象本身,但我们有遗留代码而没有任何测试覆盖率,这个解决方案现在只是一个梦想。现在我想要做的就是删除这个重复。和常数一样。

1 个答案:

答案 0 :(得分:1)

好吧,如果你想要一次定义所有这些,并从数据库中取出。这意味着你需要做一个行为,有类似静态的方法来检索这些(所以常量将成为函数),你需要创建适当的魔术方法__call来覆盖这些不存在的函数来调用你的数据库,并且您可以将此行为附加到您正在使用的任何yii类。

使用组件行为

组件支持mixin模式,可以附加一个或多个行为。行为是一种对象,其方法可以通过收集功能而不是专门化(即正常的类继承)由其附加组件“继承”。组件可以附加多个行为,从而实现“多重继承”。

行为类必须实现IBehavior接口。大多数行为都可以从CBehavior基类扩展。如果行为需要附加到模型,它也可以从CModelBehaviorCActiveRecordBehavior扩展,这实现了模型的特定功能。

要使用行为,必须首先通过调用行为的attach()方法将其附加到组件。然后我们可以通过组件调用行为方法:

// $name uniquely identifies the behavior in the component
$component->attachBehavior($name,$behavior);
// test() is a method of $behavior
$component->test();

可以像组件的普通属性一样访问附加行为。例如,如果名为tree的行为附加到组件,我们可以使用以下命令获取对此行为对象的引用:

$behavior=$component->tree;
// equivalent to the following:
// $behavior=$component->asa('tree');

可以暂时禁用某个行为,以便通过该组件无法使用其方法。例如,

$component->disableBehavior($name);
// the following statement will throw an exception
$component->test();
$component->enableBehavior($name);
// it works now
$component->test();

附加到同一组件的两个行为可能具有相同名称的方法。在这种情况下,第一个附加行为的方法将优先。

events一起使用时,行为更加强大。当附加到组件时,行为可以将其某些方法附加到组件的某些事件。通过这样做,行为有机会观察或更改组件的正常执行流程。

还可以通过附加到的组件访问行为的属性。这些属性包括公共成员变量和通过行为的getter和/或setter定义的属性。例如,如果某个行为具有名为xyz的属性,并且该行为附加到组件$ a。然后我们可以使用表达式$a->xyz来访问行为的属性。

更多阅读:
http://www.yiiframework.com/wiki/44/behaviors-events
http://www.ramirezcobos.com/2010/11/19/how-to-create-a-yii-behavior/