在教程的制作前先做一些作者的自我介绍，作者赖少林，男，毕业于 广州市南洋理工职业学院 计算机应用科学网络专业 和 海南师范大学 计算机应用科学 应用专业。于2008开始实习工作，从2008年起至今在两家公司任职过，一个是实习单位，另一个就是目前就职的企业--深圳市奇华基业信息技术有限公司，目前担任公司的技术总监职位。

说起互联网大家就可能马上想到网站，一说到网站就会想到JAVA ，.NET ，PHP，ASP 等开发语言；而对于这几个的优缺在互联网的有各种谈论在此就不再细说了。PHP同时也是我的一个选择，这或许也就我今天为什么要写Zend Framwork2 教程的原因根源之一。当前不论使用哪一种语言进行网站的开发都离不开一个东西---框架，框架到是什么呢？形象的说是一个网站的主体架构，可以理解为一座房屋的主体结构。而Zend Framework2 就是一个基于MVC形式的一个框架；那么MVC 到底是什么呢？他能够用来做什么的？ 在此就简单的说一下MVC，MVC是Model，Controller，View 三个单词的缩写，本意为模型，控制器，视图；MVC能够把用户界面，业务逻辑，数据处理等工作分离开来，使不同的层次来处理不同的工作，从而提高代码的重用性，项目可维护性。

PHP的框架的有很多，如 Zend Framwork , Symfony ,Codeilgniter, ThinkPHP 等；那么我为什么就选择Zend Framework2（以下Zend Framework简写为ZF） 呢。其实以前选择ZF 原因很简单，主要是有这几个方面的原因：

Zend 官方出的一个框架；

对执行效率高；

使用灵活；

插件丰富，也易于自写插件；

适用于大型项目等。

在此说说为什么要写ZF2 教程的原因，作者使用ZF1框架已经有多年的时间，在最开始接触ZF的时候就已经听说ZF 很难学，当时我不大相信不就是一个框架吗，有什么难的，当时就是那样的想法的。可当真的开始学习ZF的时候问题就不断的出现问题了，不问题多而且解决方法又少；因为PHP本身在国内发展及ZF在国内的应用缘故（由于国内较少有大学开设PHP语言课程，使得国内使用PHP技术的人员相对较少），导致要找到问题的相关解决方法真不简单，在国内的网站很少有ZF的相关资料，即使有一些资料也是比较零散的，而且也是已经比较过时的资料，对于解决问题基本上没有什么帮助；而对国外的资料就丰富多了，因为国外对PHP技术的发展及流行程度比较国内高，资料虽多但全是英文的，如果没有一定英文的基础根本无法从中找到有用帮助。直到今日作者发现不管是互联网上还是实体书本对于ZF的中文版教程还是少之又少。所以决定编写一本关于ZF 开发的系列教程，希望能够帮助到一些在ZF迷途的PHPER。

本书中的主要内容都是从项目的实例开发为出发点，并不是对ZF2官方资料的直接翻译；所以此书不可能将ZF2 类库的所有内容及配置都进行讲解；在写本书的同时本人也同在开发某款CMS系统，书中大部分代码均为CMS原文件中的片段，所有代码都通过本人的调试。本书比较适合用于ZF2 的项目入门指导书籍，书中集中讲述了怎样去使用及掌握ZF2的技术与技能。致以ZF2的底层实现可以查阅ZF2官方网站的开发手册。

Zend Framework 官方网址：http://www.zendframework.com/

ZF2是一个基于PHP的开源框架，可以用来开发WEB应用程序的各种服务。ZF2是一个基于面向对象的框架，一切都是以对象为基础。ZF2有非常丰富的组件库，而且大部组件之间相互独立，互不依赖，所以开发者可以独自开发并使用自定义组件。

ZF2 拥有一个强大而且高效的MVC实现，他具有强大的数据库操作、路由控制、视图渲染、HTML表单解析、表单验证、数据过滤等功能；同时ZF2还提供了多种用户认证功能,通过证书来保存用户认证和授权信息，也可以通过Amf 来为Flash等其他语言开发的软件提供相应的服务。总的来说不论你需要什么功能，你都可以从ZF2中找到相应的组件来实现，从而有效的减少开发时间，提高项目开发的效率。ZF2提供的各种组件可以用来实现你想要的各种功能，也可以添加一些你自定义组件来搭建你强大的WEB应用程序。

ZF2 的安装要求PHP的版本不低于5.3，不过作者还是建议可以升级到更高的版本，因为每个更高的版本都会对前一个版本的安全性和性能发挥上做了相应的改善和提高。本书中使用的PHP版本为PHP5.4，经过作者一些相关测试，ZF2框架在PHP 5.5 的版本上运行也是完全正常，所以PHP的版本选择范围还是比较广的。

致以安装ZF2与ZF1相比明显要复杂很多，两者即有明显的区别，又有一些本质上的联系；要想了解ZF1与ZF2的之间的区别可以到ZF的官网去找相关的说明与帮助。ZF2框架类库可以在ZF的官方网站（官网网址：http://www.zendframework.com/，下载网址：http://www.zendframework.com/downloads/latest）下载，本书使用的ZF版本为ZF2.15。至于怎样安装使用ZF2的框架，下面会做详细的介绍。

在开始使用ZF2框架前需要把开发平台先搭建好，在此作用选择 xampp 作为开发测试环境，最新版本的xampp已经集成了apache2.4与php5.4.7及其他组件，选择netbeans 作为开发工具。作者将网站的开发目录网址设置为：http://127.0.0.1/ | http://localhost/ 这两个网址是一样的。

开发环境安装好以后需要对 apache 做一些相关修改以便支持 .htaccess 文件。通过需要修改的地方为设置你网站目录的地方(httpd.conf)，将 AllowOverride None修改为 AllowOverride All

在vhost.conf下添加

网站的配置并没有太多的要求，对于目录的命名等可以根据自已的情况来配置，网站只要能支持.htaccess 文件就行。

方法一：

手动添加目录，结构如下

      /
└appliction
└css
└js
└images
└library
    └Zend
└module
└Application
└config
└language
└src
└Application
└Controller
└Model
└views
└vendor

目录解释：

目录 | 描述
/application | 你网站的根目录
/application/css(js/images) | 这些主要存放样式表、js、图片等文件
/library | 存放类库文件
/module | 存放各种模块，一般在此目录下的一个子目录为一个模块
/module/Application | 表一个名叫 Application 的模块
/module/Application/config | Application 模块的配置文件目录
/module/Application/language | 语言文件目录，用来支持多国语言实现项目的国际化
/module/Application/src | Application 模块的资源文件目录，下面包含此模块的控制器、模型、表单等一系列文件
/module/views | Application 模块的视图文件目录
/vendor | 自定义类库或其他第三方类库

往后需要添加模块可以根据相似的目录结果进行添加。

方法二：

使用netbeans 或 zend studio 新建一个项目，在创建项目的过程中选择使用 Zend Framework 框架，这样就可以创建出一个基于 Zend Framework 框架的项目，目录结构有些许差异，但目录功能与上面结构说明类似，你可以在项目找到他们对应的结构说明。下面作者使用Zend studio 来创建一个基于Zend Framework2的项目操作：

      打开zend studio 软件
--> File(打开)
--> New(新建)
--> Project(项目)
--> Local PHP Porject(本地PHP项目)
--> Next(下一步)
--> Project Name(项目名称，填写你的项目名称)
--> Location(项目放在位置，选择项目的保存位置)
--> Content(项目内容，选择 Zend Framework)
--> Version(版本，选择使用框架版本)
--> Finish(完成)；

这样一个基于Zend Framework 2的项目就已经建立好了，然后调整一个apache的目录指定。通过这种方法建立项目后可以直接使用 http://localhost/ 来打开项目了。

上面两种创建项目的方法各有优缺点，方法一：手动输入相对麻烦，但目录结构比较灵活；方法二：项目创建简单，即建即用，但类库不好找(其实就是放在vendor 下面了)。项目的创建方法不管使用哪一种，只要清楚各个目录的作用即可。本书创建项目的方法为第一种方法，此方法创建并运行项目需要添加多个文件，而第二种方法则直接创建后就可以直接运行。本书使用第一种方法创建项目的原因还是基于对ZF2框架的深化理解，使用阅读者能够真正的了解到ZF2的运行机制，同也使用读者能更多灵活的掌握和使用ZF2框架。

ZF2项目的基本目录创建好以后，在你的 apache 服务器上添加一个虚拟网站，配置示例详情如下：

      <VirtualHost *:80>
ServerName localhost
DocumentRoot /path/application
<Directory /path/application>
DirectoryIndex index.php
AllowOverride All
Order allow,deny
Allow from all
</Directory>
</VirtualHost>

如果对 apache 配置比较熟练的话可以根据自已的需要进行配置，对配置格式没有什么特殊的要求。

唯一需要注意的就是要将 AllowOverride All 打开伪静态的支持。

添加/application/.htaccess

如果使用记事本来写这个文件，那么保存的时候要使用另存为的方式进行保存，如果在netbeans中创建的话就可以直接保存。同时还应当注意在书写这种文件的时候最后至少要有一行空白行，有时候有些文件就有这样要求。此文件的主要功能就是实现URL的重写，根据URL的访问地址通过前端控制器找到相应的路由，从而实现对资源文件准确定位。重写URL的好处还在于能够让搜索引擎更容易抓取。

在文件中输入以下内容：

      RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^.*$ index.php [NC,L]

路径：/application/index.php

添加以下内容：

      chdir(__DIR__);
if (!defined('APP_PATH'))
    define ('APP_PATH', __DIR__ . '/../');
if (!defined('LIB'))
    define('LIB', APP_PATH . 'library');
require 'init_autoloader.php';
Zend\Mvc\Application::init(require APP_PATH . 'config/application.config.php')->run(); // 启动应用程序

下面代码解释:

chdir 修改当前运行目录

APP_PATH 指定网站根目录

LIB 指定类库目录

init_autoloader.php 自动加载文件

config/application.config.php 应用程序全局配置文件

Zend\Mvc\Application::init(require APP_PATH . 'config/application.config.php')->run() 启动应用程序

路径：/config/application.config.php

内容如下：

      return array(
    'modules' => array(
        'Application'
    ),
    '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=>array() 这是模块配置，网站系统的每一个模块都要添加到此，以便ZF2框架能够正确的找到模块

module_listener_options=>array() 此处用于设置模块的事件侦听

config_glob_paths=>array() 配置全局路径，以便系统自动加载相关文件类库

module_paths=>array() 配置模块路径

路径：/application/init_autoloader.php

      if (defined('LIB')) {
        include LIB . '/Zend/Loader/AutoloaderFactory.php';
        Zend\Loader\AutoloaderFactory::factory(array(
            'Zend\Loader\StandardAutoloader' => array(
                'autoregister_zf' => true
            )
        ));
}

if (!class_exists('Zend\Loader\AutoloaderFactory')) {
    throw new RuntimeException('Unable to load ZF2. ');
}

代码解释：

if (defined('LIB')) 判断是否有定义预定义变量指向ZF2类库

include LIB . '/Zend/Loader/AutoloaderFactory.php' 导入ZF2框架自动加载工厂文件

Zend\Loader\AutoloaderFactory::factory(array()) 对自动加载工厂类进行设置

if (!class_exists('Zend\Loader\AutoloaderFactory')) 判断工厂类是存在，如果不存在则抛出异常

下面是 init_autoloader.php 的另一个写法，此写法其实是 Zend studio 或 netbeans 创建项目时自动生成的写，这样的写法其实是加入对 phpunit 单元测试的支持。phpunit 的测试环境一般都是在命令行上操作完成，所以这样的写法也是对命令行环境的一种设置，在此就不多加详解；下面只贴出代码。

      if (file_exists('vendor/autoload.php')) {
    $loader = include 'vendor/autoload.php';
}

if (defined('LIB')) {
    if (isset($loader)) {
        $loader->add('Zend', LIB);
    } else {
        include LIB. '/Zend/Loader/AutoloaderFactory.php';
        Zend\Loader\AutoloaderFactory::factory(array(
            'Zend\Loader\StandardAutoloader' => array(
                'autoregister_zf' => true
            )
        ));
    }
}

if (!class_exists('Zend\Loader\AutoloaderFactory')) {
    throw new RuntimeException('Unable to load ZF2.');
}

路径：/module/Application/src/Application/controller/IndexController.php

代码如下：

      namespace Application\Controller;
use Zend\Mvc\Controller\AbstractActionController;
use Zend\View\Model\ViewModel;
class IndexController extends AbstractActionController{
    public function indexAction(){
        echo "hello world";
        exit;
    }
}

代码说明：

namespace Application\Controller 指定命名空间

use Zend\Mvc\Controller\AbstractActionController | use Zend\View\Model\ViewModel 导入相关类库

class IndexController extends AbstractActionController 定义 IndexController 类库，同时此类必需继承 AbstractActionController 类，这是ZF2 的硬性要求，除非你重写此类的实现。

public function indexAction(){} 控制器的一个响应动作，其中indexAction 这个名称为ZF默认动作

echo "hello world" 在屏幕上打印出 hello world

到此一个基本的控制器类就已经编写完成，但是不是现在就可以通过 http://localhost/ 就可以访问控制器，并可以看到屏幕上的hello world 了呢？答案当然是否定的。如果这是ZF1的话，就添加完这样一个控制器后应该是可以访问的了。但此处使用的是ZF2框架，所以要想通过 http://localhost/ 访问并在屏幕上打印出 hello world 来还需要添加多个文件来共同实现；这也是为什么ZF2比ZF1的使用要更为复杂，也是ZF2比ZF1更加强大、灵活的原因所在。

ZF2 使用模块系统将应用程序的主要代码集成到各个模块中去。同时应用模块还应提供用于引导、错误异常、路由等全部的配置信息。在模块文件里可以根据自已的需要去调整关于 实图、路由、模型等一系列应用程序级的设置，同时Module不单起到配置信息的作用，同时也是应用程序的必需中间件或桥梁，因为程序从前端控制器的分配及引导下进入的下层级就是Module类，通过解析Module类到达指定资源位置。由此也可以看出ZF2的灵活性还是比较高的。

下面开始添加模块文件。

路径：/moudle/Application/Module.php

内容如下：

      namespace Application;
use Zend\Mvc\ModuleRouteListener;
use Zend\Mvc\MvcEvent;
class Module {

    public function onBootstrap(MvcEvent $e){
        $e->getApplication()->getServiceManager()->get('translator');// 多国语言支持，这个语言文件需要自已添加
        $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__  // 导入自动加载空间
                )
            )
        );
    }
}

代码解释：

namespace Application 创建或访问指定命名空间

use Zend\Mvc\ModuleRouteListener | use Zend\Mvc\MvcEvent 导入包或类

class Module 定义类

public function onBootstrap 启动模块，onBootstrap() 将调用每个已经实现此功能的模块，并且用于执行轻量级任务和注册事件监听器等

public function getConfig 获取此模块中的配置信息，返回一个符合ZF2自动加载工厂规则的数组

public function getAutoloaderConfig 此模块中自动加载配置信息

在此说明一下，在ZF2开始引入了namespace（命名空间）的概念，使得ZF2 与java的相关概念进一步的靠近，这对于已经掌握了java开发的人来说是将是一个好的消息。空间的含义就相当于一个用来装东西的容器，而对于程序来说，空间可以简单的理解为装类的容器；有了空间的出现，所有的类库都将被包含到一个指定的空间里面，因为ZF2的文件搜索或路由也是通过空间来定位的。再者就是有了空间可以更好的去管理各种类，方便文件功能归类及使用。

路径：/module/Application/config/module.config.php

内容如下：

      return array(
    'router' => array(
        'routes' => array(
            'application' => array(
                'type' => 'segment',
                'options' => array(
                    'route' => '/[application][:controller][/:action]',
                    'constraints' => array(
                        'controller' => '[a-zA-Z][a-zA-Z0-9_-]*',
                        'action' => '[a-zA-Z0-9_-]*',
                    ),
                    'defaults' => array(
                        '__NAMESPACE__' => 'Application\Controller',
                        'controller' => 'Index',
                        'action' => 'index'
                    ),
                ),
            ),
        ),
    ),

    'controllers' => array(
        'invokables' => array(
            'Application\Controller\Index' => 'Application\Controller\IndexController'
        ),
    ),

    'view_manager' => array(
        'display_not_found_reason' => true,
        'display_exceptions' => true,
        'doctype' => 'HTML5',
        'not_found_template' => 'error/404',
        'exception_template' => 'error/index',
        'template_map' => array(
            'layout/layout' => __DIR__ . '/../view/layout/layout.phtml',
            'application/index/index' => __DIR__ . '/../view/application/index/index.phtml',
            'error/404' => __DIR__ . '/../view/error/404.phtml',
            'error/index' => __DIR__ . '/../view/error/index.phtml',
        ),
        'template_path_stack' => array(
            'application' => __DIR__ . '/../view'
        ),
    ),

    'service_manager' => array(
        'factories' => array(
            'translator' => 'Zend\I18n\Translator\TranslatorServiceFactory',
'navigation' => 'Zend\Navigation\Service\DefaultNavigationFactory',
        ),
    ),

    'translator' => array(
        'locale' => 'en_US',
        'translation_file_patterns' => array(
            array(
                'type' => 'gettext',
                'base_dir' => __DIR__ . '/../language',
                'pattern' => '%s.mo',
            ),
        ),
),

'navigation' => array(
        'default' => array(
            array(
                'label' => 'Home',
                'route' => 'applicaton’
            ),
            array(
                'label' => 'Application',
                'route' => 'application’,
                'pages' => array(
                    array(
                        'label' => 'List',
                        'route' => 'application’,
                        'action' => 'list',
                    ),
                ),
            ),
        ),
    ),
);

先对以上配置进行归类：

      array(// 总配置
‘router’                     =>array(),// 路由
‘controllers’              =>array(),// 控制器
‘view_manager’       =>array(),// 视图管理器
‘service_manager’       =>array(),// 服务器管理器
‘translator’              =>array(),// 译码器或翻译器
‘navigation’              =>array(),// 页面导航
);

下面对 router、controllers、view_manager、service_manager、translator 、navigation进行逐个解释。

路由的配置是对前台页面访问地址的具体配置，此处配置的格式将影响到前台页面访问此模块的所有地址

      链：router--->routes--->模块--->具体配置

router 此数组块为路由配置信息段

router-->routes 表示此模块的中路由，路由至少1条以上

router-->routes-->application 表示你的模块名称,在此以下的信息为具体配置信息

router-->routes-->application-->type 表示路由模式，可选 segment 或 literal，区别在于 segment 已经处理好了结尾的斜杠，而literal 会把结尾带与不带斜杠表示不同的路由进行处理; 如果使用literal 时需要特别注意这一点。

router-->routes-->application-->options 路由具体选项信息区块

router-->roytes-->application-->options-->route 路由规则，此处规则将最终决定此模块的路由访问格式

router-->roytes-->application-->options-->constraints 路由匹配规则

router-->roytes-->application-->options-->constraints-->controller 控制器的路由正规匹配规则

router-->roytes-->application-->options-->constraints-->action action(动作)的路由正规匹配规则

router-->roytes-->application-->options-->defaults 默认路由处理规则

router-->roytes-->application-->options-->defaults-->_NAMESPACE_ 指定模块控制器所在的命名空间

router-->roytes-->application-->options-->defaults-->controller 指定默认使用的控制器名称

router-->roytes-->application-->options-->defaults-->action 指定默认使用的action(动作)名称

控制器的配置将决定哪些控制器类能够被访问及使用，在此配置后ZF2自动加载厂可以很快的定位到控制所在的位置，对资源进行快速访问、使用。

      链：controllers--->invokables--->控制器

controllers 表示此数组块为控制器配置信息段

controllers-->invokables 这个是控制器区块的固定表示方法，表示此区块下的控制器为可用控制器

controllers-->invokables-->Application\Controller\Index 表示一个控制器，数组的键表示DI注入的引用，数组值则表示对应控制器所在的具体路径

控制器的配置不局限于某一个控制器，可以把所有已经存在并且有效控制器加入到此区块来进行使用。

视图管理器主要负责视图信息的配置，如：错误显示、页面类型、布局文件、视图文件位置、404页面等。

      链：view_manager-->N , view_manager-->template_map , view_manager-->template_path_stack

view_manager 表示此数组块为视图管理器配置信息段

view_manager-->display_not_found_reason 是否显示404原因

view_manager-->display_exceptions 是否显示异常信息

view_manager-->doctype 指定视图页面类型

view_manager-->not_found_template 指定404的视图模板

view_manager-->exception_template 指定异常模板文件

view_manager-->template_map 视图模块地图

view_manager-->template_map-->’layout/layout’ 指定布局文件

view_manager-->template_map-->’application/index/index’ 指定 application 模块的视图文件

view_manager-->template_map-->’error/404’ 指定404页面的视图文件

view_manager-->template_map-->’error/index’ 指定错误异常页面的视图文件

view_manager-->template_path_stack 视图模板堆栈路径

view_manager-->template_path_stack-->application 指定模块application 视图目录所在路径

服务管理器主要负责一些工厂类的配置，使用系统能够在运行时自动的加载运行某些服务性功能。

      链：service_manager-->factories

service_manager 表示此数组块为服务管理器配置信息段

service_manager-->factories 工厂类配置

service_manager-->factories-->translator 语言转换工厂，主要功能是实现多国语言的支持，语言文件需要自已编写；ZF2框架本身并不提供语言包，但提供对语言包的解析功能，通过语言包通过指定的语言进行转换；同时语言之间的转换及格式的变化比较而随意性也比较大，所以语言包可以根据项目的实现需求来进行订制，如果实际的项目开发中并不使用到国际化的功能时，可以将多国语言国际功能删除。

service_manager-->factories-->translator-->navigation 导航工厂，主要用来实现页面的导航和分页导航

翻译器的主要工作是负责对各种支持语言的转换以此为目的，从而实现网站应用程序的多国化甚至全球化。本书内容有涉及到使用语言包，语言包的生成可以参考po，mo 文件创建的其他文献；你也可以通过 Zend Studio 来创建项目以获取ZF2 默认生成的language 包。下面将会提供两个语言包内容 zh_CN.po ，en_US.po 这个为中英文件互转文件，这两语言由Zend Studio项目生成时自动创建；他们对应的mo文件可以通过 poedit 软件来生成，也可能过poedit 来修改po文件。如果在开发时不想使用任何语言转换，可以不进行任何关于语言转换相关的配置。

      链：translator-->locale , translator-->translation_file_patterns

translator 表示此数组块为翻译器配置信息段

translator-->locale 指明应用程序的本地使用语言，或是应用程序使用的默认语言

translator-->translation_file_patterns 翻译文件的配置设置

translator-->translation_file_patterns-->type 翻译文件类型

translator-->translation_file_patterns-->base_dir 指定语言文件目录

translator-->translation_file_patterns-->pattern 语言文件的匹配规则

下面提供两个语言包，语言包为中英语言包，可以在开发的时候对两种语言环境进行切换。

3.2.5.1 语言文件 zh_CN.po 内容

      msgid ""

msgstr ""

"Project-Id-Version: ZendSkeletonApplication\n"

"Report-Msgid-Bugs-To: \n"

"POT-Creation-Date: 2012-07-05 22:17-0700\n"

"PO-Revision-Date: 2013-05-06 11:26+0800\n"

"Last-Translator: \n"

"Language-Team: ZF Contibutors <zf-devteam@zend.com>\n"

"Language: en_US\n"

"MIME-Version: 1.0\n"

"Content-Type: text/plain; charset=UTF-8\n"

"Content-Transfer-Encoding: 8bit\n"

"X-Poedit-KeywordsList: translate\n"

"X-Poedit-Basepath: .\n"

"X-Generator: Poedit 1.5.5\n"

"X-Poedit-SearchPath-0: ..\n"

msgid "Home"

msgstr "主页"

msgid "All rights reserved."

msgstr "版权所有."

msgid "Help & Support"

msgstr "帮助 & 支持"

msgid "An error occurred"

msgstr "发生错误"

msgid "Additional information"

msgstr "附加信息"

msgid "File"

msgstr "文件"

msgid "Message"

msgstr "消息"

msgid "Stack trace"

msgstr "Stack trace"

msgid "Previous exceptions"

msgstr "上一个异常"

msgid "No Exception available"

msgstr "没有可用的Exception"

msgid "A 404 error occurred"

msgstr "404 缺少目标文件"

msgid "The requested controller was unable to dispatch the request."

msgstr "所请求的控制器不能分发该请求"

msgid ""

"The requested controller could not be mapped to an existing controller class."

msgstr "所请求的控制器不能映射到已存在的控制器类"

msgid "The requested URL could not be matched by routing."

msgstr "所请求的URL不能与路由对应"

msgid "We cannot determine at this time why a 404 was generated."

msgstr "我们不能确定为什么这次会出现404"

msgid "Controller"

msgstr "控制器"

msgid "resolves to %s"

msgstr "解决： %s"

msgid "Exception"

msgstr "异常"

msgid "Add"

msgstr "添加"

msgid "Delete"

msgstr "删除"

msgid "Edit"

msgstr "修改"

msgid "Add new album"

msgstr "添加新闻"

msgid "Title"

msgstr "标题"

msgid "Artist"

msgstr "文章内容"

3.2.5.2 语言文件 en_US.po 内容

      msgid ""

msgstr ""

"Project-Id-Version: ZendSkeletonApplication\n"

"Report-Msgid-Bugs-To: \n"

"POT-Creation-Date: 2012-07-05 22:17-0700\n"

"PO-Revision-Date: 2012-07-05 22:17-0700\n"

"Last-Translator: Evan Coury <me@evancoury.com>\n"

"Language-Team: ZF Contibutors <zf-devteam@zend.com>\n"

"Language: \n"

"MIME-Version: 1.0\n"

"Content-Type: text/plain; charset=UTF-8\n"

"Content-Transfer-Encoding: 8bit\n"

"X-Poedit-KeywordsList: translate\n"

"X-Poedit-Language: English\n"

"X-Poedit-Country: UNITED STATES\n"

"X-Poedit-Basepath: .\n"

"X-Poedit-SearchPath-0: ..\n"

#: ../view/layout/layout.phtml:6

#: ../view/layout/layout.phtml:33

msgid "Skeleton Application"

msgstr ""

#: ../view/layout/layout.phtml:36

msgid "Home"

msgstr ""

#: ../view/layout/layout.phtml:50

msgid "All rights reserved."

msgstr ""

#: ../view/application/index/index.phtml:2

#, php-format

msgid "Welcome to %sZend Framework 2%s"

msgstr ""

#: ../view/application/index/index.phtml:3

#, php-format

msgid "Congratulations! You have successfully installed the %sZF2 Skeleton Application%s. You are currently running Zend Framework version %s. This skeleton can serve as a simple starting point for you to begin building your application on ZF2."

msgstr ""

#: ../view/application/index/index.phtml:4

msgid "Fork Zend Framework 2 on GitHub"

msgstr ""

#: ../view/application/index/index.phtml:10

msgid "Follow Development"

msgstr ""

#: ../view/application/index/index.phtml:11

#, php-format

msgid "Zend Framework 2 is under active development. If you are interested in following the development of ZF2, there is a special ZF2 portal on the official Zend Framework website which provides links to the ZF2 %swiki%s, %sdev blog%s, %sissue tracker%s, and much more. This is a great resource for staying up to date with the latest developments!"

msgstr ""

#: ../view/application/index/index.phtml:12

msgid "ZF2 Development Portal"

msgstr ""

#: ../view/application/index/index.phtml:16

msgid "Discover Modules"

msgstr ""

#: ../view/application/index/index.phtml:17

#, php-format

msgid "The community is working on developing a community site to serve as a repository and gallery for ZF2 modules. The project is available %son GitHub%s. The site is currently live and currently contains a list of some of the modules already available for ZF2."

msgstr ""

#: ../view/application/index/index.phtml:18

msgid "Explore ZF2 Modules"

msgstr ""

#: ../view/application/index/index.phtml:22

msgid "Help & Support"

msgstr ""

#: ../view/application/index/index.phtml:23

#, php-format

msgid "If you need any help or support while developing with ZF2, you may reach us via IRC: %s#zftalk on Freenode%s. We'd love to hear any questions or feedback you may have regarding the beta releases. Alternatively, you may subscribe and post questions to the %smailing lists%s."

msgstr ""

#: ../view/application/index/index.phtml:24

msgid "Ping us on IRC"

msgstr ""

#: ../view/error/index.phtml:1

msgid "An error occurred"

msgstr ""

#: ../view/error/index.phtml:8

msgid "Additional information"

msgstr ""

#: ../view/error/index.phtml:11

#: ../view/error/index.phtml:35

msgid "File"

msgstr ""

#: ../view/error/index.phtml:15

#: ../view/error/index.phtml:39

msgid "Message"

msgstr ""

#: ../view/error/index.phtml:19

#: ../view/error/index.phtml:43

#: ../view/error/404.phtml:55

msgid "Stack trace"

msgstr ""

#: ../view/error/index.phtml:29

msgid "Previous exceptions"

msgstr ""

#: ../view/error/index.phtml:58

msgid "No Exception available"

msgstr ""

#: ../view/error/404.phtml:1

msgid "A 404 error occurred"

msgstr ""

#: ../view/error/404.phtml:10

msgid "The requested controller was unable to dispatch the request."

msgstr ""

#: ../view/error/404.phtml:13

msgid "The requested controller could not be mapped to an existing controller class."

msgstr ""

#: ../view/error/404.phtml:16

msgid "The requested controller was not dispatchable."

msgstr ""

#: ../view/error/404.phtml:19

msgid "The requested URL could not be matched by routing."

msgstr ""

#: ../view/error/404.phtml:22

msgid "We cannot determine at this time why a 404 was generated."

msgstr ""

#: ../view/error/404.phtml:34

msgid "Controller"

msgstr ""

#: ../view/error/404.phtml:41

#, php-format

msgid "resolves to %s"

msgstr ""

#: ../view/error/404.phtml:51

msgid "Exception"

msgstr ""

导航条的主要功能是生成页面导航或分页导航

      链：navigation-->N

navigation 表示此数组块为页面导航配置信息段

navigation-->default 默认页面导航

navigation-->default-->array() 一个导航标签配置

navigation-->default-->label 导航的标签

navigation-->default-->route 导航的路由，其实就是指向的控制器

通过以上对路由、控制器、视图、服务等各项功能的配置之后，现在已经可以通过 http://localhost/ 或 http://localhost/index/index 的链接来访问我们的操控了。通过 http://localhost/ 访问就能看到屏幕打印出 hello world ，你会发现这个链接即没有控制器也没有动作(action)，怎么就可以输出内容了呢，其实通过上面的路由配置，已经设置一个默认的路由，默认路由规则中规定了默认使用的控制器为IndexController，默认访问的动作为indexAction；而 http://localhost/index/index 也是同样因为路由设置，同时该连接符合路由规则，因此同样达到了打印输出 hello world 的效果。

控制器是ZF2的核心功能，其实现了前端控制器所需的全部接口。如：路由分发、视图渲染、助手、请求、响应等一系列的功能。同时也可以利用继承来设计自已的助手类或一些实用性较的插件等，来加强自已的系统功能。

在ZF2中，控制器是一个类通常称为{控制器名称}控制器。 请注意，{控制器名称}必须以大写字母开头。这个类保存在控制器模块目录内以名为{控制器名称}控制器类.php的文件中。 控制器的每个操作都是在一个公共方法内的{动作名称}中完成。一般情况下{动作名称}是以小写字母开头。

根据前面章节的相关设置，当前项目中的所有控制器都将放在 /module/Application/src/Application/Controller 的目录下；在本章节及接下来的几个章节都以一个新闻系统来对相关的知识内容来进行讲解。

现在添加一个控制器，在控制器目录下新一个控制器 NewsController 控制器，路径：/module/Application/src/Application/Controller/NewsController.php

代码如下：

      class NewsController extends AbstractActionController {}

通过以上代码便创建了一个标准的控制器类，虽然些控制器只有短短的一行代码，但他拥有操作器所需的全部基本功能；因为他已经继承了 AbstractActionController 类中的全部方法。

下面在NewsController控制器中添加几个 Action：

      public function indexAction(){
    echo "NewsController indexAction";
    exit;
}

public function listAction(){
    echo "NewsController listAction";
    exit;
}

public function addAction(){
    echo "NewsController addAction";
    exit;
}

public function editAction(){
    echo "NewsController editAction";
    exit;
}

public function deleteAction(){
    echo "NewsController deleteAction";
    exit;
}

注意：ZF2控制器的action方法都必需为 public 类型，不然ZF2前端控制器可能无法访问导致出错。同时应该注意action 的名称都是动作名+Action组成的，需要注意大写(如果项目将来是布置在Linux系统的服务器上时这点就显得尤为重要)。

经过添加以上的代码就建立了NewsController 控制器中建立了5个不同的action，在此就可以利用这5个不同的action 来实现5个不同的功能。下面说明一下上面添加的5个 action 的主要作用，indexAction 为控制器的默认action ；listAction 用来实现新闻列表功能；addAction 用来实现添加新闻的功能；editAction 用来实现修改/编辑新闻功能；deleteAction 用来实现删除新闻的功能。

有了控制器及控制器的action，那么是否就可以直接通过 http://localhost/news 来对NewsController进行访问了呢？答案是否定的。在前面的章节有提到过关于路由的概念或相关的内容，ZF2 中的所有控制的访问都需要先通过对控制器路由设定才能进行使用，没有经过路由设置的控制器就相当于一部不会割草的割草机；这种路由的设定也体现了ZF2中强大的路由功能，你可以将控制器的访问路径配置成各种各样的形式，这种路由的设置模式区别于传统PHP网址路径的访问形式，传统的访问地址往往都是包括了文件名，而ZF2的路由配置规则则可完全将文件名隐藏起来。那下面就开始对 NewsController 控制器进行路由的进行设定。

找到模块配置文件 /module/Application/config/module.config.php，打开文件并找到 router-->routes-->application 节点的未尾，在此节点的末尾添加如下代码：

      'news'=>array(
    'type'=>'segment',
    'options'=>array(
        'route'=>'/news[/:action]',
        'constraints'=>array(
            'action'=>'[a-zA-Z]'
        ),
        'defaults'=>array(
            'controller'=>'Application\Controller\News',
            'action'=>'index'
        ),
    ),
),

注意：请确认好 application 与 news 的节点是处于同一层次

下面对 NewsController 路由配置进行解释：

news=>array() 表示一个路由节点，此节点的路由名称为 news

news-->type=>segment 表示路由使用 segment 模式进行解析

news-->options=>array() 表示路由配置选项

news-->options-->route => /news[/:action] 表示路由地址

news-->options-->constraints=>array() 对路由约束规则，其实就是对路由的正则匹配

news-->options-->constraints-->action 表示 action 的匹配规则

news-->defaults 表示路由默认访问的配置

news-->defaults-->controller 表示默认使用的控制器

news-->defaults-->action 表示控制器默认使用的action

现在可以通过

      http://localhost/news         访问到 indexAction
http://localhost/news/list     访问到 listAction
http://localhost/news/add        访问到 addAction
http://localhost/news/edit    访问到 editAction
http://localhostnews/delete     访问到 deleteAction

通过以上的几个环节便完成了ZF2中的控制器的创建和使用，由此可见ZF2的使用比较ZF1更为复杂，也可以看出路由的设置及访问方式更加的灵活。在ZF2框架中控制器、视图、模型 是缺一不可的一个整体体系，缺失任何一个都将损害ZF2的完全性；致以本章节前面提到的要实现控制器中的增、删、改 的功能将会在视图及模型章节中加以补充。

视图是任何网站应用程序不可或缺的一个组成部分，它提供了与用户交互的良好界面，也提供了数据输入与输出的接口。ZF2 视图类主要有 Zend\View 包，其主要功能简单的说包括：变量传递，数据转换、视图渲染、请求映射、渲染策略、响应策略等。此外ZF2还通过 Zend\MVC\View 提供了事件侦听的一系列包，在进行项目开发的时候可以根据需求加入侦听事件。下面开始创建视图并开始使用他们。

为要呼应本书各章节的内容，在此将建立三种模板：布局模板、错误异常模板、控制器模板

5.1.1 建立布局目录

路径：/module/Application/view/layout

此目录主要用来放置网站应用程序的布局文件，在建站的时候可以根据页面的不同需要来选择不同的布局文件。布局的功能可以实现不同模块不同布局，不同模块相同布局的实际需要。

5.1.2 建立布局文件

路径：/module/Application/view/layout/layout.phtml

在此需要注意一下，ZF2默认的视图文件均以.phtml 为后缀名，如果有其他特殊要求可以修改为其他的后缀名

5.1.3 建立错误异常目录

路径：/module/Application/view/error

在此目录下主要用来放置一些关于错误异常处理的模板文件

5.1.4 建立错误异常模板文件

/module/Application/view/error/index.phtml 错误异常信息显示模板文件

/module/Application/view/error/404.phtml 404错误异常信息显示模板文件

5.1.5 建立 NewsController 模板目录

路径：/module/Application/view/application/news

此目录主要用放置 NewsController 中Action 对应的模板文件。一般情况一个控制会对应一个模板目录，同时模版目录的名称与与控制的名称一致(不包含Controller)。

5.1.6 建立 NewsController 对应的Action 模板文件

/module/Application/view/application/news/index.phtml indexAction 使用的模板文件

/module/Application/view/application/news/list.phtml listAction 使用的模板文件

/module/Application/view/application/news/add.phtml addAction 使用的模板文件

/module/Application/view/application/news/edit.phtml editAction 使用的模板文件

/module/Application/view/application/news/delete.phtml deleteAction 使用的模板文件

从上面的4个模板文件可以看出一个规律，模板的文件名都是控制器Action 的名称。其实在ZF2里面有一个默认规定，那就是在各个Action 进行模板渲染里默认搜索与Action名相同的模板文件，所以在会命名模板文件名的时候需要注意。如果不想使用默认的模板对应名称，可以在控制器中返回模板时通过 setTemplate() 函数来设置自已需要的模板文件。为了方便项目的日后维护作者在此也建议不同的action对就不同的模板。

5.1.7 视图中常用函数

$this->doctype() 指定文件的文档类型

$this->headTitle()->appendName() 输出文件标题

$this->headMeta() 设置并输出文件的Meta 属性

$this->headLink() ->prependStylesheet() 加载格式表文件

$this->headScript()->prependFile() 加载 js 文件

$this->basePath() 获取网站根路径

$this->navigation()->menu() 输出导航菜单

$this->url() 设置超链接

$this->content 输出页面内容(其实就是将其他页面的内容输出到布局页面上来)

$this->escapeHmtl() 过滤HTML标签

$this->translate() 进行语言转换(如果有设置多国语言支持)

以上是一些相对较为常用的函数功能，其他的函数可以查看Zend\View\Renderer\PhpRenderer.php 文件中的相关描述

建立好布局文件、模板文件之后需要对他们进行配置，以便后续使用中能够让ZF2自动的搜索并加载模板文件。其实对模板的配置在前面的章节已经有一些讲解，因为模板的配置也是一个重点的内容，在此对模板的配置再详细的解释一遍。打开文件：/module/Application/config/module.config.php 在配置文件中加入如下代码(已有就不用添加)：

      'view_manager' => array(
        'display_not_found_reason' => true,
        'display_exceptions' => true,
        'doctype' => 'HTML5',
        'not_found_template' => 'error/404',
        'exception_template' => 'error/index',
        'template_map' => array(
            'layout/layout' => __DIR__ . '/../view/layout/layout.phtml',
            'error/404' => __DIR__ . '/../view/error/404.phtml',
            'error/index' => __DIR__ . '/../view/error/index.phtml',
        ),
        'template_path_stack' => array(
            'application' => __DIR__ . '/../view'
        ),
),

view_manager 节点与 router、controllers 节点属于同一级。

view_manager 表示此数组块为视图管理器配置信息段

view_manager-->display_not_found_reason 是否显示404原因

view_manager-->display_exceptions 是否显示异常信息

view_manager-->doctype 指定视图页面类型

view_manager-->not_found_template 指定404的视图模板

view_manager-->exception_template 指定异常模板文件

view_manager-->template_map 视图模块地图

view_manager-->template_map-->’layout/layout’ 指定布局文件

view_manager-->template_map-->’error/404’ 指定404页面的视图文件

view_manager-->template_map-->’error/index’ 指定错误异常页面的视图文件

view_manager-->template_path_stack 视图模板堆栈路径

view_manager-->template_path_stack-->application 指定模块application 视图目录所在路径

经过以上 建立目录、文件、配置后 NewsController 就是可以直接自动的搜索对应的布局和模板等资源文件。由于以上都只是建立了空文件，在实际应用中没有什么意义，所以接下将对布局、错误异常的模板添加一些代码，致以news 目录里的模板文件的内容将在后续的讲解中逐一的进行添加填充。

为了使所有的模板文件能够看起来足够简洁和便于理解，在模板文件的编写上将尽量的用少的代码及多的常用代码来进行讲解，而且在模板文件中较多的代码仍为普通的HTML代码；所以在下面涉及到的 css 文件和 js 文件都是以空文件的形式来引用，使用空文件的原因主要是为说明相关函数的功能特性，同时对模板文件进行解释的主要内容也在ZF2的函数上。

5.3.1 模板文件layout.phtml

下面是代码内容：

      <?php echo $this->doctype();  ?>
<html>
    <head>
        <meta charset="utf-8">
        <?php echo $this->headTitle($this->translate('doc title'));  ?>
        <?php echo $this->headMeta()->appendName('viewport', 'width=device-width, initial-scale=1.0');  ?>
        <?php echo $this->headLink()->prependStylesheet($this->basePath() . '/css/style.css'); ?>
        <?php echo $this->headScript()->prependFile($this->basePath() . '/js/jquery.min.js'', 'text/javascript');?>
    </head>
    <body>
        <table border="1" cellspacing="0" cellspadding="0" width="100%">
            <tr>
                <td>header</td>
            </tr>
            <tr><td><?php echo $this->content; ?></td></tr>
            <tr><td>footer</td></tr>
        </table>
    </body>
</html>

模板内容解释：

echo $this->doctype() 文档使用的类型，这个类型与在模块配置文件中设置的类型有关

echo $this->headTitle(); 输出文档标题

$this->translate('doc title') 转换文档标题，此函数功能的实现需要语言包的支持

echo $this->headMeta() 输出HTML人 Meta 属性

appendName('viewport', 'width=device-width, initial-scale=1.0') 设置Meta 属性的具体内容

echo $this->headLink() 输出link标签

prependStylesheet($this->basePath() . '/css/style.css') 设置link标签属性

$this->basePath() 获取站点根路径

echo $this->headScript() 输出 script 标签

prependFile($this->basePath() . '/js/jquery.min.js'', 'text/javascript') 设置 script 标签属性

echo $this->content 输出控制器对应的模板页面内容

以上的内容就是一个简单的layout 而已模板，没有复杂的代码，没有复杂的样式；布局的结构最后将呈现出 上-中-下 的三行结构；上部放置导航内容，中部放置页面主要内容，下部放置版权信息等。所以最终看到的界面大概如下所示：

header 头部内容

content 正文内容

footer 底部内容

5.3.2 错误异常模板 index.phtml

下面是代码内容:

      <h1><?php echo $this->translate('An error occurred') ?></h1>
<h2><?php echo $this->message ?></h2>
<?php if (isset($this->display_exceptions) && $this->display_exceptions): ?>
<?php if(isset($this->exception) && $this->exception instanceof Exception): ?>
<hr/>
<h2><?php echo $this->translate('Additional information') ?>:</h2>
<h3><?php echo get_class($this->exception); ?></h3>
<dl>
    <dt><?php echo $this->translate('File') ?>:</dt>
    <dd>
        <pre class="prettyprint linenums"><?php echo $this->exception->getFile() ?>:<?php echo $this->exception->getLine() ?></pre>
    </dd>
    <dt><?php echo $this->translate('Message') ?>:</dt>
    <dd>
        <pre class="prettyprint linenums"><?php echo $this->exception->getMessage() ?></pre>
    </dd>
    <dt><?php echo $this->translate('Stack trace') ?>:</dt>
    <dd>
        <pre class="prettyprint linenums"><?php echo $this->exception->getTraceAsString() ?></pre>
    </dd>
</dl>
<?php
    $e = $this->exception->getPrevious();
    if ($e) :
?>
<hr/>
<h2><?php echo $this->translate('Previous exceptions') ?>:</h2>
<ul>
    <?php while($e) : ?>
    <li>
        <h3><?php echo get_class($e); ?></h3>
        <dl>
            <dt><?php echo $this->translate('File') ?>:</dt>
            <dd>
                <pre class="prettyprint linenums"><?php echo $e->getFile() ?>:<?php echo $e->getLine() ?></pre>
            </dd>
            <dt><?php echo $this->translate('Message') ?>:</dt>
            <dd>
                <pre class="prettyprint linenums"><?php echo $e->getMessage() ?></pre>
            </dd>
            <dt><?php echo $this->translate('Stack trace') ?>:</dt>
            <dd>
                <pre class="prettyprint linenums"><?php echo $e->getTraceAsString() ?></pre>
            </dd>
        </dl>
    </li>
    <?php
        $e = $e->getPrevious();
        endwhile;
    ?>
</ul>
<?php endif; ?>
<?php else: ?>
<h3><?php echo $this->translate('No Exception available') ?></h3>
<?php endif ?>
<?php endif ?>

代码解释：

echo $this->message 输出错误信息

if (isset($this->display_exceptions) && $this->display_exceptions) 判断是否显示异常信息

echo get_class($this->exception) 输出异常类型名称

echo $this->exception->getFile() 输出导致异常的文件名

echo $this->exception->getLine() 输出导致异常文件的所在行

echo $this->exception->getMessage() 输出异常信息

echo $this->exception->getTraceAsString() 输出异常堆栈信息

$e = $this->exception->getPrevious() 获取上一个异常

以上是错误异常模板内容，模板能够输出导致错误异常的文件名、出错所在行、错误类型等信息。在开发项目的时候便可以通过错误的信息提示来查找相关出错原因。理解并正确使用使用错误信息能够有效的提高开发效率。

5.3.3 404错误模板 404.phtml

下面是代码内容：

      <h1><?php echo $this->translate('A 404 error occurred') ?></h1>

<h2><?php echo $this->message ?></h2>

<?php if (isset($this->reason) && $this->reason): ?>

<?php

$reasonMessage= '';

switch ($this->reason) {

    case 'error-controller-cannot-dispatch':

        $reasonMessage = $this->translate('The requested controller was unable to dispatch the request.');

        break;

    case 'error-controller-not-found':

        $reasonMessage = $this->translate('The requested controller could not be mapped to an existing controller class.');

        break;

    case 'error-controller-invalid':

        $reasonMessage = $this->translate('The requested controller was not dispatchable.');

        break;

    case 'error-router-no-match':

        $reasonMessage = $this->translate('The requested URL could not be matched by routing.');

        break;

    default:

        $reasonMessage = $this->translate('We cannot determine at this time why a 404 was generated.');

        break;

}

?>

<p><?php echo $reasonMessage ?></p>

<?php endif ?>

<?php if (isset($this->controller) && $this->controller): ?>

<dl>

    <dt><?php echo $this->translate('Controller') ?>:</dt>

    <dd><?php echo $this->escapeHtml($this->controller) ?>

<?php

if (isset($this->controller_class)

    && $this->controller_class

    && $this->controller_class != $this->controller

) {

    echo '(' . sprintf($this->translate('resolves to %s'), $this->escapeHtml($this->controller_class)) . ')';

}

?>

</dd>

</dl>

<?php endif ?>

<?php if (isset($this->display_exceptions) && $this->display_exceptions): ?>

<?php if(isset($this->exception) && $this->exception instanceof Exception): ?>

<hr/>

<h2><?php echo $this->translate('Additional information') ?>:</h2>

<h3><?php echo get_class($this->exception); ?></h3>

<dl>

    <dt><?php echo $this->translate('File') ?>:</dt>

    <dd>

        <pre class="prettyprint linenums"><?php echo $this->exception->getFile() ?>:<?php echo $this->exception->getLine() ?></pre>

    </dd>

    <dt><?php echo $this->translate('Message') ?>:</dt>

    <dd>

        <pre class="prettyprint linenums"><?php echo $this->exception->getMessage() ?></pre>

    </dd>

    <dt><?php echo $this->translate('Stack trace') ?>:</dt>

    <dd>

        <pre class="prettyprint linenums"><?php echo $this->exception->getTraceAsString() ?></pre>

    </dd>

</dl>

<?php

    $e = $this->exception->getPrevious();

    if ($e) :

?>

<hr/>

<h2><?php echo $this->translate('Previous exceptions') ?>:</h2>

<ul>

    <?php while($e) : ?>

    <li>

        <h3><?php echo get_class($e); ?></h3>

        <dl>

            <dt><?php echo $this->translate('File') ?>:</dt>

            <dd>

                <pre class="prettyprint linenums"><?php echo $e->getFile() ?>:<?php echo $e->getLine() ?></pre>

            </dd>

            <dt><?php echo $this->translate('Message') ?>:</dt>

            <dd>

                <pre class="prettyprint linenums"><?php echo $e->getMessage() ?></pre>

            </dd>

            <dt><?php echo $this->translate('Stack trace') ?>:</dt>

            <dd>

                <pre class="prettyprint linenums"><?php echo $e->getTraceAsString() ?></pre>

            </dd>

        </dl>

    </li>

    <?php

        $e = $e->getPrevious();

        endwhile;

    ?>

</ul>

<?php endif; ?>

<?php else: ?>

<h3><?php echo $this->translate('No Exception available') ?></h3>

<?php endif ?>

<?php endif ?>

代码解析：

echo $this->message 输出错误信息

if (isset($this->reason) && $this->reason) 判断是否存在错误，$this->reason 有多种类型，控制器路由分发错误(error-controller-cannot-dispatch)、控制器不存在(error-controller-not-found)、无效控制器(error-controller-invalid)、路由不匹配(error-router-no-match)及其他

$this->controller 表示控制器名

$this->controller_class 表示控制器类名

以上内容是404错误提示模板内容；模板能够输出导致错误异常的文件名、出错所在行、错误类型等信息。在后继开发中可以通过404的相关提示信息找到出错的问题点。

针对于 Action 对应的模板文件在此就先只写一个indexAction 对应的模板文件，其他各个Action对应的模板文件内容将在后续的讲解中添加，以便与其他内容相互对应。

下面是 index.phtml 内容：

<table border="1">
<tr>
<td>Welcome to ZF2 world</td>
</tr>
</table>

模板的内容极其简单，就是一个表格并在单元格中有句 Welcome to ZF2 world，他的意义就是在访问 indexAction 的时候可以在页面上看 Welcome to ZF2 world。

经过以上众多的准备工作，现在我们已经可以通过 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模板做得丰富多彩、漂亮。

模型不仅是ZF2的重要组成部分，同时也是众MVC框架的重要组成部分。他的重要性主要在于处理用户与数据库之间的访问与操作功能。ZF2 本身并不直接直接提供 Zend\Model 组件，因为模型是一种业务逻辑，针对不同的项目可能会用不同的商业业务处理逻辑，模型的具体工作流程取决于你对相关模型组件个体设计。虽然ZF2本身并不提供模型，但ZF2提供了很多用于实现用户模型的各种组件，用户用通过ZF2提供的组件来构建自已的模型类，并且可以通过映射器对其进行映射，以方便应用程序的前端控制对他进行引用及使用，最终实现对数据库的一系列操作。

模型类的编写没有一个统一的写法，不同的人有不同的想法及写法，不同的业务逻辑有不同的实现方法，最终需求根据实际情况进行编写。在本章节中将介绍两种作者在开发中常用到的写法，一种是ORM对象映射技术，另一种是自定义对象。本章节的内容会结合前面章节中包括内容 控制器、视图模板等，同时也会涉及到ZF2 Zend\Db，Zend\Form 等相关知识内容进行综合的讲解。

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将跳转到删除的确认页面，然后确认是否删除。

当天新闻记录不断增加的时候，必然导致新闻列表不的加长以致用户不能在一屏内显示完所有内容，致使用户需要不停的拉动滚动条来获取更多的内容，这样无形之中给用户浏览新闻添加了不少障碍；因此对数据进行分页将是必然。之前做过网站的都应该知道分页样式及功能的实现有多种方法，分页可以根据不同的需要进行定制，但有一个缺点就是开发者基本上都需要自已写一个分页类库来加以调用。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)) 判断是否有上一页

<<上一页链接

foreach ($this->pagesInRange as $page) 循环首页链接页码

<?php echo $page; ?>生成各页的导航链接

if (isset($this->next)) 判断是否有下一页

>>下一页链接

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

在任何一个网站系统中数据库的操作都是一个重心核心问题，在很多时候做为一个开发都有自已已经熟练使用的一套数据库操作类库，使用自已熟悉的类库不仅有助于提高开发效率，也有助于发现问题。在此作者根据自已的使用习惯套用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章节是综合性的一个章节，章节内容包含从模块配置到建立模型、模板、使用模型、模板等内容。知识要点多，掌握不易，要想能够轻松快捷的使用ZF2框架给开发提供的数据库驱动，就需要不断的练习数据操作类库的使用。本章节内容的的重点及难道就是如何使用ZF2框架提供的数据库驱动对完成对数据库的完全操作，掌握本章节内容至少可以说已经基本完成了对ZF2的入门。为了进一步巩固前面第1章至第6章的内容，在接下来的章节里将再通过两个实例来加强ZF2的开发的重点要点内容。两个实例：一个是ZF2官网的Album实例整合应用；另一个是用户登录验证，使用持久性验证。

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

实例内容通过一个模块来进行讲解，可以更好的了解模块之间的对比性，以及模块与模块之前的耦合性。

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’；在前面章节的内容已经有说明，每增加一个模块都需要在全局配置文件中添加进行，也就是对模块进行注册使用。

添加文件：/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(){} 获取模块服务配置信息

模块配置文件主要对路由、视图等进行配置，此处配置关系到整个模块的访问方式及其他使用方式。

添加文件：/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布局

在此我们仍然后前面章节提到的 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’);

模型是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的记录行

添加文件：/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'
            ),
        ));

    }
}

添加文件：/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(){} 设置数据库网关

模板是数据处理后的最终展现平台，也是用户操作与感知的入口。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一个完整模块的存在形式，以及与其他模块同时并存且同时协同工作的具体应用，在进行具体项目开发的时候可以借鉴或参考此例以便开发出不同功能的模块，使用项目模块能够共同协同工作。

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

在开始前先建立一个数据表用来存放用户的认证字段，通俗的说就是用户名与密码。还使用之前的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’);

为了方便讲解与引用，将认证类放在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(); 实例化一个认证服务，以实现持久性认证

认证类已经建立好，将在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个章节的讲解，本书也就可以说已经完成了他的使命；本书从零开始对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/ 这里基本上可以找到你所需要的所有的ZF分类的内容，或一些简短的例子。

本书到此结束，作者真心希望此书已经引导您进入了ZF2的世界。祝愿所有开发者一路顺利。谢谢。