Zend Framework 2 教程:从零开始构建一个完整的 Web 应用
前言:为什么学习 ZF2?
虽然 Zend Framework 3 和 4 已经成为主流,但 ZF2 仍然在许多遗留项目中运行,更重要的是,理解 ZF2 的核心概念(如 MVC 架构、服务管理、事件驱动等)对于学习 ZF3/4 和其他现代 PHP 框架(如 Symfony、Laravel)非常有帮助,ZF2 是一个功能强大、高度灵活的企业级框架,它教会了开发者如何构建结构良好、可维护、可扩展的应用程序。

第一部分:环境准备与项目安装
在开始之前,请确保你的开发环境满足以下要求:
- PHP 5.3.23 或更高版本 (推荐 5.4+)
- Web 服务器 (如 Apache 2.4+ 或 Nginx)
- 数据库 (如 MySQL 5.5+)
- Composer:PHP 的依赖管理工具。
步骤 1:使用 Composer 创建 ZF2 项目
ZF2 推荐使用 Composer 来创建和管理项目,打开你的终端或命令行工具,执行以下命令:
# 创建一个名为 "my-zf2-app" 的目录,并使用 Composer 下载 ZF2 的骨架应用程序 composer create-project --stability="dev" zendframework/skeleton-application my-zf2-app
注意:
--stability="dev"会安装开发版本,这对于学习 ZF2 的最新特性很有帮助,在生产环境中,你应该使用稳定版本。
步骤 2:配置虚拟主机
为了让你的应用能够正确处理 URL 路由,你需要配置一个虚拟主机。

对于 Apache:
-
启用
mod_rewrite模块:# 在 Ubuntu/Debian 上 sudo a2enmod rewrite sudo service apache2 restart
-
在你的 Apache 配置文件中(
/etc/apache2/sites-available/000-default.conf)添加以下内容:<VirtualHost *:80> ServerName zf2.local # 将此域名指向你的本地 hosts 文件 DocumentRoot /path/to/your/my-zf2-app/public <Directory /path/to/your/my-zf2-app/public> DirectoryIndex index.php AllowOverride All Require all granted </Directory> </VirtualHost> -
编辑你的
/etc/hosts文件,添加0.0.1 zf2.local。
(图片来源网络,侵删) -
重启 Apache。
对于 Nginx:
-
创建一个 Nginx 配置文件(
/etc/nginx/sites-available/my-zf2-app):server { listen 80; server_name zf2.local; root /path/to/your/my-zf2-app/public; index index.php; location / { try_files $uri $uri/ /index.php?$query_string; } location ~ \.php$ { fastcgi_pass unix:/var/run/php/php7.4-fpm.sock; # 根据你的 PHP 版本调整 fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; } } -
创建一个符号链接到
sites-enabled目录并重启 Nginx。
在浏览器中访问 http://zf2.local,你应该能看到 ZF2 的欢迎页面。
第二部分:ZF2 核心概念解析
ZF2 的核心是 MVC (Model-View-Controller) 架构,但它的实现方式非常独特。
MVC (Model-View-Controller)
- Model (模型):负责数据和业务逻辑,在 ZF2 中,它通常是你的实体类和数据访问层(Doctrine ORM)。
- View (视图):负责展示数据,在 ZF2 中,视图文件是
.phtml文件,它们接收来自控制器的数据并将其渲染成 HTML。 - Controller (控制器):作为模型和视图之间的协调者,它接收用户请求,调用模型处理业务逻辑,然后将数据传递给视图进行渲染。
入口脚本与引导
当你访问 http://zf2.local/index.php 时,public/index.php 文件被调用,它的主要作用是:
- 定义
ZF2_PATH:指向 Zend Framework 库的路径。 - 包含
init_autoloader.php:设置项目的自动加载机制。 - 引导应用程序:调用
Zend\Mvc\Application::init(),这是启动整个 ZF2 应用程序的关键。
模块
ZF2 是一个基于模块的应用程序框架,一个模块就是一组功能(如博客、用户管理)的集合,它包含自己的 MVC 组件、配置、类等。module.config.php 是每个模块的核心配置文件。
服务管理器
这是 ZF2 的 心脏,它是一个依赖注入容器,负责管理你应用程序中的所有对象(服务)。
- 服务:任何你想要在应用中复用的对象,如数据库连接、邮件服务、控制器实例等。
- 工厂:用于创建服务的类,当你需要一个服务时,你告诉服务管理器这个服务的名称,它会找到对应的工厂来创建并返回这个服务实例。
- 控制器插件:为控制器提供辅助功能的对象,
url()用于生成链接,flashMessenger()用于显示临时消息。
路由与事件
- 路由:将 URL 映射到特定的控制器和动作。
/user/profile可能会映射到UserController的profileAction,路由定义在module.config.php中。 - 事件:ZF2 是一个事件驱动的框架,应用程序的生命周期由一系列事件组成(如
route,dispatch),你可以在这些事件发生时,执行自定义的逻辑,例如在dispatch事件中检查用户是否已登录。
第三部分:实战:构建一个简单的博客模块
我们将创建一个名为 Blog 的模块,它可以列出所有文章。
步骤 1:创建模块结构
在你的项目根目录下,创建以下文件夹和文件:
module/
└── Blog/
├── Module.php
├── config/
│ └── module.config.php
├── src/
│ └── Blog/
│ ├── Controller/
│ │ └── IndexController.php
│ └── Model/
│ └── Post.php
├── view/
│ └── blog/
│ └── index/
│ └── index.phtml
└── autoload_classmap.php
步骤 2:创建 Module.php
module/Blog/Module.php 用于加载模块的自动加载配置。
// module/Blog/Module.php
namespace Blog;
class Module
{
public function getAutoloaderConfig()
{
return array(
'Zend\Loader\ClassMapAutoloader' => array(
__DIR__ . '/autoload_classmap.php',
),
'Zend\Loader\StandardAutoloader' => array(
'namespaces' => array(
__NAMESPACE__ => __DIR__ . '/src/' . __NAMESPACE__,
),
),
);
}
public function getConfig()
{
return include __DIR__ . '/config/module.config.php';
}
}
步骤 3:创建自动加载映射
module/Blog/autoload_classmap.php 用于明确指定类的文件路径,以提高性能。
// module/Blog/autoload_classmap.php
return array(
'Blog\Module' => __DIR__ . '/Module.php',
'Blog\Controller\IndexController' => __DIR__ . '/src/Blog/Controller/IndexController.php',
'Blog\Model\Post' => __DIR__ . '/src/Blog/Model/Post.php',
);
步骤 4:创建模块配置
module/Blog/config/module.config.php 是模块的核心,定义了路由、视图管理器、控制器映射等。
// module/Blog/config/module.config.php
<?php
return array(
'controllers' => array(
'invokables' => array(
'Blog\Controller\Index' => 'Blog\Controller\IndexController',
),
),
'router' => array(
'routes' => array(
'blog' => array(
'type' => 'segment',
'options' => array(
'route' => '/blog[/:action]',
'constraints' => array(
'action' => '[a-zA-Z][a-zA-Z0-9_-]*',
),
'defaults' => array(
'controller' => 'Blog\Controller\Index',
' 