第9章 结束语

最后更新于:2022-04-01 00:41:42

经过前面8个章节的讲解,本书也就可以说已经完成了他的使命;本书从零开始对ZF2框架的使用进行了一翻的讲解,内容主要包括:下载ZF2框架、搭建适合ZF2运行的服务器环境、ZF2项目的创建方法、模块的配置、模型的建立、模型的配置、控制器的建立、路由的配置、视图模板配置等一系列内容;本书的内容虽然比较简单的,讲法也比较通俗,但他的内容已经基本覆盖了一般普通项目所需的全部要素。 作者本身做为一个开发者,全书的内容也是以一个开发的身份去写、去讲解,尽量使全书的内容表达更直接,更直白,直易于理解,以便达到书名的要求--ZF2 入门教程。这个教程是作者的第一个比较大篇幅的教程,对于写教程的原由在开始写这个文章前已经有所介绍。 由于国内Zend Framework2 的开发资料相对匮乏,对于刚刚接触ZF2的开发来是确实是件很头痛的事,有些时候可能在电脑盲目的弄了几天,但最后还是连个 hello world 都没搞出来。作者希望本书的阅读者可能从这些简单的例子入手,逐渐的去接近ZF2的核心内容。ZF系列的框架其他各种强大的功能作者也不再重述,相信能够接触到ZF系列框架的开发者已经很明确了。 对于本书中的例子作者也做了一一的测试,全部功能都可以正常运行并到预期效果。书中例子如果在某些特定的情况下使用的话,开发者也可以根据自已的需求情况对文中的例子做进行一步扩展与完善。例如模型的功能,开发可以根据不同的环境要求对模型进行重构或直接根据需要重写,还有多国语言支持,如果开发需要做一个大型项目也可以根据项目要求添加各个国家语言。作者在此再交希望阅读者能够从中书的例子举一反三进行大量反复的练习,熟练掌握ZF2框架的使用方法;然后再对ZF2框架的相关底层实现进行有针对性的研究,进而从更大的深度及广度上去使用ZF2框架。在此作者也向阅读者们推荐使用ZF官网上的开发使用手册,如果必需寻找相关的开发例程请使用Google 进行搜索,主要搜索英文网站。通过作者的相关经验ZF还有很多的英文实例的,但中文的例程可能不太多了,即使有中文的例程也免不了你抄我,我抄的习惯,所以在国内搜索出来的文章可以说是大多雷同,基本上就是直接复制粘贴行为,没人进一步的验证例程的可用性;以此同时即浪费了时间,也误导了不少开发者。 ZF官网开发帮助:[http://framework.zend.com/learn/](http://framework.zend.com/learn/) 这里基本上可以找到你所需要的所有的ZF分类的内容,或一些简短的例子。 本书到此结束,作者真心希望此书已经引导您进入了ZF2的世界。祝愿所有开发者一路顺利。谢谢。
';

8.3 引用认证类

最后更新于:2022-04-01 00:41:40

认证类已经建立好,将在AlbumController 中进行引用,以验证认证类是否可用,打开文件:`/module/Album/src/Album/Controller/AlbumController.php`,添加如下两个方法: ~~~ public function authAction(){ $auth = new \Album\Model\MyAuth(); if($auth->auth()) echo "Authentication Success"; else echo "Authentication Failure"; exit; } public function isauthAction(){ $auth = new \Album\Model\MyAuth(); if($auth->isAuth()) echo "Already Authentication Success"; else echo "Authentication Failure"; exit; } ~~~ 代码解释: public function authAction(){} 验证是否可能对指定的用户名与密码进行认证 public function isauthAction(){} 验证持久性验证是否有效 添加代码后,在浏览器中先打开:http://localhost/album/auth 查看是否通过了认证,接着在浏览器打开:http://localhost/album/isauth 查看是否在其他页面也通过了认证。 以上认证的用户名与密码作者固定的设置为了admin,这个可以根据需求进行修改。也可以根据自已的需求对MyAuth的认证类进行改进和扩展以适应具体项目的要求。
';

8.2 新建认证类

最后更新于:2022-04-01 00:41:38

为了方便讲解与引用,将认证类放在Album模块的模型目录下。用户认证的方式有多种,在这里介绍的一种是作者在开发中常用到的一种,数据库认证的持久性认证。ZF2中的持久性认证其本质还是通过Session来实现的,只不过开发者在开发的时候根据就不会察觉到Session在这期间的存在。ZF2在使用持久性认证的时候如果没有对认证空间进行重新命名,ZF2使用使用一个Zend_auth为其Session的默认命名空间,当你在new一个认证的实例的时候系统会自动的找到Zend_auth对应的Session命名空间。在此也可以看认证类与其他类在进行new 的时候可能会有所不同,这种不同也正是由于Session的相关机制所带来的。 添加文件:`/module/Album/src/Album/Model.MyAuth.php`,具体内容如下: ~~~ namespace Album\Model; use Zend\Db\Adapter\Adapter as DbAdapter; use Zend\Authentication\Adapter\DbTable as AuthAdapter; use Zend\Authentication\AuthenticationService; class MyAuth { protected $adapter; public function __construct() { $this->adapter = new DbAdapter(array( 'driver'=>'Pdo_Mysql', 'database'=>'test', 'host'=>'localhost', 'username'=>'root', 'password'=>'' )); } public function auth() { $authAdapter = new AuthAdapter($this->adapter); $authAdapter ->setTableName('user') // 认证的数据表 ->setIdentityColumn('username') // 认证字段 ->setCredentialColumn('password'); // 校验字段 $authAdapter ->setIdentity('admin') // 认证值 ->setCredential('admin');// 校验值 $auth = new AuthenticationService(); $result = $auth->authenticate($authAdapter); if($result->isValid()){ $auth->getStorage()->write($authAdapter->getResultRowObject()); return true; } return false; } public function isAuth(){ $auth = new AuthenticationService(); if($auth->hasIdentity()) return true; return false; } } ~~~ 代码解释: public function auth() {} 进行认证 public function isAuth(){} 通过持久性认证判断是否已经通过认证 $authAdapter = new AuthAdapter($this->adapter); 实例为一个认证适配器 $auth = new AuthenticationService(); 实例化一个认证服务,以实现持久性认证
';

8.1 建立数据表

最后更新于:2022-04-01 00:41:35

在开始前先建立一个数据表用来存放用户的认证字段,通俗的说就是用户名与密码。还使用之前的test数据库,建立一个user表,具体内容如下: ~~~ CREATE TABLE user(id int(10) NOT NULL AUTO_INCREMENT,username varchar(100) NOT NULL,password varchar(1000) NOT NULL,PRIMARY KEY(id)); INSERT INTO news(username,password) VALUES(‘admin’,’admin’); INSERT INTO news(username,password) VALUES(‘test’,’test’); ~~~
';

第8章 用户认证

最后更新于:2022-04-01 00:41:33

用户认证在项目的开发过程是一个不可或缺的重要组成部分,他的作用担负着对整个的项目的合法认证及准入机制。
';

7.8 添加模板文件

最后更新于:2022-04-01 00:41:31

模板是数据处理后的最终展现平台,也是用户操作与感知的入口。Album模块使用的模板有4个:index.phtml , add.phtml , edit.phtml , delete.phtml , paginator.phtml 分别 列表、添加、修改、删除、分页导航 ### 7.8.1 列表模板 index.phtml 添加文件:/module/Album/view/album/album/index.phtml,内容如下: ~~~ <table> <tr> <th><?php echo $this->translate("Title") ?></th> <th><?php echo $this->translate("Artist") ?></th> <th><a href="<?php echo $this->url('album', array('action' => 'add')); ?>"><?php echo $this->translate("Add new album") ?></a></th> </tr> <?php foreach ($paginator as $album) : ?> <tr> <td><?php echo $this->escapeHtml($album->title); ?></td> <td><?php echo $this->escapeHtml($album->artist); ?></td> <td> <a href="<?php echo $this->url('album', array('action' => 'edit', 'id' => $album->id)); ?>"><?php echo $this->translate("Edit") ?></a> <a href="<?php echo $this->url('album', array('action' => 'delete', 'id' => $album->id)); ?>"><?php echo $this->translate("Delete") ?></a> </td> </tr> <?php endforeach; ?> </table> <?php echo $this->paginationControl($this->paginator,'sliding',array('partial/paginator.phtml','Album'),array('route'=>'album')); ?> ~~~ ### 7.8.2 列表模板 add.phtml 添加文件:/module/Album/view/album/album/add.phtml,内容如下: ~~~ <?php $form = $this->form; $form->setAttribute('action',$this->url('album',array('action'=>'add'))); echo $this->form()->openTag($form); echo $this->formCollection($this->form); echo $this->form()->closeTag(); ?> ~~~ ### 7.8.3 列表模板 edit.phtml 添加文件:/module/Album/view/album/album/edit.phtml,内容如下: ~~~ <?php $form = $this->form; $form->setAttribute('action',$this->url('album',array('action'=>'edit','id'=>$this->id))); echo $this->form()->openTag($form); echo $this->formCollection($this->form); echo $this->form()->closeTag(); ?> ~~~ ### 7.8.4 列表模板 delete.phtml 添加文件:/module/Album/view/album/album/delete.phtml,内容如下: ~~~ <?php $title = 'Delete album'; $this->headTitle($title); ?> <h1><?php echo $this->escapeHtml($title); ?></h1> <p>Are you sure that you want to delete '<?php echo $this->escapeHtml($album->title); ?>' by '<?php echo $this->escapeHtml($album->artist); ?>'? </p> <?php $url = $this->url('album', array( 'action' => 'delete', 'id' => $this->id, )); ?> <form action="<?php echo $url; ?>" method="post"> <div> <input type="hidden" name="id" value="<?php echo (int) $album->id; ?>" /> <input type="submit" name="del" value="Yes" /> <input type="submit" name="del" value="No" /> </div> </form> ~~~ ### 7.8.5 列表模板 paginator.phtml 添加文件:/module/Album/view/partial/paginator.phtml,内容如下: ~~~ <?php if ($this->pageCount): ?> <div class="pagination pagination-centered"> <ul> <!-- Previous page link --> <?php if (isset($this->previous)): ?> <li> <a href="<?php echo $this->url($this->route); ?>?page=<?php echo $this->previous;?>"><<</a> </li> <?php else: ?> <li class="disabled"> <a href="#"> << </a> </li> <?php endif; ?> <!-- Numbered page links --> <?php foreach ($this->pagesInRange as $page): ?> <?php if ($page != $this->current): ?> <li> <a href="<?php echo $this->url($this->route); ?>?page=<?php echo $page; ?>"> <?php echo $page; ?> </a> </li> <?php else: ?> <li class="active"> <a href="#"><?php echo $page; ?></a> </li> <?php endif; ?> <?php endforeach; ?> <!-- Next page link --> <?php if (isset($this->next)): ?> <li> <a href="<?php echo $this->url($this->route); ?>?page=<?php echo $this->next; ?>"> >> </a> </li> <?php else: ?> <li class="disabled"> <a href="#"> >> </a> </li> <?php endif; ?> </ul> </div> <?php endif; ?> ~~~ 经过以上的准备工作,接下可以通过 http://localhost/album 来打开album 列表的,打开的页面结果如下所示: ~~~ header Title Artist Add new album First album artist01 EditDelete Second album artist02 EditDelete Third album artist03 EditDelete fourth album artist04 EditDelete Fifth album artist05 EditDelete << 1 2 >> footer ~~~ 点击 列表上面的Add new album 可以进行 album 添加,页面结果如下所示: ~~~ header 窗体顶端 Title窗体底端 Artist footer ~~~ 点击列表右边的 Edit 可以对album 的信息进行修改,页面结果如下所示: ~~~ header 窗体顶端 Title窗体底端 Artist footer ~~~ 点击列表右边的 Delete 可以对album 记录进行删除,确认删除页面如下所示: ~~~ header Delete album Are you sure that you want to delete 'First album' by 'artist01'? 窗体顶端 窗体底端 footer ~~~ 到此已经完成了对整个Album模块的的构造及功能的实现,此实例虽然没有实际的应用意义,但他已经完整的展示了在ZF2一个完整模块的存在形式,以及与其他模块同时并存且同时协同工作的具体应用,在进行具体项目开发的时候可以借鉴或参考此例以便开发出不同功能的模块,使用项目模块能够共同协同工作。
';

7.7 添加控制器 AlbumController

最后更新于:2022-04-01 00:41:28

添加文件:`/module/Album/src/Controller/AlbumController.php` 具体内容如下: ~~~ namespace Album\Controller; use Zend\Mvc\Controller\AbstractActionController; use Zend\View\Model\ViewModel; use Album\Model\Album; use Album\Form\AlbumForm; class AlbumController extends AbstractActionController{ protected $albumTalbe; public function indexAction(){ $paginator = $this->getAlbumTalbe()->fetchAll(true); $paginator->setCurrentPageNumber((int)$this->params()->fromQuery('page',1)); $paginator->setItemCountPerPage(5); return new ViewModel(array('paginator'=>$paginator)); } public function addAction(){ $form = new AlbumForm(); $form->get('submit')->setValue('Add'); $request = $this->getRequest(); if($request->isPost()){ $album = new Album(); $form->setInputFilter($album->getInputFilter()); $form->setData($request->getPost()); if($form->isValid()){ $album->exchangeArray($form->getData()); $this->getAlbumTalbe()->saveAlbum($album); return $this->redirect()->toRoute('album'); } } return array('form'=>$form); } public function editAction(){ $id = (Int) $this->params()->fromRoute('id',0); if(!$id){ return $this->redirect()->toRoute('album',array('action'=>'add')); } try{ $album = $this->getAlbumTalbe()->getAlbum($id); }catch(\Exception $e){ return $this->redirect()->toRoute('album',array('action'=>'index')); } $form = new AlbumForm(); $form->bind($album); $form->get('submit')->setAttribute('value', 'Edit'); $request = $this->getRequest(); if($request->isPost()){ $form->setInputFilter($album->getInputFilter()); $form->setData($request->getPost()); if($form->isValid()){ $this->getAlbumTalbe()->saveAlbum($form->getData()); return $this->redirect()->toRoute('album'); } } return array('id'=>$id,'form'=>$form); } public function deleteAction(){ $id = (Int) $this->params()->fromRoute('id',0); if(!$id){ return $this->redirect()->toRoute('album'); } $request = $this->getRequest(); if($request->isPost()){ $del = $request->getPost('del','No'); if($del=='Yes'){ $id = (Int)$request->getPost('id'); $this->getAlbumTalbe()->deleteAlbum($id); } return $this->redirect()->toRoute('album'); } return array('id'=>$id,'album'=>$this->getAlbumTalbe()->getAlbum($id)); } public function getAlbumTalbe(){ if(!$this->albumTalbe){ $sm = $this->getServiceLocator(); $this->albumTalbe = $sm->get('Album\Model\AlbumTable'); } return $this->albumTalbe; } } ~~~ 代码解释: public function indexAction(){} album默认访问action,也是album列表action public function addAction(){} 添加album 的 action public function editAction(){} 编辑修改album的action public function deleteAction(){} 删除album 的action lpublic function getAlbumTalbe(){} 设置数据库网关
';

7.6 添加表单 AlbumForm

最后更新于:2022-04-01 00:41:26

添加文件:`/module/Album/src/Album/Form/AlbumForm.php`,具体内容如下: ~~~ namespace Album\Form; use Zend\Form\Form; class AlbumForm extends Form{ public function __construct($name=null) { parent::__construct('album'); $this->setAttribute('method', 'post'); $this->add(array( 'name'=>'id', 'type'=>'Hidden' )); $this->add(array( 'name'=>'title', 'type'=>'Text', 'options'=>array( 'label'=>'Title' ), )); $this->add(array( 'name'=>'artist', 'type'=>'Text', 'options'=>array( 'label'=>'Artist' ), )); $this->add(array( 'name'=>'submit', 'type'=>'submit', 'attributes'=>array( 'value'=>'Go', 'id'=>'submit' ), )); } } ~~~
';

7.5 添加模型文件

最后更新于:2022-04-01 00:41:24

模型是ZF2对数据库操作的核心内容,也是进行数据过滤、数据交换的功能专区。 ### 7.5.1 添加 Album.php 此文件包括数据交换、表单数据过滤功能;添加 `/module/Album/src/Album/Model/Album.php` 内容如下: ~~~ namespace Album\Model; use Zend\InputFilter\Factory as InputFactory; use Zend\InputFilter\InputFilter; use Zend\InputFilter\InputFilterAwareInterface; use Zend\InputFilter\InputFilterInterface; class Album implements InputFilterAwareInterface { public $id; public $artist; public $title; protected $inputFilter; public function exchangeArray($data){ $this->id = (isset($data['id'])) ? $data['id'] : null; $this->artist = (isset($data['artist'])) ? $data['artist'] : null; $this->title = (isset($data['title'])) ? $data['title'] : null; } public function getArrayCopy(){ return get_object_vars($this); } public function getInputFilter() { if(!$this->inputFilter){ $this->inputFilter = new InputFilter(); $factory = new InputFactory(); $this->inputFilter->add($factory->createInput(array( 'name'=>'id', 'required'=>true, 'filters'=>array( array('name'=>'Int'), ), ))); $this->inputFilter->add($factory->createInput(array( 'name'=>'artist', 'required'=>true, 'filters'=>array( array('name'=>'StripTags'), array('name'=>'StringTrim'), ), 'validators'=>array( array( 'name'=>'StringLength', 'options'=>array( 'encoding'=>'UTF-8', 'min'=>5, 'max'=>100, ), ), ), ))); $this->inputFilter->add($factory->createInput(array( 'name'=>'title', 'required'=>true, 'filters'=>array( array('name'=>'StripTags'), array('name'=>'StringTrim'), ), 'validators'=>array( array( 'name'=>'StringLength', 'options'=>array( 'encoding'=>'UTF-8', 'min'=>5, 'max'=>100, ), ), ), ))); } return $this->inputFilter; } public function setInputFilter(InputFilterInterface $inputFilter) { throw new \Exception('Not used'); } } ~~~ 代码解释: public function exchangeArray($data){} 数据转换 public function getArrayCopy(){} 克隆对象内属性 public function getInputFilter() {} 过滤器 ### 7.5.2 添加AlbumTable.php 此文件为数据库操作网关,实现对数据库的一系列操作;添加文件:`/module/Album/src/Album/Model/AlbumTable.php`,具体内容如下: ~~~ namespace Album\Model; use Zend\Db\TableGateway\TableGateway; use Zend\Db\ResultSet\ResultSet; use Zend\Db\Sql\Select; use Zend\Paginator\Adapter\DbSelect; use Zend\Paginator\Paginator; class AlbumTable { protected $tableGateway; public function __construct(TableGateway $tg) { $this->tableGateway = $tg; } public function fetchAll($paginated=false) { if($paginated){// 分页 $select = new Select('album'); $rs = new ResultSet(); $rs->setArrayObjectPrototype(new Album()); $pageAdapter = new DbSelect($select,$this->tableGateway->getAdapter(),$rs); $paginator = new Paginator($pageAdapter); return $paginator; } $resultSet = $this->tableGateway->select(); return $resultSet; } public function getAlbum($id) { $id = (int) $id; $rowset = $this->tableGateway->select(array('id'=>$id)); $row = $rowset->current(); if(!$row){ throw new \Exception("Could not find row {$id}"); } return $row; } public function saveAlbum(Album $album) { $data = array( 'artist' =>$album->artist, 'title' =>$album->title ); $id = (int) $album->id; if($id == 0){ $this->tableGateway->insert($data); }else{ if($this->getAlbum($id)){ $this->tableGateway->update($data,array('id'=>$id)); }else{ throw new \Exception("Could not find row {$id}"); } } } public function deleteAlbum($id) { $this->tableGateway->delete(array('id'=>$id)); } } ~~~ 代码解释: public function fetchAll($paginated=false){} 获取数据表中的所有记录 public function getAlbum($id){} 获取指定ID的记录行 public function saveAlbum(Album $album){} 保存数据到数据库 public function deleteAlbum($id){} 删除指定ID的记录行
';

7.4 创建数据表 album

最后更新于:2022-04-01 00:41:22

在此我们仍然后前面章节提到的 test 数据库,在test数据库里添加一个album表并插入数据,具体如下: ~~~ CREATE TABLE album(id int(10) NOT NULL AUTO_INCREMENT,title varchar(100) NOT NULL,artist varchar(1000) NOT NULL,PRIMARY KEY(id)); INSERT INTO news(title,artist) VALUES(‘First album’,’artist01’); INSERT INTO news(title,artist) VALUES(‘Second album’,’artist02’); INSERT INTO news(title,artist) VALUES(‘Third album’,’artist03’); INSERT INTO news(title,artist) VALUES(‘fourth album’,’artist04’); INSERT INTO news(title,artist) VALUES(‘Fifth album’,’artist05’); INSERT INTO news(title,artist) VALUES(‘Sixth album’,’artist06’); ~~~
';

7.3 添加模块配置文件

最后更新于:2022-04-01 00:41:19

模块配置文件主要对路由、视图等进行配置,此处配置关系到整个模块的访问方式及其他使用方式。 添加文件:`/module/Album/config/module.config.php `,添加内容如下: ~~~ return array( 'router' => array( 'routes' => array( 'album' => array( 'type' => 'segment', 'options' => array( 'route' => '/album[/][:action][/:id]', 'constraints' => array( 'action' => '[a-zA-Z0-9_-]*', 'id'=>'[0-9]*' ), 'defaults' => array( 'controller' => 'Album\Controller\Album', 'action' => 'index' ), ), ), ), ), 'controllers' => array( 'invokables' => array( 'Album\Controller\Album' => 'Album\Controller\AlbumController' ), ), 'view_manager' => array( 'template_path_stack' => array( 'album' => __DIR__ . '/../view', ), ), ); ~~~ 代码解释: 'router' => array() 路径配置区块,可以包括有多条路由 'controllers' => array() 控制器配置区块,此处可以配置控制的使用情况 'view_manager' => array() 视图配置区块,此处配置视图存放路径;Album 模块没有再单独使用layout配置,与之前 的Application共用同一们layout布局
';

7.2 添加模块文件

最后更新于:2022-04-01 00:41:17

添加文件:`/module/Album/Module.php`,内容如下: ~~~ namespace Album; use Album\Model\Album; use Album\Model\AlbumTable; use Zend\Db\ResultSet\ResultSet; use Zend\Db\TableGateway\TableGateway; class Module{ public function getAutoloaderConfig(){ return array( 'Zend\Loader\StandardAutoloader'=>array( 'namespaces'=>array( __NAMESPACE__ => __DIR__ . '/src/' . __NAMESPACE__, ), ), ); } public function getConfig(){ return include __DIR__ . '/config/module.config.php'; } public function getServiceConfig() { return array( 'factories'=>array( 'Album\Model\AlbumTable'=>function($sm){ $tg = $sm->get('AlbumTableGateway'); $table = new AlbumTable($tg); return $table; }, 'AlbumTableGateway'=>function($sm){ $adapter = $sm->get('Zend\Db\Adapter\Adapter'); $rs = new ResultSet(); $rs->setArrayObjectPrototype(new Album()); return new TableGateway('album',$adapter,null,$rs); } ), ); } } ~~~ 代码简单解释: public function getAutoloaderConfig(){} 配置文件加载路径 public function getConfig(){} 获取模块配置文件 public function getServiceConfig(){} 获取模块服务配置信息
';

7.1 建立Album 模块

最后更新于:2022-04-01 00:41:15

实例内容通过一个模块来进行讲解,可以更好的了解模块之间的对比性,以及模块与模块之前的耦合性。 ### 7.1.1建立模块目录 目录结构如下: ~~~ /module/Album 模块目录 /module/Album/config 模块配置文件目录 /module/Album/src 模块资源文件目录 /module/Album/src/Album/Controller 控制器文件目录 /module/Album/src/Album/Form 表单文件目录 /module/Album/src/Album/Model 模型文件目录 /module/Album/view 模块模板文件目录 /module/Album/view/album/album 模板文件目录 /module/Album/view/partial 其他通用模板文件目录 ~~~ ### 7.1.2 配置模块全局设置 要让一个新添加的模块加入到ZF2搭建的网站系统中就必需为对新的模块进行设置。 打开全部配置文件:`/config/application.config.php `内容如下: ~~~ return array( 'modules' => array( 'Application', 'Album' // 此行为新加内容 ), 'module_listener_options' => array( 'config_glob_paths' => array( APP_PATH.'config/autoload/{,*.}{global,local}.php', ), 'module_paths' => array( APP_PATH.'module', APP_PATH.'vendor',// 就要应用于phpunit ), ), ); ~~~ 此文件只添加了一个行:在modules 区块中的 ‘Album’;在前面章节的内容已经有说明,每增加一个模块都需要在全局配置文件中添加进行,也就是对模块进行注册使用。
';

第7章 实例应用

最后更新于:2022-04-01 00:41:12

在接下来的内容中将以Album 为实例模块名进行讲解, 读者可能会发现这个名字很熟悉,不错ZF2官网也有一个Album实例类似内容,官网上的对该实例的讲解比较分散,对于ZF2初入门者来说不易掌握;之所以在本章节也以Album 来命名是因为实例的内容符合本书的要求,同时作者也为了使阅读者能够更加清晰及准确掌握相关内容知识要点;此章节的内容阅读者可以与ZF2官网的实例进行对比,找出两者之间的不同点及相同点,但本章节的内容与官网所要表达的结果是一致,都是为让开发者掌握ZF2对数据库的基本操作。本章的所有内容都在前面6章节的内容上完全,这样更能体现出一个网站应用的完整性。
';

6.4 章节总结

最后更新于:2022-04-01 00:41:10

第6章节是综合性的一个章节,章节内容包含从模块配置到建立模型、模板、使用模型、模板等内容。知识要点多,掌握不易,要想能够轻松快捷的使用ZF2框架给开发提供的数据库驱动,就需要不断的练习数据操作类库的使用。本章节内容的的重点及难道就是如何使用ZF2框架提供的数据库驱动对完成对数据库的完全操作,掌握本章节内容至少可以说已经基本完成了对ZF2的入门。为了进一步巩固前面第1章至第6章的内容,在接下来的章节里将再通过两个实例来加强ZF2的开发的重点要点内容。两个实例:一个是ZF2官网的Album实例整合应用;另一个是用户登录验证,使用持久性验证。
';

6.3 自定模型

最后更新于:2022-04-01 00:41:08

在任何一个网站系统中数据库的操作都是一个重心核心问题,在很多时候做为一个开发都有自已已经熟练使用的一套数据库操作类库,使用自已熟悉的类库不仅有助于提高开发效率,也有助于发现问题。在此作者根据自已的使用习惯套用ZF2中的相关数据库操作类库重写了一个实用模型。此节的内容重不在于类库本身,而是通过这个类库来扩展自已的思维,以便日后可以自已的需要重写自已使用的类库。 新建模型文件:`/module/Application/src/Application/Model/NewsModel.php`,文件的内容如下: ~~~ namespace Application\Model; use Zend\Db\Adapter\Adapter; use Zend\Db\Sql\Sql; use Zend\Db\ResultSet\ResultSet; class NewsModel { protected $adapter; /** * 构造函数 * @param Array $config 数据库连接配置 */ public function __construct($config=null) { if($config==null) $this->adapter = new Adapter(array( 'driver'=>'Pdo_Mysql', 'database'=>'test', 'hostname'=>'localhost', 'username'=>'root', 'password'=>'' )); else $this->adapter = new Adapter($config); } /** * 返回查询结果的第一行数据 * @param String $table 操作的数据表名 * @param String $where 查询条件 * @return Array */ public function fetchRow($table,$where=null){ $sql = "SELECT * FROM {$table}"; if($where!=null) $sql .= "WHERE {$where}"; $statement = $this->adapter->createStatement($sql); $result = $statement->execute(); return $result->current(); } /** * 返回查询的所有结果 * @param String $table 数据表名 * @param String $where 查询条件 * @return Array */ public function fetchAll($table,$where=null){ $sql = "SELECT * FROM {$table}"; if($where!=null) $sql .= "WHERE {$where}"; $stmt = $this->adapter->createStatement($sql); $stmt->prepare(); $result = $stmt->execute(); $resultset = new ResultSet; $resultset->initialize($result); $rows = array(); $rows = $resultset->toArray(); return $rows; } /** * 返回指定表的所有数据 * @param String $table 表名 * @return Array */ public function getTableRecords($table) { $sql = new Sql($this->adapter); $select = $sql->select(); $select->from($table); $stmt = $sql->prepareStatementForSqlObject($select); $result = $stmt->execute(); $resultSet = new ResultSet(); $resultSet->initialize($result); return $resultSet->toArray(); } /** * 插入数据到数据表 * @param String $table * @param Array $data * @return Int 返回受影响的行数 */ public function insert($table,$data){ $sql = new Sql($this->adapter); $insert=$sql->insert($table); $insert->values($data); return $sql->prepareStatementForSqlObject($insert)->execute()->getAffectedRows(); } /** * 更新数据表 * @param String $table 数据表名 * @param String $data 需要更新的数据 * @param String|Array $where 更新条件 * @return Int 返回受影响的行数 */ public function update($table,$data,$where){ $sql = new Sql($this->adapter); $update=$sql->update($table); $update->set($data); $update->where($where); return $sql->prepareStatementForSqlObject($update)->execute()->getAffectedRows(); } /** * 删除数据 * @param String $table 数据表名 * @param String|Array $where 删除条件 * @return Int 返回受影响的行数 */ public function delete($table,$where){ $sql = new Sql($this->adapter); $delete = $sql->delete($table)->where($where); return $sql->prepareStatementForSqlObject($delete)->execute()->getAffectedRows(); } /** * 返回最后插入的主键值 * @return Int */ public function lastInsertId(){ return $this->adapter->getDriver()->getLastGeneratedValue(); } } ~~~ 以上代码为一个完整的模型代码,这个模型中使用了多个ZF2中的DB类库来实现不能的功能需求,上面只是一个范例且已经对各个函数方法给出了注释,在此就不对该模型做一一详解。
';

6.2 使用分页导航

最后更新于:2022-04-01 00:41:05

当天新闻记录不断增加的时候,必然导致新闻列表不的加长以致用户不能在一屏内显示完所有内容,致使用户需要不停的拉动滚动条来获取更多的内容,这样无形之中给用户浏览新闻添加了不少障碍;因此对数据进行分页将是必然。之前做过网站的都应该知道分页样式及功能的实现有多种方法,分页可以根据不同的需要进行定制,但有一个缺点就是开发者基本上都需要自已写一个分页类库来加以调用。ZF2为了减少开发自已类库的麻烦ZF2类库本身就已经集成了分页的类库,ZF2提供的分页类库简单易用,开发都也可以根据需要重写分页类或对分类的CSS样式进行重新设定;接下来的内容将重点讲解ZF2分页类库的使用方法。 ### 6.2.1 修改模块配置文件 打开文件 `/module/Application/config/module.config.php`,对news 路由区段块进行修改,具体修改内容如下: ~~~ 'news'=>array( 'type'=>'segment', 'options'=>array( 'route'=>'/news[/][:action][[/:id][/page/:page]]', 'constraints'=>array( 'action'=>'[a-zA-Z]*', 'id'=>'[0-9]+', 'page'=>'[0-9]+', ), 'defaults'=>array( 'controller'=>'Application\Controller\News', 'action'=>'index' ), ), ) ~~~ 修改的地址: 'route'=>'/news[/][:action][/:id]' 修改为 'route'=>'/news[/][:action][[/:id][/page/:page]]' 此处修改的主要作用是为使用路由能匹配出/page/n 这样的分页链接路径 添加page'=>'[0-9]+' 路由正则区别规则 ### 6.2.2 修改模型文件 打开文件 `/module/Application/src/Model/NewsTable.php`,修改public function fetchAll(){}函数,具体内容如下: ~~~ public function fetchAll($paginated=false) { if($paginated){ $select = new Select('news'); $rs = new ResultSet(); $rs->setArrayObjectPrototype(new News()); $pageAdapter = new DbSelect($select,$this->tableGateway->getAdapter(),$rs); $paginator = new Paginator($pageAdapter); return $paginator; } $resultSet = $this->tableGateway->select(); return $resultSet; } ~~~ 代码解释: if($paginated){} 判断是否使用分页 $select = new Select('news'); 实例化一个 select ,对指定表进行操作 $rs = new ResultSet(); 实例化一个结果集,用来保存查询结果 $rs->setArrayObjectPrototype(new News()); 设置结果集的操作属性 $pageAdapter = new DbSelect($select,$this->tableGateway->getAdapter(),$rs); 实例化一个DbSelect,并通过数据网关及select来对数据库进行操作,并将最终结果传递到$rs结果集中 $paginator = new Paginator($pageAdapter); 实例化一个分页导航,并将DbSelect 传递过去 return $paginator; 返回分页导航实例 ### 6.2.3 修改控制器文件 打开文件 `/module/Application/src/Application/Controller/NewsController.php`,对`public function listAction(){}` 方法进行修改,具体内容如下: ~~~ public function listAction(){ $paginator = $this->getNewsTalbe()->fetchAll(true); $paginator->setCurrentPageNumber((int)$this->params()->fromRoute('page',1)); $paginator->setItemCountPerPage(5); return new ViewModel(array('paginator'=>$paginator)); } ~~~ 代码解释: $paginator = $this->getNewsTalbe()->fetchAll(true); 表示使用分页技术进行操作 $paginator->setCurrentPageNumber((int)$this->params()->fromRoute('page',1)); 设置当前页,如果不存在页面则默认设置为第一页 $paginator->setItemCountPerPage(5);设置每个分页将显示的记录行数 return new ViewModel(array('paginator'=>$paginator)); 将分页导航对象返回给模板调用 ### 6.2.4 添加分页导航模板 添加文件 `/module/Application/view/application/partial/parginator.phtml`, 具体内容如下: ~~~ <?php if ($this->pageCount): ?> <div class="pagination pagination-centered"> <ul> <!-- Previous page link --> <?php if (isset($this->previous)): ?> <li> <a href="<?php echo $this->url($this->route) . $this->action; ?>/page/<?php echo $this->previous;?>"><<</a> </li> <?php else: ?> <li class="disabled"> <a href="#"> << </a> </li> <?php endif; ?> <?php foreach ($this->pagesInRange as $page): ?> <?php if ($page != $this->current): ?> <li> <a href="<?php echo $this->url($this->route) . $this->action; ?>/page/<?php echo $page; ?>"> <?php echo $page; ?> </a> </li> <?php else: ?> <li class="active"> <a href="#"><?php echo $page; ?></a> </li> <?php endif; ?> <?php endforeach; ?> <?php if (isset($this->next)): ?> <li> <a href="<?php echo $this->url($this->route) . $this->action; ?>/page/<?php echo $this->next; ?>"> >> </a> </li> <?php else: ?> <li class="disabled"> <a href="#"> >> </a> </li> <?php endif; ?> </ul> </div> <?php endif; ?> ~~~ 代码解释: if ($this->pageCount) 判断分页数量决定是否显示分页导航 if (isset($this->previous)) 判断是否有上一页 <a href="<?php echo $this->url($this->route) . $this->action; ?>/page/<?php echo $this->previous;?>"><<</a>上一页链接 foreach ($this->pagesInRange as $page) 循环首页链接页码 <a href="<?php echo $this->url($this->route) . $this->action; ?>/page/<?php echo $page; ?>"><?php echo $page; ?></a>生成各页的导航链接 if (isset($this->next)) 判断是否有下一页 <a href="<?php echo $this->url($this->route) . $this->action; ?>/page/<?php echo $this->next; ?>">>></a>下一页链接 ### 6.2.4 修改新闻列表模板 打开文件 `/module/Appliction/view/application/news/list.phtml`,在此文件末尾添加收下内容: ~~~ <?php echo $this->paginationControl($this->paginator,'sliding',array('application/partial/paginator.phtml','News'),array('route'=>'news','action'=>'list')); ?> ~~~ 此内容的主要功能是将分页模板输出。 经过以上内容的添加修改整合后,现在可以通过 http://localhost/news/list 看到新的新闻列表页,与之前唯一的不同之处就是有分页导航条了,各个可能点击分页的页面数字对各个页面进行切换显示。结果如下: ~~~ header Title Content Add news First news This is the first news Edit Delete Second news This is the second news Edit Delete Third news This is the third news Edit Delete fourth news This is the fourth news Edit Delete Fifth news This is the fifth news Edit Delete << 1 2 >> footer ~~~
';

6.1 ORM 对象映射法

最后更新于:2022-04-01 00:41:03

ORM 对象映射法是在ZF2开发指南中引用的一种模型编写方法,可以当作是ZF2的推荐写法,此方法的实现主要通过TableGateway(作者称为数据库网关);此方法通过Di来实现,对其进行引用前需要对他做相关配置工作;总的来说引用简单、模型与模块关联性较强。 在编写模型代码前先进行数据表的设计,数据表创建在Mysql数据库的test默认数据库里表名为news;以下里数据表的设计及多条测试数据。 ~~~ CREATE TABLE news (id int(10) NOT NULL AUTO_INCREMENT,title varchar(100) NOT NULL,content varchar(1000) NOT NULL,PRIMARY KEY(id)); INSERT INTO news(title,content) VALUES(‘First news’,’This is the first news’); INSERT INTO news(title,content) VALUES(‘Second news’,’This is the second news’); INSERT INTO news(title,content) VALUES(‘Third news’,’This is the third news’); INSERT INTO news(title,content) VALUES(‘fourth news’,’This is the fourth news’); INSERT INTO news(title,content) VALUES(‘Fifth news’,’This is the fifth news’); INSERT INTO news(title,content) VALUES(‘Sixth news’,’This is the sixth news’); ~~~ 已经有了数据库、数据表、数据需要对数据库的访问属性(数据库适配器Adapter)进行设置后模型才能够正常的连接到我们的数据库,找到文件 `/config/autoload/global.php` 文件内容如下: ~~~ return array( 'db' => array( 'driver' => 'Pdo', 'dsn' => 'mysql:dbname=test;host=localhost', 'driver_options' => array( PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES \'UTF8\'' ), ), 'service_manager' => array( 'factories' => array( 'Zend\Db\Adapter\Adapter' => 'Zend\Db\Adapter\AdapterServiceFactory' ), ), ); ~~~ * db 表示数据库配置信息节点 * driver 表示数据库使用的驱动程序类型 * dsn 数据库连接串,也称为数据源 * driver_options 数据库驱动选项 * service_manager 表示服务器管理器节点 * factories 表示服务器管理器需要加载的工厂类 为要安全起见,将数据库的用户名与密码写入到 /config/autoload/local.php 文件,你同样也可以将他写入到global文件的db 节点中。local.php文件内容如下: ~~~ return array( 'db' => array( 'username' => 'root', 'password' => '' ), ); ~~~ ### 6.1.1 创建 News 类 News 类主要包括数据表中个各字段的映射,以及实现数组与对象之间的数据转换 路径:`/module/Application/src/Application/Model/News.php` 在文件中添加收下代码: ~~~ namespace Application\Model; class News { public $id; public $title; public $content; public function exchangeArray($data){ $this->id = (isset($data['id'])) ? $data['id'] : null; $this->artist = (isset($data['title'])) ? $data['title'] : null; $this->title = (isset($data['content'])) ? $data['content'] : null; } public function getArrayCopy(){ return get_object_vars($this); } } ~~~ 代码讲解: public $id,$title,$content 这些公共变量与数据表字段一一对应 public function exchangeArray($data) 对数组数据进行转换或都说是提取数组数据 public function getArrayCopy() 将类属性转化为一个关联数组,方便后续的使用 ### 6.1.2 创建 NewsTable 类 NewsTable 类的主要是通过TableGateway 数据网关来实现对数据库操作。 路径:`/module/Application/src/Application/Model/NewsTable.php` 在文件中添加以下代码: ~~~ namespace Application\Model; use Zend\Db\TableGateway\TableGateway; use Zend\Db\ResultSet\ResultSet; use Zend\Db\Sql\Select; class NewsTable { protected $tableGateway; public function __construct(TableGateway $tg) { $this->tableGateway = $tg; } public function fetchAll() { $resultSet = $this->tableGateway->select(); return $resultSet; } } ~~~ public function __construct(TableGateway $tg) 构造函数 public funciton fetchAll() 获取数据表的数据 ### 6.1.3 使用模型读取数据库数据 在使用模型的时候需要对其他进行模块配置,以便ZF2能够地运行的时候自动加载。 #### 6.1.3.1 模块配置 找到文件 `/module/Application/Module.php` ,在添加函数的时候注意导入相关的命名空间,添加函数 `public function getServiceConfig(){}`,函数名称是固定的,ZF2会在运行的时候自动调用Module 中的全部方法。添加内容后的文件如下: ~~~ namespace Application; use Zend\Mvc\ModuleRouteListener; use Zend\Mvc\MvcEvent; use Zend\Db\ResultSet\ResultSet; use Zend\Db\TableGateway\TableGateway; use Application\Model\News; use Application\Model\NewsTable; class Module { public function onBootstrap(MvcEvent $e){ $eventManager = $e->getApplication()->getEventManager(); $moduleRouteListener = new ModuleRouteListener(); $moduleRouteListener->attach($eventManager); } public function getConfig(){ return include __DIR__ . '/config/module.config.php'; } public function getAutoloaderConfig(){ return array( 'Zend\Loader\StandardAutoloader'=>array( 'namespaces'=>array( __NAMESPACE__ => __DIR__ . '/src/' . __NAMESPACE__ ) ) ); } public function getServiceConfig(){ return array( 'factories'=>array( 'Application\Model\NewsTable'=>function($sm){ $tg = $sm->get('NewsTableGateway'); $table = new NewsTable($tg); return $table; }, 'NewsTableGateway'=>function($sm){ $adapter = $sm->get('Zend\Db\Adapter\Adapter'); $rs = new ResultSet(); $rs->setArrayObjectPrototype(new News()); return new TableGateway('news',$adapter,null,$rs); } ), ); } } ~~~ 通过以上的函数就配置好了模块对模型的引用,从函数getServiceConfig 的内容中可以看出函数本身只返回一个关联数组,这个关联数据的 键-值 都将在后续中被引用;同时也可以看出我们目录的配置是针对news 表的操作,也是为什么我们在上面的模型中fetchAll()函数里没有看到数据表的原因。 #### 6.1.3.2 控制器中使用模型 找到文件 `/module/Application/src/Application/Controller/NewsController.php`,添加函数 `public function getNewsTable(){}`,同时修改 `public function listAction(){}`函数内容,注意导入相关包;文件修改后如下: ~~~ namespace Application\Controller; use Zend\Mvc\Controller\AbstractActionController; use Zend\View\Model\ViewModel; use Application\Model\NewsTable; class NewsController extends AbstractActionController{ protected $newsTalbe; public function __construct(){ } public function indexAction(){ $view = new ViewModel(); return $view; } public function listAction(){ $paginator = $this->getNewsTalbe()->fetchAll(); var_dump($paginator); exit; } public function addAction(){ echo 'NewsController addAction'; exit; } public function editAction(){ echo 'NewsController editAction'; exit; } public function deleteAction(){ echo 'NewsController deleteAction'; exit; } public function getNewsTalbe(){ if(!$this->newsTalbe){ $sm = $this->getServiceLocator(); $this->newsTalbe = $sm->get('Application\Model\NewsTable'); } return $this->newsTalbe; } } ~~~ public function getNewsTalbe(){} 的主要工作就是完成对数据网关的实例化 $sm = $this->getServiceLocator() 获取本地已经初化的服务管理器及服务 $this->newsTalbe = $sm->get('Application\Model\NewsTable') 获取在模块文件中的相关函数 $paginator = $this->getNewsTalbe()->fetchAll() 通过模型(数据网关)访问数据库 通过添加以上代码就可以通过 http://localhost/news/list 来查看模型对数据库的相关操作信息了。在此处只是通过 var_dump 函数对模型的操作结果进行打印输出,而并没有通过模板来呈现;要想通过模板来呈现模型对数据库查询的结果还需要进行一些小的修改。 #### 6.1.3.3 通过模板显示数据库查询结果 模板是汇集网站应用所有操作的一个最终集合点,最终将所有汇集的数据集中展现给用户。在使用模板前我们还得修改下控制器,以便控制器能将模型操作的结果传递到模板中去。修改 listAction 控制器内容为: ~~~ public function listAction(){ $paginator = $this->getNewsTalbe()->fetchAll(); $view = new ViewModel(); $view->setTemplate('application/news/list.phtml'); $view->setVariable('paginator', $paginator); return $view; } ~~~ $paginator = $this->getNewsTalbe()->fetchAll() 获取模型查询的数据 $view = new ViewModel() 实例化一个视图模型 $view->setTemplate('application/news/list.phtml') 设置视图模型所使用的模板 $view->setVariable('paginator', $paginator) 给视图传递数据 return $view 将视图模型返回给前端控制器 或者是使用以下代码: ~~~ public function listAction(){ $paginator = $this->getNewsTalbe()->fetchAll(); return new ViewModel(array('paginator'=>$paginator)); } ~~~ 以后两种方法的最终结果是一样的。 接下来修改我们的模板文件 `/module/Application/view/application/news/list.phtml`,模板的内容如下: ~~~ <table> <tr> <th>Title</th> <th>Content</th> <th>Add news</a></th> </tr> <?php foreach ($paginator as $news) : ?> <tr> <td><?php echo $this->escapeHtml($news->title); ?></td> <td><?php echo $this->escapeHtml($news->content); ?></td> <td> <a href="<?php echo $this->url('news', array('action' => 'edit', 'id' => $news->id));?>"><?php echo $this->translate("Edit") ?></a> <a href="<?php echo $this->url('news', array('action' => 'delete', 'id' => $news->id));?>"><?php echo $this->translate("Delete") ?></a> </td> </tr> <?php endforeach; ?> </table> ~~~ foreach ($paginator as $news) 使用foreach 来循环模型查询结果的数据行 echo $this->escapeHtml($news->title) 通过对象操作方式输出新闻标题 echo $this->escapeHtml($news->content) 通过对象操作方式输出新闻内容 echo $this->url('news', array('action' => 'edit', 'id' => $news->id)) 通过url 方法构造编辑新的链接 echo $this->url('news', array('action' => 'delete', 'id' => $news->id)) 通过url 方法构造删除新的链接 现在通过 http://localhost/news/list 看看是不是已经把之前我们插入到数据的数据已经全部输出了呢。结果如下所示: ~~~ header Title Content Add news First news This is the first news Edit Delete Second news This is the second news Edit Delete Third news This is the third news Edit Delete fourth news This is the fourth news Edit Delete Fifth news This is the fifth news Edit Delete Sixth news This is the sixth news Edit Delete footer ~~~ #### 6.1.3.4 插入数据 插入数据的功能通过添加新闻的方式来进行讲解,在使用插入数据的功能时同时涉及到过滤器、表单生成的相关内容,本小节将这三个内容进行结合讲解。 6.1.3.4.1 创建表单文件 添加表单文件,路径:`/module/Application/src/Application/Form/NewsForm.php` 内容如下: ~~~ namespace Application\Form; use Zend\Form\Form; class NewsForm extends Form{ public function __construct($name='news') { parent::__construct($name); $this->setAttribute('method', 'post'); $this->add(array( 'name'=>'id', 'type'=>'Hidden' )); $this->add(array( 'name'=>'title', 'type'=>'Text', 'options'=>array( 'label'=>'Title' ), )); $this->add(array( 'name'=>'content', 'type'=>'Text', 'options'=>array( 'label'=>'Content' ), )); $this->add(array( 'name'=>'submit', 'type'=>'submit', 'attributes'=>array( 'value'=>'Go', 'id'=>'submit' ), )); } } ~~~ 代码解析: public function __construct($name='news') 就是一个普通的构造函数,$name 为表单名称 $this->setAttribute('method', 'post') 设置表单属性 $this->add(array('name'=>'id','type'=>'Hidden')); 添加一个表单隐藏域,作为新闻ID $this->add(array('name'=>'title','type'=>'Text','options'=>array('label'=>'Title' ))); 添加一个input 标签,作为新闻标题输入 $this->add(array('name'=>'content','type'=>'Text','options'=>array('label'=>'Content'))); 添加一个input标签,作为新闻内容输入 $this->add(array('name'=>'submit','type'=>'submit','attributes'=>array('value'=>'Go','id'=>'submit'))); 添加一个提交按钮 以上代码就包含了一个新闻记录所需的全部表单元素。 6.1.3.4.2 添加过滤器 文件:`/module/Application/src/Application/Model/News.php` 在此文件原来的基础上添加了内容,文件内容: ~~~ namespace Application\Model; use Zend\InputFilter\Factory as InputFactory;// 新加导入包 use Zend\InputFilter\InputFilter;// 新加导入包 use Zend\InputFilter\InputFilterAwareInterface;// 新加导入包 use Zend\InputFilter\InputFilterInterface;// 新加导入包 class News implements InputFilterAwareInterface {// 添加了接口 public $id; public $content; public $title; protected $inputFilter; public function exchangeArray($data){ $this->id = (isset($data['id'])) ? $data['id'] : null; $this->content = (isset($data['content'])) ? $data['content'] : null; $this->title = (isset($data['title'])) ? $data['title'] : null; } public function getArrayCopy(){ return get_object_vars($this); } public function getInputFilter() {// 新添加,实现接口方法 if(!$this->inputFilter){ $this->inputFilter = new InputFilter(); $factory = new InputFactory(); $this->inputFilter->add($factory->createInput(array( 'name'=>'id', 'required'=>true, 'filters'=>array( array('name'=>'Int'), ), ))); $this->inputFilter->add($factory->createInput(array( 'name'=>'content', 'required'=>true, 'filters'=>array( array('name'=>'StripTags'), array('name'=>'StringTrim'), ), 'validators'=>array( array( 'name'=>'StringLength', 'options'=>array( 'encoding'=>'UTF-8', 'min'=>5, 'max'=>100, ), ), ), ))); $this->inputFilter->add($factory->createInput(array( 'name'=>'title', 'required'=>true, 'filters'=>array( array('name'=>'StripTags'), array('name'=>'StringTrim'), ), 'validators'=>array( array( 'name'=>'StringLength', 'options'=>array( 'encoding'=>'UTF-8', 'min'=>5, 'max'=>100, ), ), ), ))); } return $this->inputFilter; } public function setInputFilter(InputFilterInterface $inputFilter) {// 新添加,实现接口方法 throw new \Exception('Not used'); } ~~~ 代码解析: public function getInputFilter() 获取收入类型过滤器,对指定的表单元素进行过滤。 $this->inputFilter = new InputFilter(); 实例化一个InputFilter过滤器 $factory= new InputFactory(); 实例化一个InputFactory 输入工厂 $this->inputFilter->add($factory->createInput(array('name'=>'id','required'=>true,'filters'=>array(array('name'=>'Int'))))); 创建过滤规则并将附加到InputFilter上,规则内容:name为id的标签为必填项,并且限制为整形输入 $this->inputFilter->add($factory->createInput(array('name'=>'content','required'=>true,'filters'=>array(array('name'=>'StripTags'),array('name'=>'StringTrim'))'validators'=>array(array('name'=>'StringLength','options'=>array('encoding'=>'UTF-8','min'=>5,'max'=>100))))));建过滤规则并将附加到InputFilter上,此处的过滤规则为一个过滤链,规则内容:name 为 content的标签为必填项,并对其他输入进行去HTML标签(StripTags)和去空格(StringTrim)处理,同时对输入内容进一步校验,校验规则为将输入内容限制为utf-8,同时长度为5~100的个字符。 public function setInputFilter(InputFilterInterface $inputFilter) 设置过滤,实现接口的方法 6.1.3.4.3 创建表单 通过上面两个小节的内容已经完成了创建表单的基本要素,下面将通过控制器中的方法来引用上面的内容来生成一个新闻表单。 打开文件:`/module/Application/src/Application/Controller/NewsController.php`,添加如下内容: 导入包 ~~~ use Application\Form\NewsForm; use Application\Model\News; ~~~ 修改public function addAction(){} 函数内容,具体内容如下: ~~~ public function addAction(){ $form = new NewsForm(); $form->get('submit')->setValue('Add'); $request = $this->getRequest(); if($request->isPost()){ $news= new News(); $form->setInputFilter($news->getInputFilter()); $form->setData($request->getPost()); if($form->isValid()){ $album->exchangeArray($form->getData()); $this->getNewsTalbe()->saveNews($news); return $this->redirect()->toRoute('news');// 或者使用URL$this->redirect()->toUrl('/news/list'); } } return array('form'=>$form); } ~~~ addAction 函数内容代码解释: $form = new NewsForm(); 实例化一个新闻表单 $form->get('submit')->setValue('Add');修改新闻表单的提交按钮名称 $request = $this->getRequest(); 获取用户请求 if($request->isPost()){} 判断 是否为 POST请求 $form->setInputFilter($news->getInputFilter()); 为表单添加过滤器 $form->setData($request->getPost()); 设置表单数据 if($form->isValid()){} 判断表单是否通过校验 $news->exchangeArray($form->getData()); 能表单数据进行转换 $this->getNewsTalbe()->saveNews($news); 通过模型将表单提交的数据保存到数据库里 return $this->redirect()->toRoute('news'); 实现路由跳转 return array('form'=>$form); 返回一个表单对象 6.1.3.4.4 模板输出表单 收到从控制器中传递过来数据并将数据在模板中输出,打开文件:/module/Application/view/application/news/add.phtml,文件具体内容如下: $form = $this->form; // 接收到控制器传递过来的表单对象 $form->setAttribute('action',$this->url('news',array('action'=>'add')));// 设置表单的action属性 echo $this->form()->openTag($form);// 打开form表单 echo $this->formCollection($this->form);// 输出表单里的元素集合 echo $this->form()->closeTag();// 闭合form表单 此处是使用简洁法输出表单,即通过打开表单,输出表单、闭合表单这个动作一次性把表单里的所有元素输出。这种方法的好处是只用3行代码就能把表单里的全部元素输出,缺点就是全部属性都使用$form对象的默认设置属性,灵活度没那么好。另一种表单输出的方法就是对$form表单对象里的元素一个一个输出,并且可以对表单对象元素进行相关修改,灵活度较好,但代码量较大。 通过前面四节的课内容现在可以通过 http://localhost/news/add 打开新闻表单了,并可以通过表单将将数据提交到数据库进行保存。页面结果如下: ~~~ header 窗体顶端 Title窗体底端 Content footer ~~~ 6.1.3.4.5 添加模型方法saveNews 要把新闻表单的数据能够提交到数据库中进行保存,还需要在模型中添加保存新闻的模型方法,打开模型文件 `/module/Application/src/Application/Model/NewsTables.php` 文件,添加如下方法: ~~~ public function saveNews(News $news) { $data = array( 'content' =>$news->content, 'title' =>$news->title ); $id = (int) $news->id; if($id == 0){ $this->tableGateway->insert($data); }else{ if($this->getNews($id)){ $this->tableGateway->update($data,array('id'=>$id)); }else{ throw new \Exception("Could not find row {$id}"); } } } ~~~ 代码解释: $data = array( 'content' =>$news->content,'title' =>$news->title); 将传递过来的数据保存到数组中,因为在ZF2中对数据的操作很多是通过数组来传递的 $this->tableGateway->insert($data); 如果id不存在的时候将数据里的数据插入到数据库,此处实现插入功能 $this->tableGateway->update($data,array('id'=>$id)); 如果id存在的时候,对数据库里指定id的数据行进行更新 throw new \Exception("Could not find row {$id}"); 如果更新出现错误则抛出一个异常 public function saveNews(News $news){} 方法说明 ,此方法不单用来保存添加新闻时的数据,也将用来保存更新新闻内容后的数据,即包含了插入和更新功能。 模型方法saveNews 建立好后就可以通过 http://loaclhost/news/add 来添加新闻并保存到数据库了。 6.1.3.4.6 修改新闻内容 上面一节内容已经讲解了怎么通过表单将一个新插入到数据库里,接下来就是要实现如果使用表单来修改一条新闻记录并将他保存到数据库。在前一节讲解内容的时候已经说过 saveNews 保存数据功能不仅用于添加新闻,也用于新闻的修改,表单也是重用之前内容的表单,所以这些部分的内容就不再重复进行讲解。下面将重点放在控制器的 editAction方法和edit.phtml模板中。 6.1.3.4.6.1修改模块路由 在继续制作editAction 和 edit.phtml 前我需要对我们的module.config.php 的模块文件做一个小的修改,在修改前可以看一下之前输出的新闻列表的最后一个列中 Edit 种 Delete 的链接,看看链接地址的后面是不是没有出现我们平时做网站时应该出现的id 值。这是由于我们之前对模块路由的配置中并没有包括对参数传递的功能,如果路由上没有配置这些传递参数的功能,即使你强行在链接地址的后面加上去也会被路由匹配规则给过滤掉,最终可能导致一个404的错误出现。 打开文件:/module/Application/config/module.config.php 将路由 news 区段修改为如下内容: ~~~ 'news'=>array( 'type'=>'segment', 'options'=>array( 'route'=>'/news[/][:action][/:id]', 'constraints'=>array( 'action'=>'[a-zA-Z]*', 'id'=>'[0-9]+' ), 'defaults'=>array( 'controller'=>'Application\Controller\News', 'action'=>'index' ), ), ), ~~~ 路由做过调整的地方: 'route'=>'/news[/][:action]' 修改为 route'=>'/news[/][:action][/:id]', 'id'=>'[0-9]+' 添加了路由中id 的匹配规则,只匹配数字类型的id 添加模型方法 public function getNews($id){},此方法功能是根据$id查找数据库中的新闻记录并返回查询结果行。打开文件:`/module/Application/src/Application/Model/NewsTable.php` 在文件原来的基础上添加如下内容: ~~~ public function getNews($id){ $id = (int) $id; $rowset = $this->tableGateway->select(array('id'=>$id)); $row = $rowset->current(); if(!$row){ throw new \Exception("Could not find row {$id}"); } return $row; } ~~~ 模型方法内容解释: $id = (int) $id; 将传递过来的id强制转换为整形 $rowset = $this->tableGateway->select(array('id'=>$id)); 根据id查询新闻结果集 $row = $rowset->current(); 取出结果集的第一行记录 if(!$row){} 判断是否存在指定id 的新闻记录行,如果不存在则抛出一个异常 return $row 返回查询结果的新闻记录行 6.1.3.4.6.2修改editAction 方法 打开文件:`/module/Application/src/Application/Controller/NewsController.php`,找到editAction 方法并将内容修改为如下: ~~~ public function editAction(){ $id = (Int) $this->params()->fromRoute('id',0); if(!$id){ return $this->redirect()->toRoute('news',array('action'=>'add')); } try{ $news = $this->getNewsTalbe()->getNews($id); }catch(\Exception $e){ return $this->redirect()->toRoute('news',array('action'=>'list')); } $form = new NewsForm(); $form->bind($news); $form->get('submit')->setAttribute('value', 'Edit'); $request = $this->getRequest(); if($request->isPost()){ $form->setInputFilter($news->getInputFilter()); $form->setData($request->getPost()); if($form->isValid()){ $this->getNewsTalbe()->saveNews($news); $this->redirect()->toUrl('/news/list'); } } return array('id'=>$id,'form'=>$form); } ~~~ 代码解释: $id = (Int) $this->params()->fromRoute('id',0); 从路由中分离id,也就是获取新闻id if(!$id){} 如果id 不存在则直接跳转到添加新闻页面 $news = $this->getNewsTalbe()->getNews($id); 通过数据网关获取指定id的新闻记录 return $this->redirect()->toRoute('news',array('action'=>'list')); 如果在获取新闻记录中出现异常则直接跳转到列表页 $form = new NewsForm(); 实例化一个新闻表单 $form->bind($news); 给表单绑定数据 $form->get('submit')->setAttribute('value', 'Edit');设置表单提交按钮名称 $request = $this->getRequest(); 获取用户请求 if($request->isPost()){} 判断是否通过post提交的请求 $form->setInputFilter($news->getInputFilter()); 为表单添加过滤器 $form->setData($request->getPost());为表单附加数据 if($form->isValid()){} 判断表单数据是否通过校验 $this->getNewsTalbe()->saveNews($news);将编辑后的数据更新到数据库 $this->redirect()->toUrl('/news/list'); 跳转到新闻列表 return array('id'=>$id,'form'=>$form); 返回一个表单对象和新闻id到模板,此处的表单对象与前面章节中插入数据的表单有所区别,此表单里面的标签都已经有数据的了,而之前插入新闻的表单只是一个空的表单。 6.1.3.4.6.3修改edit.phtml模板 打开文件:`/module/Applicaiton/view/application/news/edit.phtml`,将文件内容修改为如下: ~~~ $form = $this->form; $form->setAttribute('action',$this->url('news',array('action'=>'edit','id'=>$this->id))); // 设置表单的action 属性 echo $this->form()->openTag($form);// 打开form 表单 echo $this->formCollection($this->form);// 生成表单元素 echo $this->form()->closeTag();// 关闭表单 ~~~ 到目前为止就已经完成了新闻修改功能的全部工作,现在可以通过新闻列表中的 Edit 链接来打开修改新闻的页面了,修改新闻的页面与添加新闻的页面外观上看上去是一样的;只不过新闻修改页面多了一重判断,当指定id的新闻记录存在时则可以进行修改,如果指定的id还在,则进行的是添加功能。 6.1.3.4.7 删除新闻记录 本节将讲解关于数据库CURD中的最后一个是重要环节--数据库的删除操作,本章节所讲解的主要任务是实现对指定新闻id的删除功能。 6.1.3.4.7.1修改deleteAction 方法 打开文件:`/module/Application/src/Application/Controller/NewsController.php`,找到deleteAction 方法并将内容修改为如下: ~~~ public function deleteAction(){ $id = (Int) $this->params()->fromRoute('id',0); if(!$id){ $this->redirect()->toUrl('/news/list'); } $request = $this->getRequest(); if($request->isPost()){ $del = $request->getPost('del','No'); if($del=='Yes'){ $id = (Int)$request->getPost('id'); $this->getNewsTalbe()->deleteNews($id); } $this->redirect()->toUrl('/news/list'); } return array('id'=>$id,'news'=>$this->getNewsTalbe()->getNews($id)); } ~~~ 代码解释: $id = (Int) $this->params()->fromRoute('id',0) 获取新闻记录id if(!$id){$this->redirect()->toUrl('/news/list');} 判断是否有传递id 值,如果没有则直接跳转到新闻列表页面 if($request->isPost()){} 判断用户请求类型是否为post 请求 $del = $request->getPost('del','No'); 获取用户处理动作{Yes或No} if($del=='Yes'){} 如果用户操作就连Yes,则进行删除操作 $id = (Int)$request->getPost('id'); 获取新闻id $this->getNewsTalbe()->deleteNews($id); 删除指定的新闻记录 $this->redirect()->toUrl('/news/list'); // 完成删除后跳转到新闻列表 return array('id'=>$id,'news'=>$this->getNewsTalbe()->getNews($id)); 如果用户请求为非post 请求,则返回数据给模板 6.1.3.4.7.2添加模型 deleteNews方法 打开模型文件 `/module/Application/src/Application/Model/NewsTables.php` 文件,添加如下方法: ~~~ public function deleteNews($id){ $this->tableGateway->delete(array('id'=>$id)); } ~~~ 代码解释: $this->tableGateway->delete(array('id'=>$id)); 根据传递过来的id删除新闻记录 6.1.3.4.7.2修改delete.phtml模板 打开文件:/module/Applicaiton/view/application/news/delete.phtml,将文件内容修改为如下: ~~~ $title = 'Delete news'; $this->headTitle($title); ?> <h1><?php echo $this->escapeHtml($title); ?></h1> <p>Are you sure that you want to delete '<?php echo $this->escapeHtml($news->title); ?>' by '<?php echo $this->escapeHtml($news->content); ?>'? </p> <?php $url = $this->url('news', array( 'action' => 'delete', 'id' => $this->id, )); ?> <form action="<?php echo $url; ?>" method="post"> <div> <input type="hidden" name="id" value="<?php echo (int) $news->id; ?>" /> <input type="submit" name="del" value="Yes" /> <input type="submit" name="del" value="No" /> </div> </form> ~~~ 代码解释: $this->headTitle($title); 设置文件标题 echo $this->escapeHtml($news->title); 输出新闻标题 echo $this->escapeHtml($news->content); 输出新闻内容 $url = $this->url('news', array('action' => 'delete','id' => $this->id)); 构造表单的action链接 以上为主要的php内容,致以表单中其他的html代码就不再做解释。下面转到新闻列表页面,http://localhost/news/list 在新闻列表中点击Delete将跳转到删除的确认页面,然后确认是否删除。
';

第6章 创建模型

最后更新于:2022-04-01 00:41:01

模型不仅是ZF2的重要组成部分,同时也是众MVC框架的重要组成部分。他的重要性主要在于处理用户与数据库之间的访问与操作功能。ZF2 本身并不直接直接提供 Zend\Model 组件,因为模型是一种业务逻辑,针对不同的项目可能会用不同的商业业务处理逻辑,模型的具体工作流程取决于你对相关模型组件个体设计。虽然ZF2本身并不提供模型,但ZF2提供了很多用于实现用户模型的各种组件,用户用通过ZF2提供的组件来构建自已的模型类,并且可以通过映射器对其进行映射,以方便应用程序的前端控制对他进行引用及使用,最终实现对数据库的一系列操作。 模型类的编写没有一个统一的写法,不同的人有不同的想法及写法,不同的业务逻辑有不同的实现方法,最终需求根据实际情况进行编写。在本章节中将介绍两种作者在开发中常用到的写法,一种是ORM对象映射技术,另一种是自定义对象。本章节的内容会结合前面章节中包括内容 控制器、视图模板等,同时也会涉及到ZF2 Zend\Db,Zend\Form 等相关知识内容进行综合的讲解。
';

5.5 访问 IndexAction

最后更新于:2022-04-01 00:40:59

经过以上众多的准备工作,现在我们已经可以通过 http://localhost/news 来访问到我们的 NewsController 控制器的indexAction 对应的模板了。在访问地址前我们先将之前文件 `/module/Application/src/Application/Controller/NewsController.php` 中的indexAction 函数进行一些修改。具体修改如下: ~~~ public function indexAction(){ $view = new ViewModel(); return $view; } ~~~ 讲解: $view = new ViewModel() 实例化一个视图模型,视图模型前面已经讲解,主要是用来解析模板 return $view 将视图模型返回给前端控制器 现在可以通过 http://localhost/news 来打开我们的网页了,这时我们的页面应该显示如下类似表格: header Welcome to ZF2 world footer 这个表格就是我们在 index.phtml模板中编写的表格。但为什么会在 Welcome to ZF2 world 的上面出现header,下面出footer 呢?其实header和footer 是由我们的layout 布局模板所产生的,前面在讲解布局模板文件的时候我们有说到我们的布局是 “上-中-下“这样的一个结构;header 就是表示我们将来的导航条,footer 就是表示我们将来的版权信息。 通过上面的代码可以看出 $view 视图模型并没有指定使用的模板文件,但ZF2却能够准确的找到 index.phtml模板文件。这是因为ZF2的默认模板搜索机制就是直接查找对应模块下的视图目录,然后再根据模块配置信息(module.config.php)来搜索相关目录。其完整的搜索模式如下: * 先到达模块下的视图目录 * 根据控制器名称在视图目录找与控制器名称相同的视图子目录 * 根据action名称最终在视图子目录下找到与action名相同的模板文件 如果只是需要访问一个默认的模板文件的话,还有一个更简单的方式,就是在Action 函数里什么也不写直接一个空函数,这样控制器也可以根据框架的默认模板使用规则找到对应的模板。那当然你也可以通过$view视图模型来指定你想使用的视图模板。 以上内容就是关于视图模板使用的主要内容,模板样式可以根据自已或用户的需求进行各种各样的定制,可以把UI模板做得丰富多彩、漂亮。
';