QeePHP 是一个遵循“领域驱动开发”思想的框架。而领域驱动中,最重要的就是模型。我们的大多数功能都是和模型有关的,所以在实现功能之前,首先要完善模型的定义。
先前创建的 User 模型只是半成品,现在我们需要对其进行完善。一个模型由几个方面组成:
打开模型的定义文件,在 __define() 方法中可以找到模型的详细定义。这些定义分为几个部分:
在 QeePHP 的模型中,属性本身具有多种特性:
对于 todo 这样的简单应用,我们用不到这么多高级特性,所以上述内容了解一下就行了。
todo 应用中,user 模型只有两个主要属性:用户名和密码。根据需求,这两个属性有下列要求:
现在我们一步步来实现这些要求。
在 __define() 方法中的适当位置加入下列代码:
'props' => array( ..... // 指定 username 属性为只读 'username' => array('readonly' => true), ..... ), /** * 指定更新数据库中的对象时,哪些属性的值不允许由外部提供 */ 'update_reject' => 'username',
继续:
'validations' => array ( // 指定 username 属性的验证规则 'username' => array ( array('not_empty', '用户名不能为空'), array('min_length', 5, '用户名不能少于 5 个字符'), array('max_length', 15, '用户名不能超过 15 个字符'), array('is_alnum', '用户名只能由字母和数字组成'), ), // 指定 password 属性的验证规则 'password' => array ( array('not_empty', '密码不能为空'), array('min_length', 6, '密码不能少于 6 个字符'), array('max_length', 20, '密码不能超过 20 个字符'), ), ),
经过简单的设置,大部分要求都可以由 QeePHP 来处理了,剩下的密码加密存储则可以借助现成的 acluser 行为插件来实现。
虽然我们可以通过自行编写代码来实现用户密码的加密存储,但 QeePHP 已经提供了现成的插件来帮助我们完成类似工作,并且提供了更丰富的特性。
acluser 行为插件应用到一个模型后,可以实现下列特征:
要使用 acluser 插件,只需要修改 __define() 方法:
// 指定该 ActiveRecord 要使用的行为插件 'behaviors' => 'acluser', // 指定行为插件的配置 'behaviors_settings' => array ( # '插件名' => array('选项' => 设置), 'acluser' => array( 'acl_data_props' => 'username', ), ),
acluser 插件的详细文档请参考: http://qeephp.com/docs/qeephp-api/class-model-behavior-acluser
最后,完整的 __define() 方法应该有如下内容:
static function __define() { return array ( // 指定该 ActiveRecord 要使用的行为插件 'behaviors' => 'acluser', // 指定行为插件的配置 'behaviors_settings' => array ( # '插件名' => array('选项' => 设置), 'acluser' => array( 'acl_data_props' => 'username', ), ), // 用什么数据表保存对象 'table_name' => 'users', // 指定数据表记录字段与对象属性之间的映射关系 // 没有在此处指定的属性,QeePHP 会自动设置将属性映射为对象的可读写属性 'props' => array ( // 主键应该是只读,确保领域对象的“不变量” 'user_id' => array('readonly' => true), /** * 可以在此添加其他属性的设置 */ # 'other_prop' => array('readonly' => true), 'username' => array('readonly' => true), /** * 添加对象间的关联 */ # 'other' => array('has_one' => 'Class'), 'tasks' => array(QDB::HAS_MANY => 'Task', 'target_key' => 'user_id'), ), /** * 允许使用 mass-assignment 方式赋值的属性 * * 如果指定了 attr_accessible,则忽略 attr_protected 的设置。 */ 'attr_accessible' => '', /** * 拒绝使用 mass-assignment 方式赋值的属性 */ 'attr_protected' => 'user_id', /** * 指定在数据库中创建对象时,哪些属性的值不允许由外部提供 * * 这里指定的属性会在创建记录时被过滤掉,从而让数据库自行填充值。 */ 'create_reject' => '', /** * 指定更新数据库中的对象时,哪些属性的值不允许由外部提供 */ 'update_reject' => 'username', /** * 指定在数据库中创建对象时,哪些属性的值由下面指定的内容进行覆盖 * * 如果填充值为 self::AUTOFILL_TIMESTAMP 或 self::AUTOFILL_DATETIME, * 则会根据属性的类型来自动填充当前时间(整数或字符串)。 * * 如果填充值为一个数组,则假定为 callback 方法。 */ 'create_autofill' => array ( # 属性名 => 填充值 # 'is_locked' => 0, ), /** * 指定更新数据库中的对象时,哪些属性的值由下面指定的内容进行覆盖 * * 填充值的指定规则同 create_autofill */ 'update_autofill' => array ( ), /** * 在保存对象时,会按照下面指定的验证规则进行验证。验证失败会抛出异常。 * * 除了在保存时自动验证,还可以通过对象的 ::meta()->validate() 方法对数组数据进行验证。 * * 如果需要添加一个自定义验证,应该写成 * * 'title' => array( * array(array(__CLASS__, 'checkTitle'), '标题不能为空'), * ) * * 然后在该类中添加 checkTitle() 方法。函数原型如下: * * static function checkTitle($title) * * 该方法返回 true 表示通过验证。 */ 'validations' => array ( 'username' => array ( array('not_empty', '用户名不能为空'), array('min_length', 5, '用户名不能少于 5 个字符'), array('max_length', 15, '用户名不能超过 15 个字符'), array('is_alnum', '用户名只能由字母和数字组成'), ), 'password' => array ( array('not_empty', '密码不能为空'), array('min_length', 6, '密码不能少于 6 个字符'), array('max_length', 20, '密码不能超过 20 个字符'), ), ), ); }