Skip to content

Tutorial.Database Operation II.zh_cn

linzongshu edited this page Jul 4, 2013 · 1 revision

English

  1. 实例

上面我们基本上详细地介绍了数据库操作的一些API,下面以member模块为例,介绍下这些API的使用。

4.1 添加注册信息

首先,我们需要在用户提交注册信息后,将数据写入account表里。这部分代码需要在RegisterController::indexAction的isValid()后添加:

路径:usr/module/member/src/Controller/Front/RegisterController.php

Code 4.4.1

<?php
...

class RegisterController extends ActionController
{
    ...

    public function indexAction()
    {
        $form = $this->renderForm();

        if ($this->request->isPost()) {
            $post = $this->request->getPost();
            $form->setData($post);
            $form->setInputFilter(new RegisterFilter);
            if (!$form->isValid()) {
                $this->view()->assign('form', $form);
                return ;
            }
            $data  = $form->getData();
            if ($data['password'] === $data['repeat-password']) {
                $columns = array('id', 'username', 'password', 'gender', 'country', 'feedback', 'introduction');
                foreach (array_keys($data) as $key) {
                    if (!in_array($key, $columns)) {
                        unset($data[$key]);
                    }
                }
                $row = $this->getModel('account')->createRow($data);
                $row->save();
                if (!$row->id) {
                    $this->view()->assign('form', $form);
                    $this->view()->assign('error', __('Failed insert user data!'));
                    return ;
                }
                $this->view()->setTemplate('register-success');
                $this->view()->assign('info', $data);
                return ;
            } else {
                $this->view()->assign('form', $form);
                $this->view()->assign('error', __('Password not matched'));
                return ;
            }
        }
        $this->view()->assign('form', $form);
        $this->view()->assign('title', __('Register member'));
    }
}

在上述代码里,首先判断用户两次输入的密码是否一样,如果不一样返回注册页并提示错误,否则就开始插入用户数据。

在插入数据之前,我们把$data里无关的字段全部删除掉,保证数据能正确插入,接着就是执行插入代码,执行完save后,判断是否成功插入,若成功调用成功模板,否则返回注册页。

在输入正确信息并提交后,我们看到数据表里多了一条记录:

fig4-3

图4-3 成功插入一条数据

4.2 完成登陆页面

这部分主要是介绍下如果使用简单的数据库查询操作,在登陆页将会查询用户是否已经注册,并根据密码判断是否登陆成功,若成功,保存用户信息到session里。同时下一次登陆时,根据session自动登陆。

路径:usr/module/member/src/Controller/Front/LoginController.php

Code 4.4.2

<?php
...
use Pi;

class LoginController extends ActionController
{
    ...
    public function loginAction()
    {
        $session   = Pi::service('session')->session;
        $userState = $session->offsetGet('userState');
        if (!empty($userState)) {
            $this->view()->setTemplate(false);
            $this->view()->assign('content', $userState['username'] . ', ' . __('login success!'));
            return ;
        }

        $form = $this->renderForm();

        if ($this->request->isPost()) {
            $post = $this->request->getPost();
            $form->setData($post);
            $form->setInputFilter(new LoginFilter);
            if (!$form->isValid()) {
                $this->view()->assign('form', $form);
                return ;
            }
            $data  = $form->getData();
            $model = $this->getModel('account');
            $row   = $model->find($data['username'], 'username');
            if (!$row->id or $row->password != $data['password']) {
                $this->view()->assign('form', $form);
                $this->view()->assign('error', __('Incorrect username or password, please try again!'));
                return ;
            }
            $session->offsetSet('userState', array('username' => $row->username));
            $session->setExpirationSeconds(30);
            $this->view()->setTemplate(false);
            $this->view()->assign('content', $row->username . ', ' . __('login success!'));
            return ;
        }
        $this->view()->assign('form', $form);
        $this->view()->assign('title', __('Please enter'));
    }
    ...
}

由于这里会用到Pi的API,因此需要引用Pi:(即:use Pi;)。接下来我们先从提交后的逻辑开始看,表单验证通过并过滤后,根据用户名从数据库查询数据,这里采用find()方法保证读出来的是一维数组对象,这样就是再执行一次循环取数据了。然后判断用户是否存在,密码是否正确,若正确,则调用系统的Pi::service('session')接口将用户信息写入session,并设置有效时间为30s。关于session的使用方法,可参考Pi文档,以后的章节里也会具体介绍。

现在让我们看下刚登陆的处理,这里调用Pi::service('session')->session获取Zend\Session\Container实例来处理session,这个实例在登陆成功后保存用户信息的时候也用到。而这里将会调用offsetGet()方法读取session里的用户信息,若存在就显示登陆成功,并跳过后的验证。

需要说明的是,为了让代码更简单,这里采用了明码,并且没有采用Pi的用户认证机制,实际操作中不建议这样处理。

4.3 后台用户管理

这部分通过后台用户的管理功能介绍下其他数据库查询的使用方法。这部分将在后台建立一个列表页,并可以根据条件查询用户。

在src/Controller目录下创建Admin文件夹,并在里面添加AccountController.php:

member
|- src
   |- Controller
      |- Admin
         |- AccountController.php

在AccountController.php里添加如下代码:

路径:usr/module/member/src/Controller/Admin/AccountController.php

Code 4.4.3

<?php
namespace Module\Member\Controller\Admin;

use Pi\Mvc\Controller\ActionController;

class AccountController extends ActionController
{
    public function listAction()
    {
        $username = $this->params('username', null);
        $gender   = $this->params('gender', 0);
        $feedback = $this->params('feedback', '-1');
        $country  = $this->params('country', null);

        $where    = array();
        if (!empty($username)) {
            $where['username like ?'] = '%' . $username . '%';
        } else {
            if (!empty($gender)) {
                $where['gender'] = $gender;
            }
            if ('-1' != $feedback) {
                $where['feedback'] = $feedback;
            }
            if (!empty($country)) {
                $where['country'] = $country;
            }
        }

        $columns = array('id', 'username', 'gender', 'country', 'feedback');
        $model  = $this->getModel('account');
        $select = $model->select()
                        ->columns($columns)
                        ->where($where)
                        ->order('id ASC');
        $rowset = $model->selectWith($select);
        $items  = array();
        foreach ($rowset as $row) {
            $items[$row->id] = $row->toArray();
            $items[$row->id]['feedback'] = $row->feedback ? 'Y' : 'N';
        }

        $this->view()->assign('items', $items);
    }
}

listAction()用于显示用户列表,同时能根据条件进行筛选。首先代码获取通过POST方法提交过来的参数值,这些参数值也就是筛选条件。其中,username字段为模糊查询,它在where数组里以'username like ?'来标识,并且这个查询条件与其他条件不关联。这些条件将会根据用户是否设置添加到$where数组里,我们同时在查询时加入了选择固定的列、按id升序查询。

查询的结果经过处理后,赋给模板变量。接下来我们需要创建模板并添加代码完成结果的显示,在template目录下创建admin目录,并添加account-list.phtml文件。

member
|- template
   |- admin
      |- account-list.phtml

在account-list.phtml里添加如下代码:

路径:usr/module/member/template/admin/account-list.phtml

Code 4.4.4

<form action="<?php $this->url('admin', array('action' => 'list')) ?>" method="POST">
    <label><?php echo __('Gender: ') ?></label>
    <select name="gender">
        <option value="0"><?php echo __('All') ?></option>
        <option value="M"><?php echo __('Male') ?></option>
        <option value="F"><?php echo __('Female') ?></option>
    </select>
    <label><?php echo __('Feedback: ') ?></label>
    <select name="feedback">
        <option value="-1"><?php echo __('All') ?></option>
        <option value="1"><?php echo __('Yes') ?></option>
        <option value="0"><?php echo __('No') ?></option>
    </select>
    <label><?php echo __('Country') ?></label>
    <input name="country" type="text" value="" />
    <input name="submit" type="submit" value="Go" />
</form>

<form action="<?php $this->url('admin', array('action' => 'list')) ?>" method="POST">
    <input name="username" type="text" />
    <input name="submit" type="submit" value="Search" />
</form>

<table>
    <tr>
        <th><?php echo __('Id') ?></th>
        <th><?php echo __('Username') ?></th>
        <th><?php echo __('Gender') ?></th>
        <th><?php echo __('Country') ?></th>
        <th><?php echo __('Feedback') ?></th>
        <th><?php echo __('Operation') ?></th>
    </tr>
    <?php foreach ($items as $item) { ?>
    <tr>
        <td><?php echo $item['id'] ?></td>
        <td><?php echo $item['username'] ?></td>
        <td><?php echo $item['gender'] ?></td>
        <td><?php echo $item['country'] ?></td>
        <td><?php echo $item['feedback'] ?></td>
        <td>
            <a href="<?php echo $this->url('admin', array('action' => 'delete', 'id' => $item['id'])) ?>"><?php echo __('Delete') ?></a>
        </td>
    </tr>
    <?php } ?>
</table>

在模板里,首先定义了几个用于搜索的表单,其中搜索用户名的表单和其他表单分别提交,这保证了搜索用户名时其他条件不会影响到搜索结果。action值的获取通过调用模板的url helper实现,表单提交的方式为POST。对于gender,值为0时忽略该筛选条件,对于feedback,值为-1时,忽略该筛选条件。

模板的下半部分为显示搜索结果列表,这里循环将每条记录输出。在Operation一列,我们添加了一个delete链接,这个链接将请求AccountController的deleteAction(),同时将该行数据的id传递过去,以便程序正确地删除该数据。数据的删除将在下一节里介绍。

设置gender为M以及feedback为Yes,搜索后将得到如下结果:

fig4-4

图4-4 member模块列表页

4.4 删除用户操作

上一节我们在列表页已经预留了删除链接,因此,我们现在只需要添加一个deleteAction就可以了:

路径:usr/module/member/src/Controller/Admin/AccountController.php

Code 4.4.5

<?php
class AccountController extends ActionController
{
...

    public function deleteAction()
    {
        $id = $this->params('id', 0);

        if ($id) {
            $result = $this->getModel('account')->delete(array('id' => $id));
            if (!$result) {
                $this->view()->setTemplate(false);
                $this->view()->assign('content', __('Failed delete data!'));
                return ;
            }
        }

        return $this->redirect()->toRoute('admin', array('action' => 'list'));
    }
}

在deleteAction里,我们首先获取传递过来的id值,若id不为空,就根据id删除该记录,最后调用toRoute()方法返回listAction。

我们删除id为2的用户,得到图4-5的结果,如果试图删除一个id不存在的记录,将会提示删除出错误信息。

fig4-5

图4-5 删除记录

4.5 编辑用户资料

这一节我们将通过完成一个编辑资料页来了解下数据库更新操作。用户资料的编辑需要用户在前台完成,并且需要在登陆的状态下编辑自己的资料。因此我们需要在前台创建一个ProfileController和editAction来完成这些功能。

在src/Controller/Front目录下创建ProfileController并添加如下代码:

路径:usr/module/member/src/Controller/Front/ProfileController.php

Code 4.4.6

<?php
namespace Module\Member\Controller\Front;

use Pi\Mvc\Controller\ActionController;
use Module\Member\Form\RegisterForm;
use Module\Member\Form\RegisterFilter;
use Pi;

class ProfileController extends ActionController
{
    public function editAction()
    {
        $form = new RegisterForm('register');
        $form->setAttribute('action', $this->url('', array('action' => 'edit')));

        if ($this->request->isPost()) {
            $data = $this->request->getPost();
            $form->setData($data);
            $form->setInputFilter(new RegisterFilter);
            if ($form->isValid()) {
                $this->view()->assign('form', $form);
                return ;
            }
            $data     = $form->getData();
            $username = $data['username'];
            $columns  = array('gender', 'country', 'feedback', 'introduction');
            foreach (array_keys($data) as $key) {
                if (!in_array($key, $columns)) {
                    unset($data[$key]);
                }
            }
            $result = $this->getModel('account')->update($data, array('username' => $username));
            if (!$result) {
                $this->view()->assign('form', $form);
                $this->view()->assign('error', __('Failed update data!'));
                return ;
            }
            $this->view()->setTemplate(false);
            $this->view()->assign('content', __('Update successful!'));
            return ;
        } else {
            $session   = Pi::service('session')->session;
            $userState = $session->offsetGet('userState');
            if (empty($userState)) {
                return $this->redirect()->toRoute('', array('controller' => 'login', 'action' => 'login'));
            }

            $row = $this->getModel('account')->find($userState['username'], 'username');
            if (!$row->id) {
                $this->view()->setTemplate(false);
                $this->view()->assign('content', __('User is not exists, please try again!'));
                return ;
            }

            $data = $row->toArray();
            $form->setData($data);
        }

        $this->view()->assign('form', $form);
    }
}

当第一次访问edit页面时,程序会判断该用户是否登陆,并取得用户信息,然后根据用户名从数据库读取数据。setData()方法会将这些数据保存到表单里,显示给用户看。

当用户更新完数据提交后,数据将会先经过验证,如果通过就根据用户名将该用户的信息更新。

我们还需要一个模板,这个模板和注册登陆页一致,只不过要去掉密码和重复密码两项,同时将用户名文本框禁用,不让用户改用户名,这里使用setAttribute()方法实现。在template/front目录下添加profile-edit.phtml文件并添加如下代码:

路径:usr/module/member/template/front/profile-edit.phtml

Code 4.4.7

<h2><?php echo $title; ?></h2>

<?php if (isset($error)) {
    echo $error;
} ?>

<?php echo $this->form()->openTag($form) ?>
<div id="member-register-username">
<?php $element = $form->get('username'); 
    $element->setAttribute('disabled', 'disabled'); ?>
    <div><?php echo $this->formLabel($element) ?></div>
    <div><?php
        echo $this->formElement($element);
        echo $this->formElementErrors($element); ?>
    </div>
</div>

<div id="member-register-gender">
<?php $element = $form->get('gender'); ?>
    <div><?php echo $this->formLabel($element) ?></div>
    <div><?php
        echo $this->formElement($element);
        echo $this->formElementErrors($element); ?>
    </div>
</div>

<div id="member-register-country">
<?php $element = $form->get('country'); ?>
    <div><?php echo $this->formLabel($element) ?></div>
    <div><?php
        echo $this->formElement($element);
        echo $this->formElementErrors($element); ?>
    </div>
</div>

<div id="member-register-feedback">
<?php $element = $form->get('feedback'); ?>
    <div><?php
        echo $this->formElement($element);
        echo $this->formElementErrors($element); ?>
    </div>
</div>

<div id="member-register-introduction">
<?php $element = $form->get('introduction'); ?>
    <div><?php echo $this->formLabel($element) ?></div>
    <div><?php
        echo $this->formElement($element);
        echo $this->formElementErrors($element); ?>
    </div>
</div>

<div id="login-login-submit">
<?php $element = $form->get('submit'); ?>
    <div><?php echo $this->formElement($element) ?></div>
</div>
<?php echo $this->form()->closeTag() ?>

fig4-6

图4-6 更新成功页

  1. Model类的派生

前面我们已经介绍过了,Model类可以根据情况进行扩展,模块的Model就是在src目录下创建Model目录,并添加与表的名字一致的类,对于表名里带有'_',则需要建立多级目录。如pi_member_account只需要创建Account类,而对于pi_member_account_user需要先创建Account子目录,并在Account目录里创建User类。而这个类需要继承自Pi\Application\Model\Model类。

现在我们根据member模块的表,创建如下目录及文件:

member
|- src
   |- Model
      |- Account.php

在Account.php里添加如下代码:

路径:usr/module/member/src/Model/Account.php

Code 4.5.1

<?php
namespace Module\Member\Model;

use Pi\Application\Model\Model;

class Account extends Model
{
    public function prepare($data)
    {
        $data['password'] = md5($data['password']);

        return $this->createRow($data);
    }

    public function authenticate($data)
    {
        $row = $this->find($data['username'], 'username');
        if ($row->password == md5($data['password'])) {
            return $row;
        }

        return false;
    }

    public function getList($where, $columns, $order)
    {
        $select = $this->select()
                    ->columns($columns)
                    ->where($where)
                    ->order($order);
        $rowset = $this->selectWith($select);

        return $rowset;
    }
}

在这个类里,我们提供了三个方法:

  • prepare()

这个方法是将用户注册时的密码进行md5加密,然后再调用createRow()方法创建该数据集的类。

  • authenticate()

这个方法将根据用户登陆时输入的用户名去数据库查找数据,然后将用户密码进行md5加密后与数据库里的比较,若成功则将读取的数据的数组对象返回。

  • getList()

这个方法根据传递过来的条件、要筛选的列和排序方式从数据库里查询数据并返回。

因此,现在我们可以用这三个方法去替换之前的代码。

5.1 修改用户注册页

首先打开Front/RegisterController.php,并将indexAction里的代码作如下修改:

...
$columns = array('id', 'username', 'password', 'gender', 'country', 'feedback', 'introduction');
foreach (array_keys($data) as $key) {
    if (!in_array($key, $columns)) {
        unset($data[$key]);
    }
}
$row = $this->getModel('account')->prepare($data);
$row->save();
if (!$row->id) {
    $this->view()->assign('form', $form);
    $this->view()->assign('error', __('Failed insert user data!'));
    return ;
}
...

这样用户填写密码后,密码将会被加密后写入数据库:

fig4-7

图4-7 经过密码加密处理后的数据

5.2 修改登陆页

当然登陆页也需要作相应的修改,这里就直接调用authenticate()方法,打开Front/LoginController.php,对loginAction作如下修改:

...
$data  = $form->getData();
$model = $this->getModel('account');
$row   = $model->authenticate($data);
if (empty($row)) {
    $this->view()->assign('form', $form);
    $this->view()->assign('error', __('Incorrect username or password, please try again!'));
    return ;
}
...

5.3 修改后台列表页

现在可以直接在listAction里调用getList()方法:

...
$columns = array('id', 'username', 'gender', 'country', 'feedback');
$model  = $this->getModel('account');
$rowset = $model->getList($where, $columns, 'id ASC');
$items  = array();
...

运行代码可以看,结果和以前的一致。对于扩展的类,开发者可以根据自己的需求添加相应的方法。

Clone this wiki locally