添加任务的功能实现和前面实现用户功能的流程完全一致:
打开 app/model/task.php(如果没有则用 WebSetup 创建),修改下面几个地方:
属性和关联的定义:
'props' => array ( ..... 'is_completed' => array('readonly' => true), 'completed_at' => array('readonly' => true), 'owner' => array(QDB::BELONGS_TO => 'User', 'source_key' => 'owner_id'), ),
验证规则:
'validations' => array ( 'subject' => array ( array('not_empty', '任务主题不能为空'), array('max_length', 180, '任务主题不能超过 60 个汉字或 180 个字符'), ), ),
自动填充规则:
'create_autofill' => array ( ..... // 新建任务的 is_completed 状态总是为 false 'is_completed' => false, // 新建任务的 completed_at 值总是为 null 'completed_at' => null, ),
create_autofill 设置可以在创建对象时将指定的数值设置为特定值。例如“新建任务的状态是未完成”这个逻辑就可以通过简单的指定 create_autofill 来实现。
最后再加上安全性设置:
'attr_accessible' => 'subject, description',
attr_accessible 设置会在构造对象时产生作用:
$task = new Task($form->values()); $task->save();
上述代码构造了一个新的 task 模型实例,并且以表单的值作为对象初始值。这种方式很方便,但存在一个安全隐患。
假设用户在客户端构造一个包含“task_id”字段值的表单,并提交到服务器,那么此时构造出来的 $task 对象就包含了 task_id 属性值。由于 task_id 属性也是 task 对象存储在 tasks 表中的主键字段,那么在接下来调用 save() 方法时,实际完成的操作不是“新建一个 task 对象”,而是“更新一个已有的 task 对象”。因此我们务必要堵上这样的漏洞。
设置 attr_accessible 选项,可以指定哪些属性可以通过构造函数来赋值,这样上面的代码中即便表单包含 task_id 值,也不会被存入新建的对象,实现了安全性。
添加 app/form/task.php 和 app/form/task_form.yaml 文件,内容分别是:
class Form_Task extends QForm { function __construct($action) { // 调用父类的构造函数 parent::__construct('form_task', $action); // 从配置文件载入表单 $filename = dirname(__FILE__) . '/task_form.yaml'; $this->loadFromConfig(Helper_YAML::loadCached($filename)); $this->addValidations(Task::meta()); } }
~form: _subject: "添加任务" subject: _ui: textbox _label: "任务主题" description: _ui: memo _label: "任务描述"
修改 tasks 控制器(如果没有则通过 WebSetup 创建),添加 actionCreate() 动作方法:
function actionCreate() { $form = new Form_Task(url('tasks/create')); if ($this->_context->isPOST() && $form->validate($_POST)) { // 通过应用程序对象获得当前用户对象 $user = $this->_app->currentUserObject(); // 通过用户对象创建任务 $task = $user->createTask($form['subject']->value, $form['description']->value); // 保存并重定向浏览器 $task->save(); return $this->_redirect(url('tasks/index')); } $this->_view['form'] = $form; }
以及对应的视图 app/view/tasks/create.php:
<?php $this->_extends('_layouts/default_layout'); ?> <?php $this->_block('contents'); ?> <?php $this->_element('formview_simple', array('form' => $form)); ?> <?php $this->_endblock(); ?>
这里我们用到了应用程序对象的一个新方法 currentUserObject()。但这个方法还需要添加到 app/myapp.php 中,代码如下:
/** * 获得当前用户对应的 User 模型对象实例 * * @return User */ function currentUserObject() { $data = $this->currentUser(); $user = User::find('user_id = ?', $data['id'])->query(); if ($user->id()) { return $user; } else { throw new MyAppException('当前访问用户没有对应的 User 对象'); } }
currentUserObject() 方法不但通过查询操作获得 User 模型对象实例,而且通过检查其 ID 来确保这个对象是肯定存在的。这种做法从根本上保证了后续数据的有效性。
保证逻辑的正确性和数据的有效性是应用程序的核心使命!
在前面的代码中,我们是通过 User 对象的 createTask() 方法来创建 Task 对象的,所以我们需要在 User 模型中添加该方法:
修改 User 模型的类,加入下列代码:
class User extends QDB_ActiveRecord_Abstract { /** * 创建属于当前用户的任务 * * @return Task */ function createTask($subject, $description = null) { $task = new Task(); $task->owner = $this; $task->subject = $subject; $task->description = $description; return $task; } ..... }
并且在 User 模型 __define() 方法中,增加 User 与 Task 的关联:
'props' => array( ..... 'tasks' => array(QDB::HAS_MANY => 'Task', 'target_key' => 'owner_id'), ..... ),
通过浏览器访问 http://localhost/todo/index.php?controller=tasks&action=create 就可以看到添加任务表单:
添加任务表单
输入有效数据后提交,可以通过 phpMyAdmin 看到数据已经成功添加:
成功保存到数据库的任务数据
修改 tasks 控制器的 actionIndex() 动作方法:
function actionIndex() { $tasks = Task::find('owner_id = ?', $this->_app->currentUserObject()->id())->getAll(); $this->_view['tasks'] = $tasks; }
以及对应的视图 app/view/tasks/index.php:
<?php $this->_extends('_layouts/default_layout'); ?> <?php $this->_block('contents'); ?> <div class="tasks"> <h1>我的任务</h1> <?php foreach ($tasks as $task): ?> <h2><a href="<?php echo url('tasks/edit', array('task_id' => $task->id())); ?>"><?php echo h($task->subject); ?></a></h2> <p class="meta"> <?php if ($task->is_completed): ?> <em>已经在 <?php echo date('m-d H:i', $task->completed_at); ?> 完成该任务</em> <?php else: ?> <strong>添加日期:<?php echo date('m-d H:i', $task->created); ?></strong> <?php endif; ?> </p> <?php if ($task->description): ?> <p class="description"> <?php echo nl2br(h($task->description)); ?> </p> <?php endif; ?> <hr /> <?php endforeach; ?> </div> <?php $this->_endblock(); ?>
最后在 app/public/css/style.css 添加一些样式定义,以便让列表页面看上去更美观:
.tasks h2 { font-size: 18px; font-weight: bold; } .tasks p { font-size: 14px; color: #333; font-weight: normal; } .tasks p.meta { font-size: 12px; font-weight: normal; color: #666; }
现在添加任务后就可以看到任务列表了:
任务列表页面