添加任务

添加任务的功能实现和前面实现用户功能的流程完全一致:

  1. 根据需求完善模型
  2. 创建需要的表单
  3. 编写控制器代码

完善 task 模型

打开 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 模型

在前面的代码中,我们是通过 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;
}

现在添加任务后就可以看到任务列表了:

任务列表页面

qeephp.com