-
Notifications
You must be signed in to change notification settings - Fork 0
Tutorial.Database Operation II.zh_cn
- 实例
上面我们基本上详细地介绍了数据库操作的一些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后,判断是否成功插入,若成功调用成功模板,否则返回注册页。
在输入正确信息并提交后,我们看到数据表里多了一条记录:
图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,搜索后将得到如下结果:
图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不存在的记录,将会提示删除出错误信息。
图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() ?>
图4-6 更新成功页
- 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 ;
}
...
这样用户填写密码后,密码将会被加密后写入数据库:
图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();
...
运行代码可以看,结果和以前的一致。对于扩展的类,开发者可以根据自己的需求添加相应的方法。