Zend Framework 快速指南



Zend Framework - 简介

PHP Web 框架是一组类,有助于开发 Web 应用程序。Zend 是最流行的 PHP 框架之一。它是一个开源的 MVC 框架,用于快速开发现代 Web 应用程序。Zend Framework 具有多个松散耦合的组件,因此被称为“组件库”。Zend Framework 提供任何 PHP 堆栈和 Zend 服务器来运行 Zend 框架应用程序。

Zend Studio 是一款 IDE,包含与 Zend Framework 集成的功能。它提供 MVC 视图和代码生成。当前的 Zend 框架 3.0 包括新的组件,例如 JSON RPC 服务器、XML 到 JSON 转换器、PSR-7 功能以及与 PHP 7 的兼容性。

Zend Framework 2 是一个开源框架,用于使用 PHP 5.3+ 开发 Web 应用程序和服务。Zend Framework 2 使用 100% 面向对象的代码,并利用 PHP 5.3 的大多数新功能,即命名空间、Lambda 函数闭包

Zend Framework 2 发展自 Zend Framework 1,这是一个成功的 PHP 框架,下载量超过 1500 万次。Zend Server 具有免费社区版和商业版。

Zend Framework 特性

Zend Framework 的一些主要特性如下所示:

  • 纯面向对象的 Web 应用程序框架
  • 高级 MVC 实现
  • 支持多种数据库,包括 PostgreSQL、SQLite 等。
  • 简单的云 API
  • 会话管理
  • 数据加密
  • 灵活的 URI 路由
  • Zend 提供 RESTful API 开发支持。
  • 代码可重用且易于维护。

为什么选择 Zend Framework?

是什么使 Zend Framework 成为 PHP 开发人员使用的首要框架之一?因为它提供了干净且稳定的代码,并附带知识产权。它也使编程更容易。它是一个快速、易于学习且方便的框架。Zend 支持强大的加密工具和密码哈希技术。

Zend 目标

以下是 Zend Framework 的目标。

  • 灵活性
  • 简单高效
  • 兼容性
  • 可扩展性 - 程序员可以轻松扩展所有框架类。
  • 可移植性 - 支持多种环境

Zend 应用程序

以下流行产品是使用 Zend Framework 开发的。

  • McAfee 公司网站
  • IBM 公司网站
  • Magento - 一个流行的购物车网站。

Zend Framework 的优势

Zend Framework 的一些优势列在下面。

  • 松散耦合 - Zend 提供了删除应用程序中不需要的模块或组件的选项。

  • 性能 - Zend Framework 对性能进行了高度优化。Zend Framework 3 比其前一个版本快 4 倍。

  • 安全性 - 框架支持行业标准加密。

  • 测试 - PHPUnit 集成到 Zend 中,因此您可以轻松测试框架。

在下一章中,我们将学习如何安装 Zend Framework。

Zend Framework - 安装

要安装 Zend Framework,我们必须首先安装 Composer 和最新版本的 PHP,如下面的步骤所示。

  • 安装 Composer - Zend 使用 Composer 来管理其依赖项,因此请确保您的机器上已安装 Composer。如果未安装 Composer,请访问 Composer 的官方网站并安装它。

  • 安装最新版本的 PHP - 要获得 Zend Framework 的最大收益,请安装最新版本的 PHP。Zend Framework 3 的最低要求版本为 PHP 5.6 或更高版本。

安装 Zend Framework

Zend Framework 可以通过两种方式安装。它们如下所示:

  • 手动安装
  • 基于 Composer 的安装

让我们详细讨论这两种安装。

手动安装

通过访问以下链接下载最新版本的 Zend Framework:https://framework.zend.com/downloads/archives

将下载的压缩文件的内容解压缩到您想要保存的文件夹中。一旦您的本地机器上有 Zend Framework 的副本,基于 Zend Framework 的 Web 应用程序就可以访问框架类。虽然有多种方法可以实现此目的,但您的 PHP include_path 需要包含到分发版中 /library 目录下 Zend Framework 类的路径。此方法仅适用于 Zend Framework 2.4 及更早版本。

基于 Composer 的安装

要轻松安装 Zend Framework,请使用 Composer 工具。这是安装最新版本 Zend Framework 的首选方法。要安装 Zend Framework 的所有组件,请使用以下 Composer 命令:

$ composer require zendframework/zendframework

每个 Zend Framework 模块/组件也可以单独安装。例如,要安装 Zend Framework 的MVC 组件,请使用以下composer命令:

$ composer require zendframework/zend-mvc

Zend Framework - 框架应用

让我们使用 Zend Framework MVC 层和模块系统创建一个框架应用。

使用 Composer 安装

创建新 Zend Framework 项目的最简单方法是使用 Composer。它定义如下:

$ cd /path/to/install 
$ composer create-project -n -sdev zendframework/skeleton-application myapp

您将在屏幕上看到以下结果:

Installing zendframework/skeleton-application (dev-master 
   941da45b407e4f09e264f000fb537928badb96ed)
   - Installing zendframework/skeleton-application (dev-master master)
   Cloning master

Created project in myapp
Loading composer repositories with package information
Installing dependencies (including require-dev) from lock file
   - Installing zendframework/zend-component-installer (0.3.0)
   Loading from cache
  
   - Installing zendframework/zend-stdlib (3.0.1)
   Loading from cache
   
   - Installing zendframework/zend-config (2.6.0)
   Loading from cache
   
   - Installing zendframework/zend-loader (2.5.1)
   Loading from cache
   
   - Installing zendframework/zend-eventmanager (3.0.1)
   Loading from cache
   
   - Installing zendframework/zend-view (2.8.0)
   Loading from cache
   
   - Installing container-interop/container-interop (1.1.0)
   Loading from cache
   
   - Installing zendframework/zend-servicemanager (3.1.0)
   Loading from cache
   
   - Installing zendframework/zend-validator (2.8.1)
   Loading from cache
   
   - Installing zendframework/zend-escaper (2.5.1)
   Loading from cache
   
   - Installing zendframework/zend-uri (2.5.2)
   Loading from cache
   
   - Installing zendframework/zend-http (2.5.4)
   Loading from cache
   
   - Installing zendframework/zend-router (3.0.2)
   Loading from cache
   
   - Installing zendframework/zend-modulemanager (2.7.2)
   Loading from cache

   - Installing zendframework/zend-mvc (3.0.1)
   Loading from cache
   
   - Installing zendframework/zend-skeleton-installer (0.1.3)
   Loading from cache
   
   - Installing zfcampus/zf-development-mode (3.0.0)
   Loading from cache
zendframework/zend-config suggests installing zendframework/zend-filter
   (Zend\Filter component)
zendframework/zend-config suggests installing zendframework/zend-i18n
   (Zend\I18n component)
zendframework/zend-config suggests installing zendframework/zend-json
   (Zend\Json to use the Json reader or writer classes)
zendframework/zend-view suggests installing zendframework/zend-authentication
   (Zend\Authentication component)
zendframework/zend-view suggests installing zendframework/zend-feed
   (Zend\Feed component)
zendframework/zend-view suggests installing zendframework/zend-filter
   (Zend\Filter component)
zendframework/zend-view suggests installing zendframework/zend-i18n
   (Zend\I18n component)
zendframework/zend-view suggests installing zendframework/zend-json
   (Zend\Json component)
zendframework/zend-view suggests installing zendframework/zend-navigation
   (Zend\Navigation component)
zendframework/zend-view suggests installing zendframework/zend-paginator
   (Zend\Paginator component)
zendframework/zend-view suggests installing zendframework/zend-permissions-acl
   (Zend\Permissions\Acl component)
zendframework/zend-servicemanager suggests installing ocramius/proxy-manager
   (ProxyManager 1.* to handle lazy initialization of services)
zendframework/zend-validator suggests installing zendframework/zend-db
   (Zend\Db component)
zendframework/zend-validator suggests installing zendframework/zend-filter
   (Zend\Filter component, required by the Digits validator)
zendframework/zend-validator suggests installing zendframework/zend-i18n
   (Zend\I18n component to allow translation of validation error messages as well as
   to use the various Date validators)
zendframework/zend-validator suggests installing zendframework/zend-i18nresources
   (Translations of validator messages)
zendframework/zend-validator suggests installing zendframework/zend-math
   (Zend\Math component)
zendframework/zend-validator suggests installing zendframework/zend-session
   (Zend\Session component)
zendframework/zend-router suggests installing zendframework/zend-i18n
   (^2.6, if defining translatable HTTP path segments)

zendframework/zend-modulemanager suggests installing zendframework/zend-console
   (Zend\Console component)
zendframework/zend-mvc suggests installing zendframework/zend-json ((^2.6.1 ||
   ^3.0) To auto-deserialize JSON body content in AbstractRestfulController
   extensions, when json_decode is unavailable)
zendframework/zend-mvc suggests installing zendframework/zend-mvc-console
   (zend-mvc-console provides the ability to expose zend-mvc as a console application)
zendframework/zend-mvc suggests installing zendframework/zend-mvc-i18n
   (zendmvc-i18n provides integration with zend-i18n, including a translation bridge
   and translatable route segments)
zendframework/zend-mvc suggests installing zendframework/zend-mvc-pluginfileprg
   (To provide Post/Redirect/Get functionality around forms that container
   file uploads)
zendframework/zend-mvc suggests installing zendframework/zend-mvc-pluginflashmessenger
   (To provide flash messaging capabilities between requests)
zendframework/zend-mvc suggests installing zendframework/zend-mvc-pluginidentity
   (To access the authenticated identity (per zend-authentication) in controllers)
zendframework/zend-mvc suggests installing zendframework/zend-mvc-plugin-prg
   (To provide Post/Redirect/Get functionality within controllers)
zendframework/zend-mvc suggests installing zendframework/zend-psr7bridge
   ((^0.2) To consume PSR-7 middleware within the MVC workflow)
zendframework/zend-mvc suggests installing zendframework/zend-servicemanager-di
   (zend-servicemanager-di provides utilities for integrating zend-di and
   zendservicemanager in your zend-mvc application)

Generating autoload files
   Removing optional packages from composer.json
   Updating composer.json
Removing zendframework/zend-skeleton-installer...
   - Removing zendframework/zend-skeleton-installer (0.1.3)
   Removed plugin zendframework/zend-skeleton-installer.
   Removing from composer.json
   Complete!
> zf-development-mode enable
You are now in development mode.

现在应用程序已安装,您可以立即使用PHP 的内置 Web 服务器对其进行测试:

$ cd path/to/install/myapp 
$ composer serve

然后您将看到以下响应:

> php -S 0.0.0.0:8080 -t public/ public/index.php 

这将在端口 8080 上启动 PHP 内置 CLI 服务器。启动开发服务器后,您可以访问位于(https://127.0.0.1:8080/)的站点。内置 CLI 服务器仅用于开发。

CLI Server

单元测试

要运行框架单元测试,请在终端中键入以下命令。

$ composer require --dev zendframework/zend-test

它将产生以下响应:

Using version ^3.0 for zendframework/zend-test 
   ./composer.json has been updated 
Loading composer repositories with package information 
Updating dependencies (including require-dev) 
   - Installing zendframework/zend-dom (2.6.0) 
   Loading from cache  
   
   - Installing zendframework/zend-console (2.6.0) 
   Loading from cache  
   
   - Installing sebastian/version (2.0.1) 
   Loading from cache 
   
   - Installing symfony/yaml (v3.2.1)
   Downloading: 100%           
   
   - Installing sebastian/resource-operations (1.0.0) 
   Loading from cache  
   
   - Installing sebastian/recursion-context (2.0.0) 
   Loading from cache  
   
   - Installing sebastian/object-enumerator (2.0.0) 
   Loading from cache  
   
   - Installing sebastian/global-state (1.1.1) 
   Loading from cache  
   
   - Installing sebastian/exporter (2.0.0) 
   Loading from cache  
   
   - Installing sebastian/environment (2.0.0) 
   Loading from cache  
   
   - Installing sebastian/diff (1.4.1) 
   Loading from cache  
   
   - Installing sebastian/comparator (1.2.2) 
   Loading from cache  
   
   - Installing phpunit/php-text-template (1.2.1) 
   Loading from cache  
   
   - Installing doctrine/instantiator (1.0.5) 
   Loading from cache  
   
   - Installing phpunit/phpunit-mock-objects (3.4.3) 
   Downloading: 100%          
   
   - Installing phpunit/php-timer (1.0.8)
   Loading from cache  
   
   - Installing phpunit/php-file-iterator (1.4.2) 
   Loading from cache  
   
   - Installing sebastian/code-unit-reverse-lookup (1.0.0) 
   Loading from cache  
   
   - Installing phpunit/php-token-stream (1.4.9) 
   Loading from cache  
   
   - Installing phpunit/php-code-coverage (4.0.4) 
   Downloading: 100%           
   
   - Installing webmozart/assert (1.2.0) 
   Loading from cache  
   
   - Installing phpdocumentor/reflection-common (1.0) 
   Loading from cache  
   
   - Installing phpdocumentor/type-resolver (0.2.1) 
   Loading from cache  
   
   - Installing phpdocumentor/reflection-docblock (3.1.1) 
   Loading from cache  
   
   - Installing phpspec/prophecy (v1.6.2) 
   Loading from cache  
   
   - Installing myclabs/deep-copy (1.5.5) 
   Loading from cache  
   
   - Installing phpunit/phpunit (5.7.4) 
   Downloading: 100%          
   
   - Installing zendframework/zend-test (3.0.2) 
   Loading from cache

zendframework/zend-console suggests installing zendframework/zend-filter 
   (To support DefaultRouteMatcher usage) 
symfony/yaml suggests installing symfony/console (For validating YAML files 
   using the lint command) 
sebastian/global-state suggests installing ext-uopz (*) 
phpunit/phpunit-mock-objects suggests installing ext-soap (*) 
phpunit/php-code-coverage suggests installing ext-xdebug (>=2.4.0) 
phpunit/phpunit suggests installing phpunit/php-invoker (~1.1) 
phpunit/phpunit suggests installing ext-xdebug (*) 
zendframework/zend-test suggests installing zendframework/zend-mvc-console 
   (^1.1.8, to test MVC <-> console integration) 
Writing lock file 
Generating autoload files

现在测试支持已启用,因此您可以使用以下命令运行测试。

$ ./vendor/bin/phpunit

Apache Web 服务器

在生产环境中托管基于 Zend Framework 的应用程序非常简单直接。只需在 Apache 配置文件中创建一个虚拟主机,并将DocumentRoot指向 Zend Framework 应用程序的Public文件夹即可。

下面给出一个示例配置 (myapp):

<VirtualHost *:80> 
   ServerName myapp.localhost 
   DocumentRoot /path/to/install/myapp/public 
   <Directory /path/to/install/myapp/public> 
      DirectoryIndex index.php 
      AllowOverride All 
      Order allow,deny 
      Allow from all 
      <IfModule mod_authz_core.c> 
         Require all granted 
      </IfModule> 
   </Directory> 
</VirtualHost>

Zend Framework - MVC 架构

在继续下一章之前,让我们简要了解一下 MVC。模型-视图-控制器是一种软件方法,它将应用程序逻辑与表示分离。在实践中,它允许网页包含最少的 PHP 脚本,因为表示与它分离。

MVC 组件的简要描述如下

  • 模型 - 模型表示应用程序数据的结构。通常,模型类包含有助于在后端数据库(MySQL、PostgreSQL 等)中检索、插入更新业务数据的函数。

  • 视图 - 视图是 MVC 应用程序的表示层。它通过控制器获取模型数据并根据需要显示它。它与控制器模型松散耦合,因此可以更改它而不会影响模型和控制器。

  • 控制器 - 控制器是 MVC 架构的主要组件。每个请求首先都会到达控制器。换句话说,控制器处理所有请求,并充当模型、视图和处理 HTTP 请求并生成响应所需的任何其他资源之间的中介。

在下一章中,我们将了解 Zend Framework 的不同概念。

Zend Framework - 概念

Zend Framework 是一个包含 60 多个组件的集合。它们彼此松散连接。它们既可以作为独立组件使用,也可以作为一组组件作为一个单元工作。

Zend Framework 提供了三个最重要的组件,它们是:

  • zend-servicemanager
  • zend-eventmanager 和
  • zend-modulemanager。

它们使 Zend 组件能够有效地与其他组件集成。

  • 事件管理器 - 它提供了创建基于事件的编程的能力。这有助于创建、注入和管理新事件。

  • 服务管理器 - 它提供了无需太多努力即可从任何地方使用任何服务(PHP 类)的能力。

  • 模块管理器 - 将具有相似功能的 PHP 类集合转换为称为模块的单个单元的能力。新创建的模块可以作为单个单元进行使用、维护和配置。

我们将在后续章节中详细介绍这些概念。

Zend Framework - 服务管理器

Zend Framework 包含一个强大的服务定位器模式实现,称为zend-servicemanager。Zend 框架广泛使用服务管理器来实现其所有功能。服务管理器为 Zend Framework 提供了高级抽象。它还可以很好地与 Zend Framework 的所有其他组件集成。

安装服务管理器

可以使用composer工具安装服务管理器组件。

composer require zendframework/zend-servicemanager

示例

首先,所有服务都需要注册到服务管理器中。一旦服务注册到服务器管理器系统中,就可以在任何时候以最少的努力访问它。服务管理器提供了许多注册服务的选项。一个简单的示例如下所示:

use Zend\ServiceManager\ServiceManager; 
use Zend\ServiceManager\Factory\InvokableFactory; 
use stdClass;  
$serviceManager = new ServiceManager([ 
   'factories' => [stdClass::class => InvokableFactory::class,], 
]);

上面的代码使用Factory选项将stdClass注册到系统中。现在,我们可以使用服务管理器的get()方法随时获取 stdClass 的实例,如下所示。

use Zend\ServiceManager\ServiceManager;  
$object = $serviceManager->get(stdClass::class);

get() 方法共享检索到的对象,因此多次调用 get() 方法返回的对象是同一个实例。要每次获取不同的实例,服务管理器提供了另一种方法,即build()方法。

use Zend\ServiceManager\ServiceManager;  
$a = $serviceManager->build(stdClass::class); 
$b = $serviceManager->build(stdClass::class);

服务管理器注册

服务管理器提供了一组注册组件的方法。一些最重要的方法如下所示:

  • 工厂方法
  • 抽象工厂方法
  • 初始化方法
  • 委托工厂方法

我们将在接下来的章节中详细讨论每一个方法。

工厂方法

工厂基本上是任何可调用对象或任何实现了FactoryInterface(Zend\ServiceManager\Factory\FactoryInterface)的类。

FactoryInterface 具有一个方法:

public function __invoke(ContainerInterface $container, $requestedName, array 
   $options = null)

FactoryInterface 的参数详细信息如下:

  • container (ContainerInterface) - 它是 ServiceManager 的基本接口。它提供了一个获取其他服务的选项。

  • requestedName - 它是服务名称。

  • options - 它提供了服务所需的其他选项。

让我们创建一个实现 FactoryInterface 的简单类,并看看如何注册该类。

类 Test - 要检索的对象

use stdClass;  
class Test { 
   public function __construct(stdClass $sc) { 
      // use $sc 
   } 
} 

Test类依赖于 stdClass。

类 TestFactory - 初始化测试对象的类

class TestFactory implements FactoryInterface { 
   public function __invoke(ContainerInterface $container, $requestedName, 
      array $options = null) { 
      $dep = $container->get(stdClass::class); 
      return new Test($dep); 
   } 
}

TestFactory 使用容器检索 stdClass,创建 Test 类的实例并返回。

Zend Framework 的注册和使用

现在让我们了解如何注册和使用 Zend Framework。

serviceManager $sc = new ServiceManager([ 
   'factories' => [stdClass::class => InvokableFactory::class, 
      Test::class => TestFactory::class] 
]); 
$test = $sc->get(Test::class);

服务管理器提供了一个名为 **InvokableFactory** 的特殊工厂来检索任何没有依赖关系的类。例如,**stdClass** 可以使用 InvokableFactory 进行配置,因为 stdClass 不依赖于任何其他类。

serviceManager $sc = new ServiceManager([ 
   'factories' => [stdClass::class => InvokableFactory::class] 
]);  
$stdC = $sc->get(stdClass::class); 

另一种无需实现 **FactoryInterface** 或使用 **InvokableFactory** 即可检索对象的方法是使用如下所示的内联方法。

$serviceManager = new ServiceManager([ 
   'factories' => [ 
      stdClass::class => InvokableFactory::class, 
      Test::class => function(ContainerInterface $container, $requestedName) { 
         $dep = $container->get(stdClass::class); 
         return new Test($dep); 
      }, 
   ], 
]);

抽象工厂方法

有时,我们可能需要创建只有在运行时才知道的对象。这种情况可以使用 **AbstractFactoryInterface** 来处理,它派生自 FactoryInterface。

AbstractFactoryInterface 定义了一个方法来检查对象是否可以在请求的实例中创建。如果可以创建对象,它将使用 FactoryInterface 的 **__invoke** 方法创建对象并返回。

AbstractFactoryInterface 的签名如下:

public function canCreate(ContainerInterface $container, $requestedName) 

初始化器方法

初始化器方法是一个特殊的选项,用于为已创建的服务注入额外的依赖项。它实现了 **InitializerInterface**,并且可用的唯一方法的签名如下:

public function(ContainerInterface $container, $instance)  
function(ContainerInterface $container, $instance) { 
   if (! $instance instanceof EventManagerAwareInterface) { 
      return; 
   } 
   $instance->setEventManager($container->get(EventManager::class)); 
} 

在上面的例子中,该方法检查实例是否为 EventManagerAwareInterface 类型。如果它是 **EventManagerAwareInterface** 类型,则设置事件管理器对象,否则不设置。由于该方法可能设置也可能不设置依赖项,因此它不可靠,并会导致许多运行时问题。

委托工厂方法

Zend Framework 通过 **DelegatorFactoryInterface** 支持委托模式。它可以用来装饰服务。

此函数的签名如下:

public function __invoke(ContainerInterface $container, 
   $name, callable $callback, array $options = null 
); 

这里,**$callback** 负责装饰服务实例。

延迟服务

延迟服务是在创建时不会完全初始化的服务之一。它们只是被引用,只有在真正需要时才会被初始化。一个最好的例子是数据库连接,它可能并非在所有地方都需要。它是一种昂贵的资源,并且创建过程也需要花费时间。Zend framework 提供了派生自 **DelegatorFactoryInterface** 的 **LazyServiceFactory**,它可以借助 **Delegator** 概念和一个称为 **ocramius proxy manager** 的第三方代理管理器来生成延迟服务。

插件管理器

插件管理器扩展了服务管理器,并提供了额外的功能,如实例验证。Zend Framework 广泛使用插件管理器。

例如,所有验证服务都位于 **ValidationPluginManager** 下。

配置选项

服务管理器提供了一些选项来扩展服务管理器的功能。它们是 **shared、shared_by_default** 和 **aliases**。正如我们之前讨论的,检索到的对象默认情况下在请求的对象之间共享,我们可以使用 **build()** 方法获取一个不同的对象。我们也可以使用 **shared** 选项来指定哪些服务需要共享。**shared_by_default** 与 **shared** 功能相同,只是它适用于所有服务。

$serviceManager = new ServiceManager([ 
   'factories' => [ 
      stdClass::class => InvokableFactory::class 
   ], 
   'shared' => [ 
      stdClass::class => false // will not be shared 
   ], 
   'shared_by_default' => false, // will not be shared and applies to all service 
]);

**aliases** 选项可以用来为注册的服务提供一个替代名称。这既有优点也有缺点。从好的方面来说,我们可以为服务提供替代的简短名称。但同时,名称也可能脱离上下文并引入错误。

aliases' => ['std' => stdClass::class, 'standard' => 'std'] 

Zend Framework - 事件管理器

所有现代应用程序都需要强大而灵活的事件组件。Zend Framework 提供了一个这样的组件,**zend-eventmanager**。zend-eventmanager 有助于设计高级架构,并支持发布/订阅模式和面向方面的编程。

安装事件管理器

可以使用如下所示的 **Composer** 安装事件管理器:

composer require zendframework/zend-eventmanager 

事件管理器的概念

事件管理器的核心概念如下:

  • **事件** - 事件是任意命名的动作,例如 **greet**。

  • **监听器** - 任何 PHP 回调。它们附加到事件上,并在事件触发时被调用。监听器的默认签名为:

function(EventInterface $e)
  • **EventInterface 类** - 用于指定事件本身。它具有设置和获取事件信息的方法,如名称(set/getName)、目标(get/setTarget)和参数(get/setParams)。

  • **EventManager 类** - EventManager 的实例跟踪应用程序中所有定义的事件及其对应的监听器。EventManager 提供了一个方法 **attach** 来将监听器附加到事件上,并提供了一个方法 **trigger** 来触发任何预定义的事件。一旦触发被调用,EventManager 就会调用附加到它的监听器。

  • **EventManagerAwareInterface** - 为了让一个类支持基于事件的编程,它需要实现 EventManagerAwareInterface。它提供了两个方法,**setEventManager** 和 **getEventManager** 来获取和设置事件管理器。

示例

让我们编写一个简单的 PHP 控制台应用程序来理解事件管理器的概念。请按照以下步骤操作。

  • 创建一个名为“eventapp”的文件夹。

  • 使用 composer 安装 **zend-eventmanager**。

  • 在“eventapp”文件夹内创建一个 PHP 文件 **Greeter.php**。

  • 创建类 **Greeter** 并实现 **EventManagerAwareInterface**。

require __DIR__ . '/vendor/autoload.php'; 
class Greeter implements EventManagerAwareInterface { 
   // code 
}

这里,**require** 用于自动加载所有 composer 安装的组件。

在类 **Greeter** 中编写 **setEventManager** 方法,如下所示:

public function setEventManager(EventManagerInterface $events) { 
   $events->setIdentifiers([ __CLASS__, get_called_class(),]); 
   $this->events = $events; 
   return $this; 
}

此方法将当前类设置到给定的事件管理器($events 参数)中,然后将事件管理器设置到本地变量 **$events** 中。

下一步是在类 **Greeter** 中编写 **getEventManager** 方法,如下所示:

public function getEventManager() { 
   if (null === $this->events) { 
      $this->setEventManager(new EventManager()); 
   } 
   return $this->events; 
}

该方法从本地变量获取事件管理器。如果它不可用,则创建一个事件管理器的实例并返回它。

在类 **Greeter** 中编写一个方法 **greet**。

public function greet($message) { 
   printf("\"%s\" from class\n", $message); 
   $this->getEventManager()->trigger(__FUNCTION__, $this, $message ]); 
} 

此方法获取事件管理器并触发/触发附加到它的事件。

下一步是创建 **Greeter** 类的实例并将监听器附加到其方法 **greet** 上。

$greeter = new Greeter();  
$greeter->getEventManager()->attach('greet', function($e) { 
   $event_name = $e->getName(); 
   $target_name = get_class($e->getTarget()); 
   $params_json = json_encode($e->getParams());  
   printf("\"%s\" event of class \"%s\" is called." . 
      " The parameter supplied is %s\n",  
      $event_name,  
      $target_name,  
      $params_json); 
});

监听器回调只打印事件的名称、目标和提供的参数。

**Greeter.php** 的完整列表如下:

<?php  
require __DIR__ . '/vendor/autoload.php';  

use Zend\EventManager\EventManagerInterface; 
use Zend\EventManager\EventManager; 
use Zend\EventManager\EventManagerAwareInterface; 

class Greeter implements EventManagerAwareInterface { 
   protected $events;
   public function setEventManager(EventManagerInterface $events) { 
      $events->setIdentifiers([__CLASS__, get_called_class(), ]); 
      $this->events = $events; 
      return $this; 
   }  
   public function getEventManager() { 
      if (null === $this->events) { 
         $this->setEventManager(new EventManager()); 
      } 
      return $this->events; 
   } 
   public function greet($message) { 
      printf("\"%s\" from class\n", $message); 
      $this->getEventManager()->trigger(__FUNCTION__, $this, [$message ]); 
   } 
} 

$greeter = new Greeter(); 
$greeter->greet("Hello");  
$greeter->getEventManager()->attach('greet', function($e) { 
   $event_name = $e->getName(); 
   $target_name = get_class($e->getTarget()); 
   $params_json = json_encode($e->getParams()); 
   printf("\"%s\" event of class \"%s\" is called." . " The parameter supplied is %s\n",
      $event_name,
      $target_name,  
      $params_json); 
});  
$greeter->greet("Hello"); 

现在,在命令提示符 php **Greeter.php** 中运行应用程序,结果将如下所示:

"Hello" from class 
"Hello" from class 
"greet" event of class "Greeter" is called. The parameter supplied is ["Hello"] 

上述示例应用程序仅解释了事件管理器的基础知识。事件管理器提供了更多高级选项,例如 **监听器优先级、自定义回调原型/签名、短路** 等。事件管理器在 Zend MVC 框架中被广泛使用。

Zend Framework - 模块系统

Zend Framework 提供了一个强大的模块系统。模块系统有三个组件。它们如下:

  • **模块自动加载器** - 模块自动加载器负责从各种来源查找和加载模块。它也可以加载打包为 **Phar 档案** 的模块。模块自动加载器的实现位于 myapp/vendor/zendframework/zend-loader/src/ModuleAutoloader.php 中。

  • **模块管理器** - 一旦模块自动加载器找到模块,模块管理器就会为每个模块触发一系列事件。模块管理器的实现位于 myapp/vendor/zendframework/zendmodulemanager/src/ModuleManager.php 中。

  • **模块管理器监听器** - 它们可以附加到模块管理器触发的事件上。通过附加到模块管理器的事件,它们可以执行从解析和加载模块到为每个模块执行复杂工作的所有操作。

MVC Web 模块系统

Zend Framework 中的 MVC Web 应用程序通常编写为模块。单个网站可以包含一个或多个按功能分组的模块。面向 MVC 的模块的推荐结构如下:

module_root/ 
   Module.php 
   autoload_classmap.php 
   autoload_function.php 
   autoload_register.php 
   config/ 
      module.config.php 
   public/ 
      images/ 
      css/ 
      js/ 
   src/ 
      <module_namespace>/ 
      <code files> 
   test/ 
      phpunit.xml
      bootstrap.php 
      <module_namespace>/ 
         <test code files> 
   view/ 
      <dir-named-after-module-namespace>/ 
         <dir-named-after-a-controller>/ 
            <.phtml files>

该结构与上一章中讨论的相同,但这里它是通用的。**autoload_ 文件** 可以用作自动加载模块中可用类的默认机制,而无需使用 **zend-modulemanager** 中提供的先进 **模块管理器**。

  • **autoload_classmap.php** - 返回一个类名及其对应文件名数组。

  • **autoload_function.php** - 返回一个 PHP 回调。这可以利用 autoload_classmap.php 返回的类。

  • **autoload_register.php** - 注册 autoload_function.php 返回的 PHP 回调。

这些自动加载文件不是必需的,但建议使用。在框架应用程序中,我们没有使用 **autoload_ 文件**。

模块类

模块类应该命名为 **Module**,并且模块类的命名空间应该为 **模块名称**。这将有助于 Zend Framework 轻松解析和加载模块。框架应用程序(myapp)中的 **Application** 模块代码,myapp/module/Application/src/Module.php 如下所示:

namespace Application; 
class Module { 
   const VERSION = '3.0.2dev'; 
   public function getConfig() { 
      return include __DIR__ . '/../config/module.config.php'; 
   } 
}

Zend Framework 模块管理器将自动调用 **getConfig()** 函数并执行必要的步骤。

Zend Framework - 应用程序结构

在本章中,让我们了解 Zend Framework 应用程序的结构。**myapp** 应用程序的结构如下:

├── composer.json 
├── composer.lock 
├── CONDUCT.md 
├── config 
│   ├── application.config.php 
│   ├── autoload 
│   │   ├── development.local.php 
│   │   ├── development.local.php.dist 
│   │   ├── global.php 
│   │   ├── local.php.dist 
│   │   ├── README.md 
│   │   └── zend-developer-tools.local-development.php 
│   ├── development.config.php 
│   ├── development.config.php.dist 
│   └── modules.config.php 
├── CONTRIBUTING.md 
├── data 
│   └── cache 
│       └── module-classmap-cache.application.module.cache.php ├── docker-compose.yml 
├── Dockerfile 
├── LICENSE.md 
├── module 
│   └── Application 
│       ├── config 
│       ├── src 
│       ├── test 
│       └── view 
├── phpcs.xml 
├── phpunit.xml.dist 
├── public
│   ├── css 
│   │   ├── bootstrap.css 
│   │   ├── bootstrap.css.map 
│   │   ├── bootstrap.min.css 
│   │   ├── bootstrap.min.css.map 
│   │   ├── bootstrap-theme.css 
│   │   ├── bootstrap-theme.css.map 
│   │   ├── bootstrap-theme.min.css 
│   │   ├── bootstrap-theme.min.css.map 
│   │   └── style.css 
│   ├── fonts 
│   │   ├── glyphicons-halflings-regular.eot 
│   │   ├── glyphicons-halflings-regular.svg 
│   │   ├── glyphicons-halflings-regular.ttf 
│   │   ├── glyphicons-halflings-regular.woff 
│   │   └── glyphicons-halflings-regular.woff2 
│   ├── img 
│   │   ├── favicon.ico 
│   │   └── zf-logo-mark.svg 
│   ├── index.php 
│   ├── js 
│   │   ├── bootstrap.js 
│   │   ├── bootstrap.min.js 
│   │   └── jquery-3.1.0.min.js 
│   └── web.config 
├── README.md 
├── TODO.md 
├── Vagrantfile 
└── vendor     
├── autoload.php     
├── bin     
│   ├── phpunit -> ../phpunit/phpunit/phpunit     
│   ├── templatemap_generator.php -> ../zendframework/zend-
view/bin/templatemap_generator.php
│   └── zf-development-mode -> ../zfcampus/zf-development-mode/bin/zf-
development-mode 
├── composer     
│   ├── autoload_classmap.php     
│   ├── autoload_namespaces.php     
│   ├── autoload_psr4.php     
│   ├── autoload_real.php     
│   ├── ClassLoader.php     
│   ├── installed.json 
│   └── LICENSE     
├── container-interop 
│   └── container-interop     
├── doctrine 
│   └── instantiator     
├── myclabs 
│   └── deep-copy     
├── phpdocumentor     
│   ├── reflection-common     
│   ├── reflection-docblock 
│   └── type-resolver     
├── phpspec 
│   └── prophecy     
├── phpunit     
│   ├── php-code-coverage     
│   ├── php-file-iterator     
│   ├── php-text-template     
│   ├── php-timer     
│   ├── php-token-stream     
│   ├── phpunit 
│   └── phpunit-mock-objects     
├── sebastian     
│   ├── code-unit-reverse-lookup     
│   ├── comparator     
│   ├── diff     
│   ├── environment     
│   ├── exporter     
│   ├── global-state     
│   ├── object-enumerator
│   ├── recursion-context     
│   ├── resource-operations 
│   └── version     
├── symfony 
│   └── yaml     
├── webmozart 
│   └── assert     
├── zendframework     
│   ├── zend-component-installer     
│   ├── zend-config     
│   ├── zend-console     
│   ├── zend-dom     
│   ├── zend-escaper     
│   ├── zend-eventmanager     
│   ├── zend-http     
│   ├── zend-loader     
│   ├── zend-modulemanager     
│   ├── zend-mvc     
│   ├── zend-router     
│   ├── zend-servicemanager     
│   ├── zend-stdlib     
│   ├── zend-test     
│   ├── zend-uri     
│   ├── zend-validator 
│   └── zend-view 
└── zfcampus 
└── zf-development-mode  

73 directories, 55 files

Zend Framework 应用程序由不同的文件夹组成。它们如下:

  • **Application** - 此目录包含您的应用程序。它将容纳 MVC 系统,以及使用的配置、服务和您的引导文件。

  • **Config** - 此目录包含应用程序的配置文件。

  • **Data** - 此目录提供了一个存储应用程序数据的位置,这些数据是易失的,并且可能是临时的。

  • **Module** - 模块允许开发人员将一组相关的控制器分组到一个逻辑上组织的组中。

  • **Public** - 这是应用程序的文档根目录。它启动 Zend 应用程序。它还包含应用程序的资产,如 JavaScript、CSS、图像等。

  • **Vendor** - 此目录包含 composer 依赖项。

应用程序模块的结构

这是应用程序的主目录。Zend Framework 2 引入了一个强大而灵活的模块系统,以有效地组织应用程序。框架应用程序的 **Application** 模块为整个应用程序提供了引导、错误和路由配置。**Application** 模块的结构如下所示:

├── module 
│   └── Application 
│       ├── config 
│       │   └── module.config.php 
│       ├── src 
│       │   ├── Controller 
│       │   │   └── IndexController.php 
│       │   └── Module.php 
│       ├── test 
│       │   └── Controller 
│       │       └── IndexControllerTest.php 
│       └── view 
│           ├── application 
│           │   └── index 
│           │       └── index.phtml 
│           ├── error 
│           │   ├── 404.phtml 
│           │   └── index.phtml 
│           └── layout 
│               └── layout.phtml

让我们详细介绍每个模块目录:

  • **Application** - 这是模块的根目录。文件夹的名称将与模块的名称匹配,并且该名称也用作模块内所有定义类的 PHP 命名空间。它将容纳 MVC 系统,以及使用的配置、服务和您的引导文件。

  • **Config** - 模块的独立配置。

  • **Src** - 应用程序的主要业务逻辑。

  • **View** - 包含设计/演示(HTML)文件。例如,index.phtml。

  • **src/Module.php** - 它是模块的核心。它充当模块的“前端控制器”。Zend 在处理此模块中的任何 PHP 类之前处理 **src/Module.php** 文件。

  • **Application/config/module.config.php** - 它用于路由器配置和自动加载文件。

  • 应用/视图/布局 - 布局表示多个视图的公共部分。例如,页面页眉和页脚。默认情况下,布局应存储在views/layouts文件夹中。

所有模块都共享与上述应用模块相同或相似的结构。

Zend Framework - 创建模块

在本章中,我们将学习如何在 Zend Framework 中创建基于 MVC 的模块。让我们创建一个名为Tutorial的模块来了解模块创建过程。

  • 在 –myapp/module/Tutorial/src/ 目录中创建一个名为Module的新 PHP 类,并实现 ConfigProviderInterface 接口。

  • Tutorial设置为Module类的命名空间。

  • Module类中编写一个公共函数getConfig,并返回Tutorial模块的配置文件。

Module类的完整代码如下所示:

<?php  
namespace Tutorial; 
use Zend\ModuleManager\Feature\ConfigProviderInterface;
class Module implements ConfigProviderInterface { 
   public function getConfig() {    
      return include __DIR__ . '/../config/module.config.php'; 
   }    
} 

使用以下代码在composer.jsonautoload部分配置Tutorial模块。

"autoload": { 
   "psr-4": { 
      "Application\\": "module/Application/src/", 
      "Tutorial\\": "module/Tutorial/src/" 
   } 
}

使用 composer update命令更新应用程序,如下所示。

composer update 

composer命令将对应用程序进行必要的更改,并在命令提示符中显示日志,如下所示:

Loading composer repositories with package information 
Updating dependencies (including require-dev) 
   - Removing zendframework/zend-component-installer (0.3.0) 
   - Installing zendframework/zend-component-installer (0.3.1) 
   Downloading: 100%           
   
   - Removing zendframework/zend-stdlib (3.0.1) 
   - Installing zendframework/zend-stdlib (3.1.0) 
   Loading from cache  
   
   - Removing zendframework/zend-eventmanager (3.0.1) 
   - Installing zendframework/zend-eventmanager (3.1.0) 
   Downloading: 100%           
   
   - Removing zendframework/zend-view (2.8.0) 
   - Installing zendframework/zend-view (2.8.1) 
   Loading from cache  
   
   - Removing zendframework/zend-servicemanager (3.1.0) 
   - Installing zendframework/zend-servicemanager (3.2.0) 
   Downloading: 100%           
   
   - Removing zendframework/zend-escaper (2.5.1) 
   - Installing zendframework/zend-escaper (2.5.2) 
   Loading from cache  
   
   - Removing zendframework/zend-http (2.5.4) 
   - Installing zendframework/zend-http (2.5.5) 
   Loading from cache  
   
   - Removing zendframework/zend-mvc (3.0.1) 
   - Installing zendframework/zend-mvc (3.0.4) 
   Downloading: 100%          
   
   - Removing phpunit/phpunit (5.7.4) 
   - Installing phpunit/phpunit (5.7.5) 
   Downloading: 100%           

Writing lock file 
Generating autoload files 

/config/处创建模块配置文件“module.config.php”,代码如下:

<?php  
namespace Tutorial;  
   
use Zend\ServiceManager\Factory\InvokableFactory; 
use Zend\Router\Http\Segment;  
return [ 
   'controllers' => [ 
      'factories' => [Controller\TutorialController::class => InvokableFactory::class,], 
   ],
   'view_manager' => [ 
      'template_path_stack' => ['tutorial' => __DIR__ . '/../view',], 
   ], 
];

配置文件包含三个部分,如下所示:

  • 控制器配置 - 指定模块内可用的控制器。

  • 路由配置 - 指定如何将模块中的控制器解析为 URL。

  • 视图配置 - 指定与视图引擎相关的配置,例如视图的位置等。

在应用程序级配置文件 – myapp/config/modules.config.php 中配置Tutorial模块。

return ['Zend\Router', 'Zend\Validator', 'Application', 'Tutorial'];

在应用程序文件夹的根目录执行composer serve来运行应用程序。

我们已成功添加了一个新模块,但我们仍需要添加控制器、路由视图才能成功运行Tutorial模块。

Zend Framework - 控制器

如前所述,控制器在 Zend MVC 框架中发挥着重要作用。应用程序中的所有网页都需要由控制器处理。

在 Zend MVC 框架中,控制器是实现了 – Zend/Stdlib/DispatchableInterface 接口的对象。DispatchableInterface只有一个方法dispatch,它以Request对象作为输入,执行一些逻辑并返回Response对象作为输出。

dispatch(Request $request, Response $response = null) 

返回“Hello World”的控制器对象的简单示例如下:

use Zend\Stdlib\DispatchableInterface; 
use Zend\Stdlib\RequestInterface as Request; 
use Zend\Stdlib\ResponseInterface as Response;  
class HelloWorld implements DispatchableInterface { 
   public function dispatch(Request $request, Response $response = null) { 
      $response->setContent("Hello World!"); 
   } 
}

DispatchableInterface是基本的,它需要许多其他接口才能编写高级控制器。其中一些接口如下:

  • InjectApplicationEventInterface - 用于注入事件(Zend EventManager)

  • ServiceLocatorAwareInterface - 用于定位服务(Zend ServiceManager)

  • EventManagerAwareInterface - 用于管理事件(Zend EventManager)

牢记这些要点,Zend Framework 提供了许多实现这些接口的现成控制器。最重要的控制器如下所述。

AbstractActionController

AbstractActionController(Zend/Mvc/Controller/AbstractActionController)是 Zend MVC 框架中使用最广泛的控制器。它具有编写典型网页所需的所有必要功能。它允许路由(路由将请求 URL 与控制器及其方法之一匹配)匹配操作。匹配后,将调用以操作命名的控制器方法。

例如,如果匹配路由test,并且路由test为操作返回hello,则将调用helloAction方法。

让我们使用AbstractActionController编写我们的TutorialController

  • 创建一个名为TutorialController的新 PHP 类,扩展AbstractActionController,并将其放置在module/Tutorial/src/Controller/目录中。

  • Tutorial\Controller设置为命名空间。

  • 编写一个indexAction方法。

  • indexAction方法返回ViewModel对象。ViewModel对象用于将数据从控制器发送到视图引擎,我们将在后续章节中看到。

完整的代码清单如下:

?php  
namespace Tutorial\Controller;  
use Zend\Mvc\Controller\AbstractActionController; 
use Zend\View\Model\ViewModel;  
class TutorialController extends AbstractActionController { 
   public function indexAction() { 
      return new ViewModel(); 
   } 
}

我们已成功添加了新的TutorialController

AbstractRestfulController

AbstractRestfulController(Zend\Mvc\Controller\AbstractRestfulController)检查传入请求的 HTTP方法,并通过考虑 HTTP 方法匹配操作(方法)。

例如,使用 GET HTTP 方法的请求要么匹配getList()方法,要么匹配get()方法(如果在请求中找到id参数)。

AbstractConsoleController

AbstractConsoleController(Zend\Mvc\Controller\AbstractConsoleController)类似于 AbstractActionController,除了它只在控制台环境而不是浏览器中运行。

Zend Framework - 路由

路由将请求 URI映射到特定控制器的某个方法。在本章中,我们将了解如何在 Zend Framework 中实现路由。

一般来说,任何 URI 都有三个部分:

  • 主机名部分,
  • 路径部分,以及
  • 查询部分。

例如,在 URI/URL - http://www.example.com/index?q=data 中,www.example.com是主机名部分,index是路径部分,q=data是查询部分。通常,路由会根据一组约束检查路径部分。如果任何约束匹配,则它会返回一组值。其中一个主要值是控制器。

路由还会在某些情况下检查主机部分、查询部分、请求 HTTP 方法、请求 HTTP 标头等。

路由和路由栈

路由是路由中的主要对象。Zend Framework 为路由对象提供了一个特殊的接口RouteInterface。所有路由对象都需要实现 RouteInterface。RouteInterface 的完整列表如下:

namespace Zend\Mvc\Router;  
use Zend\Stdlib\RequestInterface as Request;  
interface RouteInterface { 
   public static function factory(array $options = []); 
   public function match(Request $request); 
   public function assemble(array $params = [], array $options = []); 
}

主要方法是match。此 match 方法根据其中定义的约束检查给定的请求。如果找到任何匹配项,则它会返回RouteMatch对象。此 RouteMatch 对象提供匹配请求的详细信息作为参数。这些参数可以使用getParams方法从RouteObject中提取。

RouteObject 的完整列表如下:

namespace Zend\Mvc\Router;  
class RouteMatch { 
   public function __construct(array $params); 
   public function setMatchedRouteName($name); 
   public function getMatchedRouteName(); 
   public function setParam($name, $value); 
   public function getParams(); 
   public function getParam($name, $default = null); 
} 

通常,一个典型的 MVC 应用程序有很多路由。每个路由将按后进先出 (LIFO) 顺序处理,并且将匹配并返回单个路由。如果没有任何路由匹配/返回,则应用程序将返回“页面未找到”错误。Zend Framework 提供了一个处理路由的接口RouteStackInterface。此 RouteStackInterface 具有添加/删除路由的选项。

RouteStackInterface 的完整列表如下:

namespace Zend\Mvc\Router;  
interface RouteStackInterface extends RouteInterface { 
   public function addRoute($name, $route, $priority = null); 
   public function addRoutes(array $routes); 
   public function removeRoute($name); 
   public function setRoutes(array $routes); 
}

Zend 框架提供了RouteStack接口的两个实现,它们分别是:

  • SimpleRouteStack
  • TreeRouteStack

路由类型

Zend 框架为“Zend\Mvc\Router\Http”命名空间下的所有情况提供了许多现成的路由对象。为给定情况选择和使用正确的路由对象就足够了。

可用的路由如下:

  • Hostname - 用于匹配 URI 的主机部分。

  • Literal - 用于匹配精确的 URI。

  • Method - 用于匹配传入请求的 HTTP 方法。

  • Part - 使用自定义逻辑匹配 URI 路径部分。

  • Regex - 使用正则表达式模式匹配 URI 路径部分。

  • Schema - 用于匹配 URI 方案,例如 http、https 等。

  • Segment - 用于通过将其拆分为多个部分来匹配 URI 路径。

让我们看看如何编写最常用的文字和分段路由。路由通常在每个模块的配置文件 - module.config.php 中指定。

文字路由

通常,路由按后进先出 (LIFO) 顺序查询。文字路由用于对 URI 路径进行精确匹配。

其定义如下:

$route = Literal::factory(array( 
   'route' => '/path', 
   'defaults' => array('controller' => 'Application\Controller\IndexController', 
      'action' => 'index',), 
));

以上路由匹配请求 url 中的/path,并返回index作为操作IndexController作为控制器。

分段路由

当您的 url 应该包含可变参数时,使用分段路由。

其描述如下:

$route = Segment::factory(array( 
   'route' => '/:controller[/:action]', 
   'constraints' => array( 
      'controller' => '[a-zA-Z][a-zA-Z0-9_-]+', 
      'action' => '[a-zA-Z][a-zA-Z0-9_-]+', 
   ), 
   'defaults' => array( 
      'controller' => 'Application\Controller\IndexController', 
      'action' => 'index',), 
));

这里,分段由冒号表示,后跟字母数字字符。如果您将某个分段设置为可选,则将其括在方括号中。每个分段可能与其关联的约束。每个约束都是一个正则表达式。

在 Tutorial 模块中配置路由

让我们在我们的 Tutorial 模块中添加一个分段路由。更新 Tutorial 模块配置文件 - module.config.php(位于myapp/module/Tutorial/config)。

<?php  
namespace Tutorial;  
use Zend\ServiceManager\Factory\InvokableFactory; 
use Zend\Router\Http\Segment;  
return [ 
   'controllers' => [ 
      'factories' => [ 
         Controller\TutorialController::class => InvokableFactory::class, 
      ], 
   ], 
   'router' => [ 
      'routes' => [ 
         'tutorial' => [ 
            'type'    => Segment::class, 
               'options' => [ 
                  'route' => '/tutorial[/:action[/:id]]', 
                  'constraints' => [ 
                     'action' => '[a-zA-Z][a-zA-Z0-9_-]*', 
                     'id'     => '[0-9]+', 
                  ], 
                  'defaults' => [
                     'controller' => Controller\TutorialController::class, 
                     'action'     => 'index', 
                  ], 
               ], 
            ], 
      ], 
   ], 
   'view_manager' => [ 
      'template_path_stack' => ['tutorial' => __DIR__ . '/../view',], 
   ], 
];

我们已成功为Tutorial模块添加了路由。我们距离完成 Tutorial 模块只有一步之遥。我们需要为我们的模块添加视图,我们将在后续章节中学习。

Zend Framework - 视图层

视图层是 MVC 应用程序的表示层。它将应用程序逻辑与表示逻辑分离。在典型的 PHP Web 应用程序中,所有业务逻辑和设计都混合在一起。混合可以在小型项目中实现更快的开发。但是,它在大规模项目中惨败,在大规模项目中涉及许多高级架构。要更改 Web 应用程序的设计,开发人员也需要处理业务逻辑。这可能很灾难性,导致业务逻辑中断。

Zend Framework 提供了一个经过深思熟虑、简洁、灵活且可扩展的视图层。视图层作为单独的模块Zend/View提供,并与Zend/Mvc模块完美集成。Zend 视图层被分为多个组件,彼此很好地交互。

其各种组件如下:

  • 变量容器 - 保存视图层的数据。

  • 视图模型 - 保存变量容器和设计模板。

  • 渲染器 - 处理来自视图模型的数据和模板,并输出设计表示,可能是最终的 html 输出。

  • 解析器 - 以渲染器可以使用的形式解析视图模型中可用的模板。

  • 视图 (Zend\View\View) − 将请求映射到渲染器,然后将渲染器映射到响应。

  • 渲染策略 − 视图用来将请求映射到渲染器的。

  • 响应策略 − 视图用来将渲染器映射到响应的。

视图层,View 处理 ViewModel,使用 Resolver 解析模板,使用 Rendering Strategy 渲染它,最后使用 Response Renderer 输出它。

视图层配置

像控制器一样,视图层可以在模块的配置文件中配置,称为 – module.config.php。主要配置是指定模板将放置的位置。这可以通过在“module.config.php”中添加以下配置来实现。

'view_manager' => [ 
   'template_path_stack' => ['tutorial' => __DIR__ . '/../view',], 
] 

默认情况下,视图层对其所有组件都有默认行为。例如,ViewModel 根据“小写模块名/小写控制器名/小写动作名”规则在模板根目录中解析控制器动作的模板名称。但是,这可以通过 ViewModelsetTemplate() 方法覆盖。

控制器和视图层

默认情况下,控制器不需要向视图层发送任何数据。将模板写入正确的位置就足够了。

例如,在我们的示例中,TutorialController,模板需要放置在 myapp/module/Tutorial/view/tutorial/tutorial/index.phtmlindex.phtml 指的是基于 PHP 的模板,它将由 PHPRenderer 渲染。还有其他渲染器,例如 JsonRenderer 用于 json 输出和 FeedRenderer 用于 rssatom 输出。

完整的列表如下所示:

<?php  
namespace Tutorial\Controller;  
use Zend\Mvc\Controller\AbstractActionController; 
use Zend\View\Model\ViewModel;  
class TutorialController extends AbstractActionController { 
   public function indexAction() { 
   } 
}

Zend 应用程序模板

<div class = "row content"> 
   <h3>This is my first Zend application</h3> 
</div>

最后,我们已成功完成了 Tutorial 模块,我们可以使用 url 访问它 – https://127.0.0.1:8080/tutorial

Application Template

将数据传递到视图层

将数据发送到视图层最简单的方法是使用 ViewModel 参数。修改后的 indexAction 方法如下所示:

public function indexAction() { 
   $view = new ViewModel([ 
      'message' => 'Hello, Tutorial' 
   ]);  
   return $view; 
} 

现在,修改 index.phtml 文件如下:

<div class = "row content"> 
   <h3>This is my first Zend application</h3> 
   <h4><?php echo $this->message?></h4> 
</div>

视图助手

视图助手用于编写小的、原子化的函数,以便在模板中使用。Zend 框架提供了一个接口,Zend\View\Helper\HelperInterface 用于编写标准的视图助手。

HelperInterface 只有两个方法,

  • setView() − 此方法接受 Zend\View\Renderer\RendererInterface 实例/实现。

  • getView() − 用于检索该实例。

HelperInterface 的完整代码列表如下:

namespace Zend\View\Helper;  
use Zend\View\Renderer\RendererInterface as Renderer;  
interface HelperInterface { 
   /** 
      * Set the View object 
      * 
      * @param  Renderer $view 
      * @return HelperInterface 
   */ 
   public function setView(Renderer $view);  
   /** 
      * Get the View object 
      * 
      * @return Renderer 
   */ 
   public function getView(); 
}

要在视图脚本中使用助手,请使用 $this->helperName() 访问它。

内置助手

Zend Framework 为各种目的提供了许多内置的助手函数。zend-mvc 中提供的一些视图助手如下:

URL

URL 助手用于生成与应用程序中定义的路由匹配的 URL。

URL 助手的定义为:

$this->url($name, $params, $options, $reuseMatchedParameters)

例如,在 tutorial 模块中,路由名为 tutorial,它有两个参数 actionid。我们可以使用 URL 助手生成两个不同的 URL,如下所示:

<a href = "<? = $this->url('tutorial'); ?>">Tutorial Index</a>  
<a href = "<? = $this->url('tutorial', ['action' => 'show', 'id' =>10]); ?>"> 
   Details of Tutorial #10 
</a>

结果将如下所示:

<a href = "/tutorial">Tutorial Index</a>  
<a href = "/tutorial/show/10"> Details of Tutorial #10</a> 

占位符

占位符助手用于在视图脚本和视图实例之间保留内容。它提供了最初设置数据然后在后续阶段使用它的选项。

例如,我们可以设置“公司名称”,然后在所有其他地方使用它。

<?php $this->placeholder('companyname')->set("TutorialsPoint") ?>  
<?= $this->placeholder('companyname'); ?>

占位符提供了一些高级选项,可以从 PHP 数组和对象生成复杂内容。它还可以选择捕获模板本身的某些部分。

例如,以下代码捕获中间的模板结果并将其存储在 productlist 占位符中。

类 – 产品

class Product { 
   public $name; 
   public $description; 
} 

控制器

$p1 = new Product(); 
$p1->name = 'Car';  
$p1->description = 'Car';  
$p2 = new Product(); 
$p2->name = 'Cycle'; 
$p2->description = 'Cycle';  
$view = new ViewModel(['products' => $products]); 

模板

<!-- start capture --> 
<?php $this->placeholder('productlist')->captureStart(); 
   foreach ($this->products as $product): ?> 
<div> 
   <h2><?= $product->name ?></h2> 
   <p><?= $product->description ?></p> 
</div> 
<?php endforeach; ?> 
<?php $this->placeholder('productlist')->captureEnd() ?> 
<!-- end capture -->  
<?= $this->placeholder('productlist') ?> 

结果

<div class = "foo"> 
   <h2>Car</h2> 
   <p>Car</p> 
</div>
<div class = "foo"> 
   <h2>Cycle</h2> 
   <p>Cycle</p> 
</div> 

文档类型

Doctype 助手用于生成各种 html 文档类型。它是 Placeholder 助手 的具体实现。文档类型可以在引导文件和配置文件中设置。

基本用法如下所示:

应用程序引导文件

use Zend\View\Helper\Doctype;  
$doctypeHelper = new Doctype(); 
$doctypeHelper->doctype('XHTML5'); 

模块配置

// module/Application/config/module.config.php: 
return [ 
   /* ... */ 
   'view_manager' => [ 
      'doctype' => 'html5', 
      /* ... */ 
   ], 
]; 

模板

<?php echo $this->doctype() ?> 

HeadTitle

HeadTitle 助手用于生成 html 标题元素。它是 Placeholder 助手 的具体实现。Zend 提供了一个选项可以在模块配置文件中设置标题,并且可以在任何级别设置,例如站点、模块、控制器、动作等。HeadTitle 的部分代码如下:

模块

headTitleHelper->append($action); 
$headTitleHelper->append($controller); 
$headTitleHelper->append($module); 
$headTitleHelper->append($siteName);

模板

<?= $this->headTitle() ?>

结果

action - controller - module - Zend Framework

HeadMeta

HeadMeta 助手用于生成 html 元标记。它是 Placeholder 助手 的具体实现。

模板

<?php 
   $this->headMeta()->appendName('keywords', 'turorialspoint, zend framework, php');  
   echo $this->headMeta() 
?>

结果

<meta name = "keywords" content = "tutorialspoint, zend framework, php" />

HeadLink

HeadLink 助手用于生成包含外部资源的 html 链接。它是 Placeholder 助手 的具体实现。

模板

<?php 
   // setting links in a view script: 
   $this->headLink(['rel' => 'icon', 'href' => '/img/favicon.ico'], 'PREPEND') 
      ->appendStylesheet('/styles/site.css') 
      ->prependStylesheet('/styles/mystyle.css', 'screen', true, ['id' => 'mystyle']);  
   
   // rendering the links from the layout: 
   echo $this->headLink(); 
?>

结果

<link href = "/styles/mystyle.css" media = "screen" rel = "stylesheet" 
   type = "text/css" id = "mystyle"> 
<link href = "/img/favicon.ico" rel = "icon"> 
<link href = "/styles/site.css" media = "screen" rel = "stylesheet" type = "text/css">

HeadStyle

HeadStyle 助手用于生成内联 CSS 样式。它是 Placeholder 助手 的具体实现。

模板

<?php $this->headStyle()->appendStyle($styles); ?>  
<?php echo $this->headStyle() ?>

HeadScript

HeadScript 用于生成内联脚本或包含外部脚本。它是 Placeholder 助手 的具体实现。

模板

<? $this->headScript()->appendFile(‘/js/sample.js’);?>  
<?php echo $this->headScript() ?>

InlineScript

InlineScript 用于在 html 模板的头部和主体部分生成脚本。它派生自 HeadScript。

HTMLList

HTMLList 用于生成有序和无序列表。HTMLList 的定义如下:

定义

htmlList($items, $ordered, $attribs, $escape) 

模板

$items = [ 
   '2015', 
   ['March', 'November'], 
   '2016', 
];  
echo $this->htmlList($items);

结果

<ul> 
   <li>2015 
      <ul> 
         <li>March</li> 
         <li>November</li> 
      </ul> 
   </li> 
   <li>2016</li> 
</ul>

循环

循环用于在循环环境中生成备选方案。它有 assign、next 和 prev 函数。

控制器

$view = new ViewModel(['message' => 'Hello, Tutorial', 'data' => array('One', 'Two')]);

模板

<?php $this->cycle()->assign(['#F0F0F0', '#FFF'], 'colors'); ?>

<table>
   <?php foreach ($this->data as $datum): ?>
   <tr style = "background-color: <?= $this->cycle()->setName('colors')>next() ?>">
      <td><?= $this->escapeHtml($datum) ?></td>
   </tr>
   <?php endforeach ?>
</table>

结果

<table> 
   <tr style = "background-color: #F0F0F0"> 
      <td>One</td> 
   </tr> 
   <tr style = "background-color: #FFF"> 
      <td>Two</td> 
   </tr> 
</table>

其他一些重要的内置助手如下:

  • BasePath − BasePath 用于生成应用程序根目录的 public 文件夹的路径。

  • Partial − Partial 用于在其自己的变量作用域中渲染特定模板。

  • PartialLoop − PartialLoop 类似于 Partial,但在循环环境中使用。

  • Identity − Identity 用于从身份验证服务中检索已登录用户的身份。

  • JSON − JSON 在 restful 环境中使用,其中输出为 JSON 格式。它发出正确的 HTTP 标头并禁用布局概念。

Zend Framework 中还有许多其他助手,例如 i18n 助手、表单助手、分页助手、导航助手等。

创建视图助手

Zend Framework 提供了一个内置的 AbstractHelper 实现 HelperInterface 来编写视图助手。

编写新助手的步骤如下:

  • 步骤 1 − 扩展类 Zend\View\Helper\AbstractHelper。

  • 步骤 2 − 覆盖 __invoke() 函数。

  • 步骤 3 − 在 module.config.php 文件 中设置配置。

  • 步骤 4 − 在视图脚本中使用视图助手。

现在让我们创建一个 TestHelper

myapp/module/Tutorial/src/View 目录 中创建 Helper 文件夹。在 Helper 目录中编写 TestHelperTestHelper.php

完整的列表如下所示:

<?php  
namespace Tutorial\View\Helper; 
use Zend\View\Helper\AbstractHelper; 
class TestHelper extends AbstractHelper { 
   public function __invoke() { 
      $output = "I am from test helper"; 
      return htmlspecialchars($output, ENT_QUOTES, 'UTF-8'); 
   } 
}

module.config.php 中设置配置。

'view_helpers' => [ 
   'aliases' => [ 
      'testHelper' => View\Helper\TestHelper::class, 
   ], 
   'factories' => [ 
      View\Helper\TestHelper::class => InvokableFactory::class, 
   ],
], 

about 视图脚本中使用新创建的 TestHelper

<?= $this->testHelper() ?> 

Zend Framework - 布局

布局表示多个视图的公共部分,例如页面标题和页脚。默认情况下,布局应存储在 view/layout 文件夹中。

布局配置在模块的 module.config.php 中的 view_manager 部分下定义。

骨架应用程序的默认配置如下:

'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( 
   __DIR__ . '/../view', 
),

这里,template_map 用于指定布局。如果找不到布局,则它将返回错误。让我们看看骨架应用程序的主要布局。

Layout.phtml

<?= $this->doctype() ?>  
<html lang = "en"> 
   <head> 
      <meta charset = "utf-8"> 
      <?= $this->headTitle('ZF Skeleton Application')->setSeparator(' - ')>
         setAutoEscape(false) ?>
      <?= $this->headMeta() 
         ->appendName('viewport', 'width = device-width, initial-scale = 1.0') 
         ->appendHttpEquiv('X-UA-Compatible', 'IE = edge') 
      ?>  
      
      <!-- Le styles --> 
      <?= $this->headLink(['rel' => 'shortcut icon', 'type' => 
         'image/vnd.microsoft.icon', 
         'href' => $this->basePath() . '/img/favicon.ico']) 
         ->prependStylesheet($this->basePath('css/style.css')) 
         ->prependStylesheet($this->basePath('css/bootstraptheme.min.css')) 
         ->prependStylesheet($this->basePath('css/bootstrap.min.css')) 
      ?>  
      
      <!-- Scripts --> 
      <?= $this->headScript() 
         ->prependFile($this->basePath('js/bootstrap.min.js')) 
         ->prependFile($this->basePath('js/jquery-3.1.0.min.js')) 
      ?> 
   </head> 
   
   <body> 
      <nav class = "navbar navbar-inverse navbar-fixed-top" role = "navigation"> 
         <div class = "container"> 
            <div class = "navbar-header"> 
               <button type = "button" class = "navbar-toggle" data-
                  toggle = "collapse" data-target = ".navbar-collapse"> 
                  <span class = "icon-bar"></span> 
                  <span class = "icon-bar"></span> 
                  <span class = "icon-bar"></span> 
               </button> 
            
               <a class = "navbar-brand" href = "<?= $this->url('home') ?>"> 
                  <img src = "<?= $this->basePath('img/zf-logo-mark.svg') ?>
                     " height = "28" alt = "Zend Framework <?= \Application\Module::
                     VERSION ?>"/> Skeleton Application 
               </a> 
            </div>
         
            <div class = "collapse navbar-collapse"> 
               <ul class = "nav navbar-nav"> 
                  <li class = "active"><a href = "<?= 
                     $this->url('home') ?>">Home</a></li> 
               </ul> 
            </div> 
         </div> 
      </nav> 
   
      <div class = "container"> 
         <?= $this->content ?> 
         <hr> 
         <footer> 
            <p>© 2005 - <?= date('Y') ?> by Zend Technologies Ltd. 
               All rights reserved.</p> 
         </footer> 
      </div> 
      <?= $this->inlineScript() ?> 
   </body> 
</html>

当您分析布局时,它主要使用我们在上一章中讨论的视图助手。当我们仔细观察时,布局使用了一个特殊的变量 $this->content。此变量很重要,因为它将被实际请求页面的视图脚本(模板)替换。

创建新的布局

让我们为我们的 Tutorial 模块创建一个新的布局。

首先,让我们在“public/css”目录下创建一个 tutorial.css 文件

 body { 
   background-color: lightblue; 
} 
h1 { 
   color: white; 
   text-align: center; 
}

在 /myapp/module/Tutorial/view/layout/ 中创建一个新的布局文件 newlayout.phtml,并从现有布局中复制内容。然后,使用 HeadLink 助手类在布局头部部分添加 tutorial.css 样式表。

<?php echo $this->headLink()->appendStylesheet('/css/tutorial.css');?> 

使用 URL 助手在导航部分添加一个新的 about 链接。

<li><a href = "<?= $this->url('tutorial', ['action' => 'about']) ?>">About</a></li>

此布局页面是 tutorial 模块应用程序的通用页面。更新 tutorial 模块配置文件的 view_manager 部分。

'view_manager' => array( 
   'template_map' => array( 
      'layout/layout' => __DIR__ . '/../view/layout/newlayout.phtml'), 
   'template_path_stack' => array('tutorial' => __DIR__ . '/../view',), 
)

TutorialController 中添加 aboutAction 函数。

 public function aboutAction() { 
} 

在 myapp/module/Tutorial/view/tutorial/tutorial/ 中添加 about.phtml,内容如下。

<h2>About page</h2>

现在,您可以准备运行应用程序了 – https://127.0.0.1:8080/tutorial/about.

About Page

Zend Framework - 模型和数据库

在本章中,我们将讨论 Zend Framework 的各种模型和数据库。

Zend Framework 中的模型

模型定义了应用程序的逻辑数据表示。例如,在购物车应用程序中 – 产品、客户、购物车和订单是模型。它们定义了其所持实体的属性。模型的一些概念如下:

  • 控制器与模型通信并要求它们检索它们需要的信息。然后,控制器将检索到的信息传递给视图。最后,视图将模型呈现为用户可消费的表示数据。

  • 模型直接与视图交互的情况非常少见,但有时可能会发生。

  • 模型可以相互通信,并且不是自包含的。它们彼此之间存在关系。这些关系使控制器更容易、更快速地获取信息,因为它不必与不同的模型交互;模型本身可以做到这一点。

让我们看一下一个简单的模型 – MyModel

<?php  
namespace Tutorial\Model;  
class Book { 
   public $id; 
   public $author; 
   public $title; 
}

Zend Framework 中的数据库

Zend 框架提供了一个简单且功能丰富的类,Zend\Db\TableGateway\TableGateway,用于查找、插入、更新和删除数据库表中的数据。

让我们看看如何通过以下步骤通过 PHP 的 PDO 驱动程序在 Zend 框架中连接 MySqlservice

步骤 1:在 MySQL 中创建数据库

在本地 MySQL 服务器中创建数据库 tutorials。我们可以为此目的使用 phpmyadmin 或任何其他 MySQL GUI 工具。让我们在命令提示符中使用 MySQL 客户端。连接到 mysql 服务器并运行以下命令以创建 tutorial 数据库。

create database tutorials

步骤 2:在 tutorials 数据库中创建表

现在让我们使用以下 SQL 命令在 tutorials 数据库中创建一个名为 book 的数据库。

use tutorials;  
CREATE TABLE book ( 
   id int(11) NOT NULL auto_increment, 
   author varchar(100) NOT NULL, 
   title varchar(100) NOT NULL, 
   PRIMARY KEY (id) 
);

步骤 3:在 book 表中填充数据

使用示例数据填充 book 表。使用以下 SQL 命令。

INSERT INTO book (author, title) VALUES ('Dennis Ritchie', 'C Programming'); 
INSERT INTO book (author, title) VALUES ('James gosling', 'Java Programming'); 
INSERT INTO book (author, title) VALUES ('Rasmus Lerdorf', 'Programming PHP');

步骤 4:更新数据库连接

更新全局配置文件,即 – myapp/config/autoload/global.php,并提供必要的数据库驱动信息。

<?php 
return array( 
   'db' => array( 
      'driver' => 'Pdo', 
      'dsn' => 'mysql:dbname = tutorials;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', 
      ), 
   ), 
);

步骤 5:更新数据库凭据

在本地配置文件中更新数据库凭据,该文件为 – myapp/config/autoload/local.php。通过这种方式,我们可以分离本地和生产环境的数据库连接凭据。

<?php 
return array( 
   'db' => array( 
      'username' => '<user_name>', 
      'password' => '<password>', 
   ), 
); 

步骤 6:创建图书模型

让我们在模块 src 目录中创建一个名为 Book 的模型。通常,模型会被分组到 Model 文件夹下 – /myapp/module/Tutorial/src/Model/Book.php。

<?php  
namespace Tutorial\Model;  
class Book { 
   public $id; 
   public $author; 
   public $title; 
}

步骤 7:在图书模型中实现 exchangeArray

TableGateway 通过 exchangeArray 函数与模型进行交互。exchangeArray 函数的标准参数是存储为 PHP 数组的数据库结果集。使用 exchangeArray 函数,可以轻松地将模型的属性与相应的数据库表同步。

更新模型 Book,如下所示:

<?php  
namespace Tutorial\Model;  
class Book { 
   public $id; 
   public $author; 
   public $title;  
   public function exchangeArray($data) { 
      $this->id = (!empty($data['id'])) ? $data['id'] : null; 
      $this->Author = (!empty($data['author'])) ? $data['author'] : null; 
      $this->Title = (!empty($data['title'])) ? $data['title'] : null; 
   } 
}

步骤 8:使用 TableGateway 获取图书

创建一个名为 BookTable 的类,用于从数据库中获取图书信息。在 Model 文件夹中创建 BookTable 类。

<?php  
namespace Tutorial\Model;  
use Zend\Db\TableGateway\TableGatewayInterface;  
class BookTable {
   protected $tableGateway; 
   public function __construct(TableGatewayInterface $tableGateway) { 
      $this->tableGateway = $tableGateway; 
   }  
   public function fetchAll() { 
      $resultSet = $this->tableGateway->select(); 
      return $resultSet; 
   } 
}

我们使用了 TableGateway 类的 select() 方法从数据库中获取图书信息。但是,我们在代码中没有使用任何对 book 表的引用。TableGateway 本质上是通用的,它可以通过某些配置从任何表中获取数据。通常,这些配置是在 module.config.php 文件中完成的,我们将在后续步骤中讨论。

步骤 9:配置 BookTable 类

使用 getServiceConfig() 方法更新教程模块的 Module.php

<?php
namespace Tutorial;
use Zend\Db\Adapter\AdapterInterface;
use Zend\Db\ResultSet\ResultSet;
use Zend\Db\TableGateway\TableGateway;
use Zend\ModuleManager\Feature\ConfigProviderInterface;

class Module implements ConfigProviderInterface {
   
   public function getConfig() {
      return include __DIR__ . '/../config/module.config.php';
   }
   public function getServiceConfig() {
      return [
         'factories' => [
            Model\BookTable::class => function ($container) {
               $tableGateway = $container->get(Model\BookTableGateway::class);
               $table = new Model\BookTable($tableGateway);
               return $table;
            },
            Model\BookTableGateway::class => function ($container) {
               $dbAdapter = $container->get(AdapterInterface::class);
               $resultSetPrototype = new ResultSet();
               $resultSetPrototype->setArrayObjectPrototype(new Model\Book());
               return new TableGateway('book', $dbAdapter, null, $resultSetPrototype);
            },
         ],
      ];
   }
}

在这里,我们使用服务管理器注册了 BookTable 类。BookTable 类用于获取图书信息,通过注册它,我们可以在任何需要的地方访问它。由于注册的服务是共享的,因此它们可以提高性能,减少内存消耗等。

另一个项目 Model\BookTableGateway::class 是专门用于 Book 模型的 TableGateway 对象,并且是 BookTable 的依赖项。

步骤 10:更新 TutorialController 配置

我们需要在教程控制器中使用 BookTable 服务来获取图书信息。要获取 BookTable 服务,请将其作为构造函数依赖项注册到 TutorialController 中。

此构造函数依赖项有助于在控制器本身处于初始化阶段时获取 BookTable 服务。更新教程模块配置的控制器部分,即 module.config.php,如下所示。

'controllers' => [ 
   'factories' => [ 
      Controller\TutorialController::class => function($container) { 
         return new Controller\TutorialController( 
            $container->get(Model\BookTable::class) 
         ); 
      }, 
   ], 
],

步骤 11:更新 Tutorial Controller

这可以通过遵循以下三个步骤来完成。

  • 添加以 BookTable 作为参数的构造函数。
private $table;
public function __construct(BookTable $table) { 
   $this->table = $table; 
}
  • 使用 BookTablefetchAll() 方法获取图书信息,并将其注册到视图中。

public function indexAction() { 
   $view = new ViewModel([ 
      'data' => $this->table->fetchAll(), 
   ]);  
   return $view; 
}
  • 在视图脚本中显示图书信息。

<table class = "table"> 
   <tr> 
      <th>Author</th> 
      <th>Title</th> 
      <th> </th> 
   </tr> 
   <?php foreach ($data as $sampledata) : ?> 
   <tr> 
      <td><?php echo $this->escapeHtml($data->author);?></td>  
      <td><?php echo $this->escapeHtml($data->title);?></td> 
   </tr> 
   <?php endforeach ?> 
</table>

步骤 12:运行应用程序

通过运行以下命令检查应用程序:https://127.0.0.1:8080/tutorial

Run Application

Zend Framework - 不同的数据库

如上一章所述,Zend 框架提供了一种使用 数据库驱动程序 概念通用访问数据库的方法。与数据库的交互完全取决于驱动程序信息,因此,连接到不同的数据库只需更改驱动程序信息即可。

现在让我们将 book 示例更改为连接到 postgresql 数据库,并遵循以下步骤。

步骤 1 - 使用以下命令在本地 postgresql 数据库中创建一个名为 tutorials 的数据库:

CREATE DATABASE tutorials

步骤 2 - 添加 book 表。切换到新数据库并执行表创建脚本。

\c tutorials 
CREATE TABLE book ( 
   id SERIAL NOT NULL, 
   author varchar(100) NOT NULL, 
   title varchar(100) NOT NULL, 
   PRIMARY KEY (id) 
); 

步骤 3 - 使用以下脚本添加示例图书信息:

INSERT INTO book (author, title) VALUES ('Dennis Ritchie', 'C Programming'); 
INSERT INTO book (author, title) VALUES ('James gosling', 'Java Programming'); 
INSERT INTO book (author, title) VALUES ('Rasmus Lerdorf', 'Programming PHP');

步骤 4 - 更改 global.config 文件中的驱动程序信息。

<?php 
return array ( 
   'db' => array ( 
      'driver' => 'Pdo', 
      'dsn' => 'pgsql:dbname = tutorials;host = localhost', 
      'driver_options' => array ( 
      ), 
   ), 
); 

步骤 5 - 更改 local.config 文件中的数据库凭据。

return array ( 
   'db' => array( 
      'username' => '<username>', 
      'password' => '<password>', 
   ), 
);

步骤 6 - 最后,运行应用程序 https://127.0.0.1:8080/tutorial。结果与 MySQL 应用程序相同。

Zend Framework - 表单和验证

Zend Framework 提供了一个单独的组件 zend-form 来加速表单创建和验证过程。它连接模型和视图层。它提供了一组表单元素,可以从预定义的模型中创建完整的 html 表单,一个 InputFilter 类来根据表单验证模型,以及将数据从表单绑定到模型并反之亦然的功能。

安装表单组件

可以使用以下指定的 Composer 命令安装 Zend 表单组件:

composer require zendframework/zend-form 

Zend 表单框架有三个子组件来管理表单。它们在下面详细解释:

  • 元素 - 用于定义映射到模型中属性的单个 html 输入控件。

  • 字段集 - 用于以嵌套方式对元素和其他 字段集 进行分组。

  • 表单 - 用于创建 html 表单,并包含元素和字段集。

Zend 表单通常在 module//src/Form 目录下创建。

示例

现在让我们创建一个简单的表单来将 book 添加到数据库中。为此,我们应该遵循以下步骤:

步骤 1:创建 BookForm

在 *myapp/module/Tutorial/src/Form” 目录下创建“BookForm.php”。在文件中添加以下更改:

<?php  
namespace Tutorial\Form;  
use Zend\Form\Form;  

class BookForm extends Form {
   
   public function __construct($name = null) { 
      parent::__construct('book');  
      $this->add(array( 
         'name' => 'id', 
         'type' => 'Hidden', 
      ));  
      $this->add(array( 
         'name' => 'author', 
         'type' => 'Text', 
         'options' => array( 
            'label' => 'Author', 
         ), 
      ));  
      $this->add(array( 
         'name' => 'title', 
         'type' => 'Text', 
         'options' => array( 
            'label' => 'Title', 
         ), 
      ));  
      $this->add(array( 
         'name' => 'submit', 
         'type' => 'Submit', 
         'attributes' => array( 
            'value' => 'Go', 
            'id' => 'submitbutton', 
         ), 
      )); 
   } 
}

Form 类提供了一个 add 方法来映射模型及其对应的表单详细信息。我们通过扩展 Form 类创建了 BookForm,并添加了 Book 模型的表单详细信息。

步骤 2:更新图书模型 Book.php

更新模型 ‘Book’,添加过滤器和验证,如下所示:

<?php 
namespace Tutorial\Model;  
use Zend\InputFilter\InputFilterInterface; 
use Zend\InputFilter\InputFilterAwareInterface; 
use Zend\InputFilter\InputFilter;  

class Book implements InputFilterAwareInterface { 
   public $id; 
   public $author; 
   public $title;  
   protected $inputFilter;  
   public function setInputFilter(InputFilterInterface $inputFilter) { 
      throw new \Exception("Not used"); 
   }  
   public function getInputFilter() { 
      if (!$this->inputFilter) { 
         $inputFilter = new InputFilter(); 
         $inputFilter->add(array( 
            'name' => 'id', 
            'required' => true, 
            'filters' => array( 
               array('name' => 'Int'), 
            ),
         )); 
         $inputFilter->add(array( 
            'name' => 'author', 
            'required' => true, 
            'filters' => array( 
               array('name' => 'StripTags'), 
               array('name' => 'StringTrim'), 
            ), 
            'validators' => array( 
               array( 
                  'name' => 'StringLength', 
                  'options' => array( 
                     'encoding' => 'UTF-8', 
                     'min' => 1, 
                     'max' => 100, 
                  ), 
               ), 
            ), 
         )); 
         $inputFilter->add(array( 
            'name' => 'title', 
            'required' => true, 
            'filters' => array( 
               array('name' => 'StripTags'), 
               array('name' => 'StringTrim'), 
            ), 
            'validators' => array( 
               array( 
                  'name' => 'StringLength', 
                  'options' => array( 
                     'encoding' => 'UTF-8', 
                     'min' => 1, 
                     'max' => 100, 
                  ), 
               ),
            ),  
         )); 
         $this->inputFilter = $inputFilter; 
      } 
      return $this->inputFilter; 
   }  
   public function exchangeArray($data) { 
      $this->id = (!empty($data['id'])) ? $data['id'] : null; 
      $this->author = (!empty($data['author'])) ? $data['author'] : null; 
      $this->title = (!empty($data['title'])) ? $data['title'] : null; 
   } 
}

每个模型都应该实现 InputFilterAwareInterface。InputFilterAwareInterface 提供了两个方法,setInputFilter()getInputFilter()

getInputFilter 用于获取模型的验证详细信息。Zend 框架提供了一套丰富的过滤器和验证器来验证表单。在图书模型中使用的一些过滤器和验证器如下:

  • StripTags - 删除不需要的 HTML。

  • StringTrim - 删除不必要的空格。

  • StringLength 验证器 - 确保用户输入的字符数不超过指定的限制。

步骤 3:更新 BookTable 类

包含 saveBook 方法以将图书添加到数据库。

BookTable.php

<?php  
namespace Tutorial\Model;  
use Zend\Db\TableGateway\TableGatewayInterface;  

class BookTable {
   protected $tableGateway; 
   public function __construct(TableGatewayInterface $tableGateway) { 
      $this->tableGateway = $tableGateway; 
   }  
   public function fetchAll() { 
      $resultSet = $this->tableGateway->select(); 
      return $resultSet; 
   }  
   public function getBook($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 saveBook(Book $book) { 
      $data = array ( 
         'author' => $book->author, 
         'title'  => $book->title, 
      );  
      $id = (int) $book->id; 
      if ($id == 0) { 
         $this->tableGateway->insert($data); 
      } else {
         if ($this->getBook($id)) { 
            $this->tableGateway->update($data, array('id' => $id));  
         } else { 
            throw new \Exception('Book id does not exist'); 
         } 
      } 
   } 
}

步骤 4:更新 TutorialController 类

在教程控制器中添加一个新的操作 addAction – myapp/module/Tutorial/src/Controller/TutorialController.php。

public function addAction() { 
   $form = new BookForm(); 
   $form->get('submit')->setValue('Add');  
   $request = $this->getRequest(); 
   if ($request->isPost()) { 
      $book = new Book(); 
      $form->setInputFilter($book->getInputFilter()); 
      $form->setData($request->getPost());  
      if ($form->isValid()) { 
         $book->exchangeArray($form->getData()); 
         $this->bookTable->saveBook($book);  
         
         // Redirect to list of Tutorial 
         return $this->redirect()->toRoute('tutorial'); 
      } 
   }  
   return array('form' => $form); 
}

addAction 方法执行以下过程:

  • 获取请求对象。

  • 检查请求的 http 方法是否为 post 方法。

  • 如果请求的 http 方法不是 post,则只渲染模板 add.phtml

  • 如果请求的 http 方法不是 post,则设置 inputfilter,获取请求数据并将其设置为 inputfiler。

  • 使用 Form 类的 isValid() 方法检查表单是否有效。

  • 如果表单无效,则再次渲染模板 add.phtml

  • 如果表单有效,则将图书保存到数据库中并重定向到主页。

步骤 5:添加 add.phtml 模板

在 myapp/module/Tutorial/view/tutorial/tutorial/add.phtml 下创建一个模板 – add.phtml。

Add.phtml

<?php  
$title = 'Add new Book'; 
$this->headTitle($title);  
?>  
<h1><?php echo $this->escapeHtml($title); ?></h1>  
<?php  
if(!empty($form)) {  
   $form->setAttribute('action', $this->url('tutorial', array('action' => 'add'))); 
   $form->prepare();  
   echo $this->form()->openTag($form); 
   echo $this->formHidden($form->get('id')); 
   echo $this->formRow($form->get('author'))."<br>"; 
   echo $this->formRow($form->get('title'))."<br>"; 
   echo $this->formSubmit($form->get('submit')); 
   echo $this->form()->closeTag(); 
} 

在这里,我们使用 Form 实例 $form 渲染图书表单。

步骤 6:运行应用程序

现在,我们可以运行应用程序 – https://127.0.0.1:8080/tutorial/add

表单页面

Form Page

验证错误页面

Error Page

Zend Framework - 文件上传

文件上传是表单编程中的一个主要概念。Zend 框架提供了通过 zend-formzend-inputfilter 组件上传文件所需的所有项目。

FileInput 类

zend-inputfilter 组件提供 Zend\InputFilter\FileInput 类来处理 html 文件输入元素 – <input type = 'file' />。FileInput 与其他输入过滤器类似,但有一些例外。它们如下:

  • 由于 PHP 将上传的文件详细信息保存在 $_FILES 全局数组中,因此 FileInput 只通过 $_FILES 收集上传的文件信息。

  • 在 FileInput 类处理数据之前需要进行验证。这与其他输入过滤器的行为相反。

  • Zend\Validator\File\UploadFile 是要使用的默认验证器。UploadFile 验证文件输入详细信息。

要在表单中添加文件上传类型,我们需要使用输入类型 File。部分代码如下:

$form->add(array( 
   'name' => 'imagepath', 
   'type' => 'File', 
   'options' => array('label' => 'Picture',), 
)); 

文件上传中使用的另一个类是 Zend\Filter\File\RenameUpload。RenameUpload 用于将上传的文件移动到我们期望的位置。使用文件过滤器的部分类如下:

$file = new FileInput('imagepath'); 
$file->getValidatorChain()->attach(new UploadFile());
$file->getFilterChain()->attach( 
   new RenameUpload([ 
      'target'    => './public/tmpuploads/file', 
      'randomize' => true, 
      'use_upload_extension' => true 
   ]));
$inputFilter->add($file); 

这里,RenameUpload 的选项如下:

  • target - 上传文件的目标路径。

  • randomize - 添加随机字符串以防止上传文件重复。

  • use_upload_extension - 将上传文件的扩展名追加到目标。

文件上传 - 工作示例

让我们修改教程模块并包含一个图片上传功能。

修改数据库表

让我们通过执行以下 SQL 命令将 imagepath 列添加到 book 表中:

ALTER TABLE `book` ADD `imagepath` VARCHAR(255) NOT NULL AFTER 'imagepath';

更新 BookForm.php

在图书表单中添加文件输入元素以上传图片 – myapp/module/Tutorial/src/Model/BookForm.php。

在 BookForm 类的 __construct 方法中包含以下代码。

$this->add(array( 
   'name' => 'imagepath', 
   'type' => 'File', 
   'options' => array ('label' => 'Picture',), 
)); 

更新 Book.php

在 Book 类中进行以下更改 – myapp/module/Tutorial/src/Model/Book.php。

  • 为图片添加一个新的属性 imagepath

public $imagepath; 
  • 更新 getInputFilter 方法,如下所示:

    • 为文件输入元素添加 FileInput 过滤器。

    • 设置 UploadFile 验证以验证文件输入元素。

    • 配置 RenameUpload 将上传的文件移动到正确的位置。

部分代码清单如下:

$file = new FileInput('imagepath'); 
$file->getValidatorChain()->attach(new UploadFile()); 
$file->getFilterChain()->attach( 
   new RenameUpload([ 
      'target'    => './public/tmpuploads/file', 
      'randomize' => true, 'use_upload_extension' => true 
   ])); 
$inputFilter->add($file); 
  • 更新 exchangeArray 方法以包含 imagepath 属性。imagepath 可能来自表单或数据库。如果 imagepath 来自表单,则格式将是一个具有以下规范的数组:

array(1) { 
   ["imagepath"] => array(5) { 
      ["name"]     => string "myimage.png" 
      ["type"]     => string "image/png"           
      ["tmp_name"] => string 
         "public/tmpuploads/file_<random_string>.<image_ext>" 
      ["error"]    => int <error_number> 
      ["size"]     => int <size> 
   } 
}
  • 如果 imagepath 来自数据库,则它将是一个简单的字符串。解析 imagepath 的部分代码清单如下:

if(!empty($data['imagepath'])) { 
   if(is_array($data['imagepath'])) { 
      $this->imagepath = str_replace("./public", "", $data['imagepath']['tmp_name']); 
   } else { 
      $this->imagepath = $data['imagepath']; 
   } 
} else { 
   $data['imagepath'] = null; 
}

Book 模型的完整代码清单如下:

<?php  
namespace Tutorial\Model;  
use Zend\InputFilter\InputFilterInterface; 
use Zend\InputFilter\InputFilterAwareInterface;  
use Zend\Filter\File\RenameUpload; 
use Zend\Validator\File\UploadFile; 
use Zend\InputFilter\FileInput; 
use Zend\InputFilter\InputFilter;  

class Book implements InputFilterAwareInterface { 
   public $id; 
   public $author; 
   public $title; 
   public $imagepath;  
   protected $inputFilter;  
   public function setInputFilter(InputFilterInterface $inputFilter) { 
      throw new \Exception("Not used");
   }  
   public function getInputFilter() { 
      if (!$this->inputFilter) { 
         $inputFilter = new InputFilter(); 
         $inputFilter->add(array( 
            'name' => 'id', 
            'required' => true, 
            'filters' => array( 
               array('name' => 'Int'), 
            ), 
         )); 
         $inputFilter->add(array( 
            'name' => 'author', 
            'required' => true, 
            'filters' => array( 
               array('name' => 'StripTags'), 
               array('name' => 'StringTrim'), 
            ), 
            'validators' => array( 
               array( 
                  'name' => 'StringLength', 
                  'options' => array( 
                     'encoding' => 'UTF-8', 
                     'min' => 1, 
                     'max' => 100, 
                  ), 
               ), 
            ), 
         )); 
         $inputFilter->add(array( 
            'name' => 'title', 
            'required' => true, 
            'filters' => array( 
               array('name' => 'StripTags'), 
               array('name' => 'StringTrim'), 
            ),  
            'validators' => array( 
               array( 
                  'name' => 'StringLength', 
                  'options' => array( 
                     'encoding' => 'UTF-8', 
                     'min' => 1, 
                     'max' => 100, 
                  ), 
               ), 
            ), 
         ));  
         $file = new FileInput('imagepath'); 
         $file->getValidatorChain()->attach(new UploadFile()); 
         $file->getFilterChain()->attach( 
            new RenameUpload([ 
               'target'    => './public/tmpuploads/file', 
               'randomize' => true, 
               'use_upload_extension' => true 
            ])); 
            $inputFilter->add($file);  
            $this->inputFilter = $inputFilter; 
      } 
      return $this->inputFilter; 
   }  
   public function exchangeArray($data) { 
      $this->id = (!empty($data['id'])) ? $data['id'] : null; 
      $this->author = (!empty($data['author'])) ? $data['author'] : null; 
      $this->title = (!empty($data['title'])) ? $data['title'] : null; 
      
      if(!empty($data['imagepath'])) { 
         if(is_array($data['imagepath'])) { 
            $this->imagepath = str_replace("./public", "", 
               $data['imagepath']['tmp_name']); 
         } else { 
            $this->imagepath = $data['imagepath']; 
         } 
      } else { 
         $data['imagepath'] = null; 
      } 
   } 
}

更新 BookTable.php

我们已经更新了 BookFormBook 模型。现在,我们更新 BookTable 并修改 saveBook 方法。这足以将 imagepath 条目包含在数据数组 $data 中。

部分代码清单如下:

$data = array('author' => $book->author, 'title'  => $book->title, 
   'imagepath' => $book->imagepath 
); 

BookTable 类的完整代码清单如下:

<?php  
namespace Tutorial\Model;  
use Zend\Db\TableGateway\TableGatewayInterface;  

class BookTable {  
   protected $tableGateway; 
   public function __construct(TableGatewayInterface $tableGateway) { 
      $this->tableGateway = $tableGateway; 
   }  
   public function fetchAll() { 
      $resultSet = $this->tableGateway->select(); 
      return $resultSet; 
   }  
   public function getBook($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 saveBook(Book $book) { 
      $data = array ( 
         'author' => $book->author,
         'title'  => $book->title, 
         'imagepath' => $book->imagepath 
      );  
      $id = (int) $book->id; 
      if ($id == 0) { 
         $this->tableGateway->insert($data); 
      } else { 
         if ($this->getBook($id)) {  
            $this->tableGateway->update($data, array('id' => $id)); 
         } else { 
            throw new \Exception('Book id does not exist'); 
         } 
      } 
   } 
}

更新 TutorialController.php 中的 addAction 方法:文件上传信息将存储在全局数组 $_FILES 中,并且可以使用 Request 的 getFiles() 方法访问。因此,将提交的数据和文件上传信息合并,如下所示。

$post = array_merge_recursive( 
   $request->getPost()->toArray(), 
   $request->getFiles()->toArray() 
); 

addAction() 方法的完整代码如下所示:

public function addAction() { 
   $form = new BookForm(); 
   $form->get('submit')->setValue('Add');  
   $request = $this->getRequest(); 
   if ($request->isPost()) { 
      $book = new Book(); 
      $form->setInputFilter($book->getInputFilter()); 
      $post = array_merge_recursive( 
         $request->getPost()->toArray(), 
         $request->getFiles()->toArray() 
      );  
      $form->setData($post);   
      if ($form->isValid()) { 
         $book->exchangeArray($form->getData());  
         $this->bookTable->saveBook($book);  
         
         // Redirect to list of Tutorial 
         return $this->redirect()->toRoute('tutorial'); 
      } 
   }  
   return array('form' => $form); 
}

更新 add.phtml 的视图

最后,修改 “add.phtml” 并包含如下所示的 imagepath 文件输入元素:

echo $this->formRow($form->get('imagepath'))."<br>";

完整的列表如下所示:

<?php 
$title = 'Add new Book'; 
$this->headTitle($title); 
?> 
<h1><?php echo $this->escapeHtml($title); ?></h1> 
<?php  
if(!empty($form)) {  
   $form->setAttribute('action', $this->url('tutorial', array('action' => 'add'))); 
   $form->prepare();  
   echo $this->form()->openTag($form); 
   echo $this->formHidden($form->get('id')); 
   echo $this->formRow($form->get('author'))."<br>"; 
   echo $this->formRow($form->get('title'))."<br>"; 
   echo $this->formRow($form->get('imagepath'))."<br>"; 
   echo $this->formSubmit($form->get('submit')); 
   echo $this->form()->closeTag(); 
}

运行应用程序

最后,在 https://127.0.0.1:8080/tutorial/add 运行应用程序,并添加新的记录。

结果将如以下截图所示:

表单页面

New Book Example

首页

Index Page

Zend Framework - Ajax

AJAX 是 Web 编程中的一项现代技术。它提供了一种在网页中异步发送和接收数据而不刷新页面的选项。Zend 框架提供了一个通过 zend-viewzend-json 组件使用 json 模型的选项。让我们在本节中学习 Zend AJAX 编程。

安装 json 组件

可以使用以下 Composer 命令安装 Zend json 组件:

composer require zendframework/zend-json 

概念

Zend 框架提供了两种方法来轻松编写支持 AJAX 的 Web 应用程序。它们如下所示:

  • Request 对象中的 isXmlHttpRequest() 方法 - 如果发出了 AJAX 请求,则请求对象的 isXmlHttpRequest() 方法返回 true,否则返回 false。此方法用于在服务器端正确处理 AJAX 请求。

if ($request->isXmlHttpRequest()) { 
   // Ajax request 
} else { 
   // Normal request 
}
  • Zend/View/Model/JsonModel - JsonModelViewModel 的替代方案,专门用于 AJAX 和 REST API 场景。JsonModel 以及 JsonStrategy(在模块的视图管理器块中配置)将模型数据编码为 Json 并将其作为响应返回,而不是视图 (phtml)。

AJAX - 工作示例

让我们在 tutorial 模块中添加一个新的 ajax 页面 ajax,并异步获取图书信息。为此,我们应遵循以下步骤。

步骤 1:在模块配置中添加 JsonStrategy

更新 tutorial 模块配置文件 myapp/module/Tutorial/config/module.config.php 中的视图管理器块。然后,JsonStrategy 将与 JsonModel 协同工作以编码并发送 json 数据。

'view_manager' => [ 
   'template_map' => array
      ('layout/layout' => __DIR__ . '/../view/layout/newlayout.phtml'), 
   'template_path_stack' => [ 
      'tutorial' => __DIR__ . '/../view', 
   ], 
   'strategies' => array('ViewJsonStrategy',), 
],

步骤 2:在 TutorialController.php 中添加 ajaxAction 方法

使用以下代码在 TutorialController.php 中添加 ajaxAction 方法:

public function ajaxAction() { 
   $data = $this->bookTable->fetchAll(); 
   $request = $this->getRequest(); 
   $query = $request->getQuery();  
   if ($request->isXmlHttpRequest() || $query->get('showJson') == 1) { 
      $jsonData = array(); 
      $idx = 0; 
      foreach($data as $sampledata) { 
         $temp = array( 
            'author' => $sampledata->author, 
            'title' => $sampledata->title, 
            'imagepath' => $sampledata->imagepath 
         );  
         $jsonData[$idx++] = $temp; 
      } 
      $view = new JsonModel($jsonData); 
      $view->setTerminal(true); 
   } else { 
      $view = new ViewModel(); 
   }  
   return $view; 
} 

在这里,ajaxAction 将检查传入的请求是否为 AJAX 请求。如果传入的请求是 AJAX 请求,则将创建 JsonModel。否则,将创建一个普通的 ViewModel

在这两种情况下,都将从数据库中获取图书信息并填充到模型中。如果模型是 JsonModel,则将调用 JsonStrategy,它将数据编码为 json 并作为响应返回。

$query->get('showJson') == 1 用于调试目的。只需在 url 中添加 showJson=1,页面就会显示 json 数据。

步骤 3:添加 ajax.phtml

现在,为 ajaxAction 方法添加视图脚本 ajax.phtml。此页面将包含一个标签为“加载图书信息”的链接。

单击该链接将发出 AJAX 请求,该请求将以 Json 数据的形式获取图书信息,并以格式化表格的形式显示图书信息。AJAX 处理是使用 JQuery 完成的。

完整的代码清单如下:

<a id = "loadbook" href = "#">Load book information</a> 
</br> </br> 

<table class = "table"> 
   <tbody id = "book"> 
   </tbody> 
</table>  

<script language = "javascript"> 
$(document).ready(function(){  
   $("#loadbook").on("click", function(event){ 
      $.ajax({ 
         url:        '/tutorial/ajax', 
         type:       'POST',  
         dataType:   'json', 
         async:      true, 
         
         success: function(data, status) { 
            var e = $('<tr><th>Author</th><th>Title</th><th>Picture</th></tr>'); 
            $('#book').html(''); 
            $('#book').append(e); 
            
            for(i = 0; i < data.length; i++) { 
               book = data[i]; 
               var e = $('<tr><td id = "author"></td><td id = "title"></td>
               <td id="imagepath"><img src = ""/></td></tr>'); 
               $('#author', e).html(book['author']); 
               $('#title', e).html(book['title']); 
               $('#imagepath img', e).attr('src', book['imagepath']); 
               $('#book').append(e); 
            } 
         }, 
         error : function(xhr, textStatus, errorThrown) { 
            alert('Ajax request failed.'); 
         } 
      }); 
   }); 
}); 
</script>

步骤 4:运行应用程序

最后,运行应用程序 - https://127.0.0.1:8080/tutorial/ajax 并单击“加载图书信息”链接。

结果将如下所示:

Ajax 页面 -

Ajax Page

包含图书信息的 Ajax 页面

Book Information

包含调试信息的 Ajax 页面

Debugging Information

Zend Framework - Cookie 管理

Cookie 是 Web 应用程序中一个非常重要的概念。它提供了一个选项,可以在浏览器本身中持久保存用户数据(通常是一小段信息),且持续时间有限。

Cookie 用于维护 Web 应用程序的状态。Zend 框架在 zend-http 组件中提供了一个 cookie 模块。此 zend-http 提供了 HTTP 抽象及其实现。

安装 HTTP 组件

可以使用以下代码中指定的 Composer 轻松安装 HTTP 组件。

composer require zendframework/zend-http 

概念

zend-http 提供了 Zend\Http\Cookies 类来管理 cookie。它与 Zend\Http\Client 类一起使用,后者用于向 Web 服务器发送请求。可以像以下代码中所示初始化 cookie:

use Zend\Http\Cookies  
$c = new Cookies(); 

当 HTTP 客户端 (Zend\Http\Client) 首次向 Web 服务器发送 URI 请求时,它没有任何 cookie。Web 服务器接收到请求后,会在其响应对象中包含 cookie 作为 HTTP 标头 Set-Cookie 并将其发送到 HTTP 客户端。HTTP 客户端将从 http 响应中提取 cookie,并在后续请求中将其作为相同的 HTTP 标头重新发送。通常,每个 cookie 都将映射到一个域和该域的路径。

Cookies 类中可用的方法如下:

  • addCookie(uri) - 用于将 cookie 添加到给定 URI 的请求对象中。

  • getCookie(cookieName, $cookieForm) - 用于获取给定 URI $uri 中可用的 cookie $cookieName。第三个参数是 cookie 的返回方式,可以是字符串或数组。

  • fromResponse(uri) - 用于从给定 URI 的响应对象中提取 cookie。

  • addCookiesFromResponse - 与 fromResponse 相同,但它会提取 cookie 并将其重新添加到给定 URI 的请求对象中。

  • isEmpty() - 用于查找给定的 Cookie 对象是否包含任何 cookie。

  • reset() - 用于清除给定 URI 中的所有 cookie。

在下一章中,我们将讨论 Zend Framework 中的会话管理。

Zend Framework - 会话管理

会话是 Web 应用程序中一个非常重要的概念。它提供了一个选项,可以在 Web 服务器中持久保存用户数据,持续时间有限。Zend 框架提供了一个单独的组件 zend-session 来处理会话信息。

安装会话组件

可以使用以下 Composer 命令安装会话组件:

composer require zendframework/zend-session 

会话组件

Zend 框架提供了六个组件来处理会话管理。所有这些组件都在下面进行了说明:

  • Zend\Session\Container - 读取和写入会话信息的主要 API。

  • Zend\Session\SessionManager - 用于管理会话的整个生命周期。

  • Zend\Session\Storage - 用于指定会话数据如何在内存中存储。

  • Zend\Session\SaveHandler - 用于将会话数据存储和检索到物理位置,如 RDBMS、Redis、MangoDB 等。

  • Zend\Session\Validator - 用于通过交叉检查初始请求和后续请求的远程地址和用户代理来保护会话免遭劫持。

  • Zend\Session\Config\SessionConfig - 用于配置会话的行为方式。

默认配置足以使用会话。使用上述组件,可以轻松处理会话的所有方面。

会话组件示例

让我们遵循以下几点来创建一个新页面以了解 Zend 框架中的会话。默认情况下,创建 Container 类的实例足以管理会话。

  • TutorialController 中创建一个新的操作 sessionAction

  • 初始化一个 Container 对象。

$c = new Container();
  • 检查任意键 count 是否存在。如果该键不可用,则将 count 初始化为 1。如果可用,则按以下代码所示递增其值。

if (!isset($c->count)) { 
   $c->count = 0; 
} else { 
   $c->count++; 
} 
  • 在 ViewModel 中注册 count。

  • 为 sessionAction 创建一个模板文件 - session.phtml,位于 myapp/module/Tutorial/view/tutorial/tutorial/session.phtml 中,然后呈现 count 值。

  • 刷新页面将增加会话中 count 的值。完整列表如下所示:

TutorialController.php

public function sessionAction() { 
   $c = new Container();  
   if (!isset($c->count)) { 
      $c->count = 0; 
   } else { 
      $c->count++; 
   }  
   $view = new ViewModel([ 
      'count' => $c->count, 
   ]);  
   return $view; 
}

session.pthml

Session data, COUNT = <?= $this->count ?>

示例结果

Session data, Count = 5

Zend Framework - 身份验证

身份验证是任何 Web 应用程序中最重要且必不可少的特性之一。Zend Framework 提供了一个单独的组件来处理身份验证,称为 zend-authentication

安装身份验证组件

可以使用以下 Composer 命令安装身份验证组件。

composer require zendframework/zend-authentication

概念

通常,开发人员会编写一个 php 函数来根据数据源对用户详细信息进行身份验证。身份验证完成后,身份验证详细信息将保留以供后续请求使用。Zend Framework 将此概念泛化,并提供了两个类,如下所述:

类 1 Zend\Authentication\Adaptor\AdaptorInterface

此类提供了一个名为 authenticate 的单一方法来编写身份验证逻辑。authenticate 方法返回 Zend\Authentication\Result 类的实例。

Result 对象包含身份验证状态;如果身份验证成功则为身份,如果身份验证失败则为错误消息。authenticate 接口和 result 类的签名如下所示:

AdaptorInterface

namespace Zend\Authentication\Adaptor; 
public function authenticate() { 
   // code 
}

Result 类

namespace Zend\Authentication; 
class Result { 
   public function __construct($code, $identity, array $messages = []); 
}

Zend Framework 提供了一个默认实现,用于针对数据库、ldap、http 基本凭据和摘要凭据进行身份验证。Adaptor 会进行身份验证,但不会将详细信息保留以供任何未来请求使用。

类 2 Zend\Authentication\AuthenticationService

AuthenticationService 是主要组件,它使用已配置的适配器进行身份验证。身份验证完成后,它会保留身份验证详细信息并提供方法 hasIdentity() 来检查身份是否可用、getIdentity() 来获取身份验证详细信息以及 clearIdentity() 来清除身份验证详细信息。

使用此 AuthenticationService 的部分代码列表如下所示:

$adap = new Adapter($username, $password);  
$auth = new AuthenticationService(); 
$result = $auth->authenticate($adap);  
if($result->isValid) { 
   $identity = $auth->getIdentity(); 
} else { 
   // process $result->getMessages() 
}  
// clear 
$auth->clearIdentity();

与授权相关的内容被打包为两个单独的模块,它们是 - zend-permissions-aclzend-permissions-rbac。zend-permissions-acl 基于访问控制列表,zend-permissions-rbac 基于基于角色的访问控制列表。它们提供了 ACL 和 RBAC 概念的高级抽象,并有助于编写企业级应用程序。

Zend Framework - 邮件管理

Zend Framework 提供了一个名为 zend-mail 的单独组件来发送邮件消息。zend-mail 组件还提供了一个选项,可以使用文本和 html 格式读取和编写带附件的邮件消息。在 Zend 中发送邮件非常容易且配置简单。

让我们在本节中了解邮件概念、基本设置、高级设置(如 SMTP 传输)等。

安装邮件组件

可以使用以下 Composer 命令安装邮件组件。

composer require zendframework/zend-mail

基本邮件配置

基本邮件包含一个或多个收件人、主题、正文和发件人。Zend 提供了 Zend\Mail\Message 类来创建新的邮件消息。要使用 zend-mail 发送邮件,必须至少指定一个收件人和邮件正文。

创建新邮件消息的部分代码如下所示:

use Zend\Mail;
$mail = new Mail\Message(); 
$mail->setSubject('Zend email sample'); 
$mail->setBody('This is content of the mail message'); 
$mail->setFrom('[email protected]', "sender-name"); 
$mail->addTo('[email protected]', "recipient-name"); 

Zend 提供了 Zend\Mail\Sendmail 类来发送邮件消息。Sendmail 使用 php 本机邮件函数 mail 来发送邮件消息,并且我们可以使用 php 配置文件配置传输层。

使用 Sendmail 的部分代码如下所示:

$transport = new Mail\Transport\Sendmail(); 
$transport->send($mail);

zend-mail 提供了许多传输层,并且每个传输层可能需要许多其他参数,例如用户名、密码等。

邮件管理方法

一些值得注意的邮件管理方法如下:

  • isValid − 缺少“发件人”地址的邮件无效。

isValid() : bool
  • setEncoding − 设置邮件编码。

setEncoding(string $encoding) : void
  • getEncoding − 获取邮件编码。

getEncoding() : string
  • setHeaders − 编写邮件头。

setHeaders(Zend\Mail\Headers $headers) : void
  • getHeaders − 访问邮件头集合。

getHeaders() : Zend\Mail\Headers
  • setFrom − 设置(覆盖)发件人地址。它包含键值对,其中键是可读的名称,值是电子邮件地址。

setFrom( 
   string|AddressInterface|array|AddressList|Traversable $emailOrAddressList, 
      string|null $name 
) : void 
  • addFrom − 添加“发件人”地址。

addFrom( 
   string|AddressInterface|array|AddressList|Traversable $emailOrAddressOrList, 
      string|null $name 
) : void 
  • getFrom − 获取“发件人”列表。

getFrom() : AddressList 
setTo - Overwrite the address list in the To recipients. 
setTo( 
   string|AddressInterface|array|AddressList|Traversable $emailOrAddressList, 
      null|string $name 
) : void 
  • setSubject − 设置邮件主题头值。

setSubject(string $subject) :void 
  • setBody − 设置邮件正文。

setBody(null|string|Zend\Mime\Message|object $body) : void 

SMTP 传输层

zend-mail 提供了使用 SMTP 服务器发送邮件的选项,通过 Zend\Mail\Transport\Smtpclass 类实现。它类似于 Sendmail,但有一些额外的选项可以配置 SMTP 主机、端口、用户名、密码等。

部分代码如下:

use Zend\Mail\Transport\Smtp as SmtpTransport; 
use Zend\Mail\Transport\SmtpOptions;  
$transport = new SmtpTransport(); 
$options = new SmtpOptions([ 
   'name' => 'localhost', 
   'host' =>'smtp.gmail.com', 
   'port' => 465, 
]); 
$transport->setOptions($options); 

这里:

  • name − SMTP 主机的名称。

  • host − 远程主机名或 IP 地址。

  • port − 远程主机监听的端口。

邮件概念 - 示例

让我们按照以下步骤编写一个简单的 PHP 控制台应用程序来理解邮件概念。

  • 创建一个名为“mailapp”的文件夹。

  • 使用 composer 工具安装 zend-mail

  • 在“mailapp”文件夹内创建一个名为 Mail.php 的 PHP 文件。

  • 使用 Zend\Mail\Message 创建邮件。

$message = new Message(); 
$message->addTo('[email protected]'); 
$message->addFrom('[email protected]'); 
$message->setSubject('Hello!'); 
$message->setBody("My first Zend-mail application!"); 
  • 创建 SMTP 传输层并添加必要的配置。

// Setup SMTP transport using LOGIN authentication 
$transport = new SmtpTransport(); 
$options = new SmtpOptions([ 
   'name' => 'localhost', 
   'host' => 'smtp.gmail.com', // or any SMTP server 
   'port' => 465, // port on which the SMTP server is listening 
   'connection_class' => 'login', 
   'connection_config' => [ 
      username' => '<your username>', 'password' => '<your password>', 
      'ssl' => 'ssl'], 
]); 
$transport->setOptions($options); 
  • 使用 send 方法发送邮件。

$transport->send($message);

完整列表,Mail.php 如下:

<?php  
require __DIR__ . '/vendor/autoload.php';  

use Zend\Mail\Message; 
use Zend\Mail\Transport\Smtp as SmtpTransport; 
use Zend\Mail\Transport\SmtpOptions;  
  
$message = new Message(); 
$message->addTo('[email protected]'); 
$message->addFrom('[email protected]'); 
$message->setSubject('Hello!'); 
$message->setBody("My first Zend-mail application!");  
  
// Setup SMTP transport using LOGIN authentication 
$transport = new SmtpTransport(); 
$options = new SmtpOptions([ 
   'name' => 'localhost', 
   'host' => 'smtp.gmail.com', // or any SMTP server 
   'port' => 465, // port on which the SMTP server is listening 
   'connection_class' => 'login', 
   'connection_config' => [ 
      'username' => '<your username>', 'password' => '<your password>', 
      'ssl' => 'ssl'], 
]); 
$transport->setOptions($options); 
$transport->send($message);

现在,在命令提示符中运行应用程序 php Mail.php。这将根据应用程序中配置的内容发送邮件。

Zend Framework - 单元测试

通常,我们可以使用 高级调试工具 或简单的命令如 echodie 来调试 PHP 应用程序。在 Web 场景中,我们需要测试业务逻辑以及表示层。Web 应用程序中的表单可以通过输入相关的测试数据来测试,以确保表单按预期工作。

网站的设计可以通过浏览器手动测试。这些类型的测试过程可以使用单元测试自动化。单元测试在大型项目中至关重要。这些单元测试将有助于自动化测试过程并在出现问题时提醒开发人员。

设置 PHPUnit

Zend 框架集成了 PHPUnit 单元测试框架。要为 Zend 框架编写单元测试,我们需要设置 PHPUnit,这可以通过以下 Composer 命令轻松完成。

$ composer require --dev phpunit/phpunit

执行上述命令后,您将收到如下代码块所示的响应。

Using version ^5.7 for phpunit/phpunit 
./composer.json has been updated 
Loading composer repositories with package information 
Updating dependencies (including require-dev) 
Nothing to install or update 
Writing lock file 
Generating autoload files 

现在,当您打开“composer.json”文件时,您将看到以下更改:

"require-dev": { 
   "phpunit/phpunit": "^5.7" 
}

TestCase 和断言

Zend 框架提供了辅助类来对控制器进行单元测试。TestCasePHPUnit 框架中编写测试用例的主要组件,Zend 框架提供了 TestCase 的抽象实现,称为 AbstractHttpControllerTestCase

此 AbstractHttpControllerTestCase 提供各种 Assert 方法,并且可以按功能分组。它们如下:

  • 请求断言 − 用于断言 HTTP 请求。例如,assertControllerName。

  • CSS 选择器断言 − 使用 HTML DOM 模型检查响应 HTML。

  • XPath 断言 − 基于 XPath 的 CSS 选择器断言的替代方法。

  • 重定向断言 − 用于检查页面重定向。

  • 响应头断言 − 用于检查响应头,如状态代码 (assertResponseStatusCode)

创建测试目录

可以为每个模块单独编写单元测试。所有与测试相关的编码都需要在模块根目录下的 test 文件夹内创建。

例如,要为 Tutorial 模块下可用的 TutorialController 编写测试,测试类需要放在 myapp/module/Tutorial/test/Controller/ 目录下。

示例

让我们编写一个测试类来对 TutorialController 进行单元测试。

首先,我们应该编写一个名为 TutorialControllerTest 的类并将其扩展到 AbstractHttpControllerTestCase。

下一步是编写一个 Setup 方法来设置测试环境。这可以通过调用 setApplicationConfig 方法并传递我们的主应用程序配置文件 myapp/config/application.config.php 来完成。

public function setUp() { 
   $configOverrides = [];  
   $this->setApplicationConfig(ArrayUtils::merge( 
      include __DIR__ . '/../../../../config/application.config.php', 
         $configOverrides 
   )); 
   parent::setUp(); 
}

编写一个或多个方法并根据需要调用各种断言方法。

 $this->assertMatchedRouteName('tutorial');

我们已经编写了测试类,完整列表如下:

<?php  
namespace TutorialTest\Controller;  
use Tutorial\Controller\TutorialController; 
use Zend\Stdlib\ArrayUtils; 
use Zend\Test\PHPUnit\Controller\AbstractHttpControllerTestCase;  

class TutorialControllerTest extends AbstractHttpControllerTestCase { 
   public function setUp() { 
      $configOverrides = [];  
      $this->setApplicationConfig(ArrayUtils::merge( 
         include __DIR__ . '/../../../../config/application.config.php', 
            $configOverrides 
      ));  
      parent::setUp(); 
   }  
   public function testIndexActionCanBeAccessed() { 
      $this->dispatch('/tutorial', 'GET'); 
      $this->assertResponseStatusCode(200); 
      $this->assertModuleName('tutorial'); 
      $this->assertControllerName(TutorialController::class); 
      $this->assertControllerClass('TutorialController'); 
      $this->assertMatchedRouteName('tutorial'); 
   } 
} 

现在,打开命令提示符,移动到应用程序根目录并执行 vendor 文件夹中可用的 phpunit 可执行文件。

cd /path/to/app  
./vendor/bin/phpunit ./vendor/bin/phpunit module/
   Tutorial/test/Controller/TutorialControllerTest.php 

结果将如下代码块所示:

PHPUnit 5.7.5 by Sebastian Bergmann and contributors.  
.1 / 1 (100%)  
Time: 96 ms, Memory: 8.00MB  
OK (1 test, 5 assertions)

Zend Framework - 错误处理

为了系统的顺利运行,需要有效地处理系统故障。Zend 框架带有一个 默认错误捕获,它会在错误发生时打印并记录错误。此相同的错误处理程序用于捕获 异常

当调试为真时,错误处理程序显示错误,当调试为假时,错误处理程序记录错误。Zend 框架有几个异常类,内置的异常处理将捕获任何未捕获的异常并呈现一个有用的页面。

默认错误处理

我们可以在应用程序配置文件 myapp/module/Application/config/module.config.php 中配置默认错误设置。

部分代码示例如下:

'view_manager' => [ 
   'display_not_found_reason' => true, 
   'display_exceptions'       => true, 
   'doctype'                  => 'HTML5', 
   'not_found_template'       => 'error/404', 
   'exception_template'       => 'error/index', 
   'template_map' => [ 
      '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' => [ 
      __DIR__ . '/../view', 
   ], 
], 

这里,display_exception、not_found_template、exception_template、error/404 和 error/index 是与错误相关的配置项,并且不言自明。

其中最重要的项目是 error/index。这是在系统中发生异常时显示的模板。我们可以修改此模板 myapp/module/Application/view/error/index.phtml 来控制显示的错误数量。

Zend Framework - 工作示例

在本章中,我们将学习如何在 Zend 框架中创建一个完整的基于 MVC 的员工应用程序。请按照以下步骤操作。

步骤 1:Module.php

首先,我们应该在 – myapp/module/Employee/src/ 目录内创建一个 Employee 模块,然后实现 ConfigProviderInterface 接口。

Module 类的完整代码如下:

<?php  
namespace Employee;  
use Zend\ModuleManager\Feature\ConfigProviderInterface;  
class Module implements ConfigProviderInterface { 
   public function getConfig() {    
      return include __DIR__ . '/../config/module.config.php'; 
   }    
}

步骤 2:composer.json

在 autoload 部分使用以下代码在 composer.json 中配置 Tutorial 模块。

"autoload": { 
   "psr-4": { 
      "Application\\": "module/Application/src/", 
      "Tutorial\\": "module/Tutorial/src/", 
      "Employee\\": "module/Employee/src/" 
   } 
}

现在,使用 composer update 命令更新应用程序。

composer update

Composer 命令将对应用程序进行必要的更改并在以下命令提示符中显示日志。

Loading composer repositories with package information 
Updating dependencies (including require-dev) 
   - Removing zendframework/zend-component-installer (0.3.0) 
   - Installing zendframework/zend-component-installer (0.3.1) 
   Downloading: 100%           
    
   - Removing zendframework/zend-stdlib (3.0.1) 
   - Installing zendframework/zend-stdlib (3.1.0) 
   Loading from cache  
    
   - Removing zendframework/zend-eventmanager (3.0.1) 
   - Installing zendframework/zend-eventmanager (3.1.0) 
   Downloading: 100%           
    
   - Removing zendframework/zend-view (2.8.0) 
   - Installing zendframework/zend-view (2.8.1) 
   Loading from cache  
    
   - Removing zendframework/zend-servicemanager (3.1.0) 
   - Installing zendframework/zend-servicemanager (3.2.0) 
   Downloading: 100%           
    
   - Removing zendframework/zend-escaper (2.5.1) 
   - Installing zendframework/zend-escaper (2.5.2) 
   Loading from cache  
   
   - Removing zendframework/zend-http (2.5.4) 
   - Installing zendframework/zend-http (2.5.5) 
   Loading from cache  
    
   - Removing zendframework/zend-mvc (3.0.1)
   - Installing zendframework/zend-mvc (3.0.4)  
   Downloading: 100%           
   
   - Removing phpunit/phpunit (5.7.4) 
   - Installing phpunit/phpunit (5.7.5) 
   Downloading: 100%           
  
Writing lock file 
Generating autoload files     

步骤 3:Employee 模块的 module.config.php

使用以下代码创建模块配置文件“module.config.php”,位于 myapp/module/Employee/config 下。

<?php  
namespace Employee;  
use Zend\ServiceManager\Factory\InvokableFactory; 
use Zend\Router\Http\Segment;  
return [ 
   'controllers' => [ 
      'factories' => [ 
         Controller\EmployeeController::class => InvokableFactory::class, 
      ], 
   ], 
   'view_manager' => [ 
      'template_path_stack' => ['employee' => __DIR__ . '/../view',], 
   ], 
];

现在,在应用程序级配置文件 myapp/config/modules.config.php 中配置 Employee 模块。

return ['Zend\Router', 'Zend\Validator', 'Application', 'Tutorial', 'Employee'];

步骤 4:EmployeeController

创建一个新的 PHP 类 EmployeeController,通过扩展 AbstractActionController 并将其放在 myapp/module/Employee/src/Controller 目录下。

完整的代码清单如下:

<?php  
namespace Employee\Controller;  
use Zend\Mvc\Controller\AbstractActionController; 
use Zend\View\Model\ViewModel;  
class EmployeeController extends AbstractActionController { 
   public function indexAction() { 
      return new ViewModel(); 
   } 
}

步骤 5:路由器配置

让我们在 Employee 模块中添加一个分段路由。更新 Employee 模块配置文件 module.config.php,位于 myapp/module/Employee/config 下。

<?php  
namespace Employee;
use Zend\ServiceManager\Factory\InvokableFactory; 
use Zend\Router\Http\Segment;  
return [ 
   'controllers' => [ 
      'factories' => [ 
         Controller\EmployeeController::class => InvokableFactory::class, 
      ], 
   ], 
   'router' => [ 
      'routes' => [ 
         'employee' => [ 
            'type' => Segment::class,
            'options' => [ 
               'route' => '/employee[/:action[/:id]]',
               'constraints' => [
                  'action' => '[a-zA-Z][a-zA-Z0-9_-]*',
                  'id' => '[0-9]+', 
               ], 
               'defaults' => [ 
                  'controller' => Controller\EmployeeController::class,
                  'action' => 'index', 
               ], 
            ], 
         ], 
      ], 
   ], 
   'view_manager' => [ 
      'template_path_stack' => [ 
         'employee' => __DIR__ . '/../view', 
      ], 
   ], 
]; 

我们已成功添加了 Employee 模块的路由。下一步是为 Employee 应用程序创建视图脚本。

步骤 6:创建 ViewModel

在 myapp/module/Employee/view/employee/employee 目录下创建一个名为“index.phtml”的文件。

在文件中添加以下更改:

<div class = "row content"> 
   <h3>This is my first Zend application</h3> 
</div> 
Move to “EmployeeController.php” file and edit the following changes, 

<?php 
namespace Employee\Controller;  
use Zend\Mvc\Controller\AbstractActionController; 
use Zend\View\Model\ViewModel;  
class EmployeeController extends AbstractActionController { 
   public function indexAction() { 
      return new ViewModel();  
   } 
}

最后,我们已成功完成 Employee 模块。我们可以使用以下 URL 访问它:https://127.0.0.1:8080/employee

结果

Application Template

在下一步中,我们将执行员工应用程序中的添加、编辑删除数据操作。要执行这些操作,我们应该首先创建一个数据库模型。将在下一步中进行描述。

步骤 7:创建模型

让我们在模块的src 目录中创建一个名为 Employee 的模型。通常,模型在 Model 文件夹下分组 (myapp/module/Employee/src/Model/Employee.php)

<?php  
namespace Employee\Model;  
class Employee { 
   public $id; 
   public $emp_name; 
   public $emp_job; 
}

步骤 8:MySQL 表

使用以下命令在本地 MYSQL 服务器中创建一个名为tutorials的数据库:

create database tutorials;

让我们使用以下 SQL 命令在数据库中创建一个名为employee的表:

use tutorials;  
CREATE TABLE employee ( 
   id int(11) NOT NULL auto_increment, 
   emp_name varchar(100) NOT NULL, 
   emp_job varchar(100) NOT NULL, 
   PRIMARY KEY (id) 
);

使用以下查询将数据插入employee表:

INSERT INTO employee (emp_name, emp_job) VALUES ('Adam',  'Tutor'); 
INSERT INTO employee (emp_name, emp_job) VALUES ('Bruce',  'Programmer'); 
INSERT INTO employee (emp_name, emp_job) VALUES ('David',  'Designer'); 

步骤 9:更新数据库配置

使用必要的数据库驱动程序信息更新全局配置文件 myapp/config/autoload/global.php。

return [
   'db' => [
      'driver' => 'Pdo',
      'dsn' => 'mysql:dbname = tutorials;host=localhost',
      'driver_options' => [PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES \'UTF8\''],
   ],
];

现在,在本地配置文件 myapp/config/autoload/local.php 中更新数据库凭据。这样,我们可以分离本地和实时数据库连接凭据。

<?php 
return array( 
   'db' => array('username' => '<user_name>', 'password' => '<password>',), 
); 

步骤 10:实现 exchangeArray

在 Employee 模型中实现 exchangeArray 函数。

<?php 
namespace Employee\Model; 
class Employee { 
   public $id; 
   public $emp_name; 
   public $emp_job;  
   public function exchangeArray($data) { 
      $this->id = (!empty($data['id'])) ? $data['id'] : null; 
      $this->emp_name = (!empty($data['emp_name'])) ? $data['emp_name'] : null; 
      $this->emp_job = (!empty($data['emp_job'])) ? $data['emp_job'] : null; 
   } 
}

步骤 11:使用 TableGateway 获取员工数据

在 Model 文件夹本身中创建类 EmployeeTable。它在以下代码块中定义。

<?php  
namespace Employee\Model;  
use Zend\Db\TableGateway\TableGatewayInterface;  
class EmployeeTable { 
   protected $tableGateway; 
   public function __construct(TableGatewayInterface $tableGateway) { 
      $this->tableGateway = $tableGateway; 
   }
   public function fetchAll() { 
      $resultSet = $this->tableGateway->select();  
      return $resultSet; 
   } 
}    

步骤 12:配置 EmployeeTable 类

使用 getServiceConfig() 方法更新 Module.php 中的员工服务。

<?php
namespace Employee;
use Zend\Db\Adapter\AdapterInterface;
use Zend\Db\ResultSet\ResultSet;
use Zend\Db\TableGateway\TableGateway;
use Zend\ModuleManager\Feature\ConfigProviderInterface;

class Module implements ConfigProviderInterface {
   public function getConfig() {
      return include __DIR__ . '/../config/module.config.php';
   }
   public function getServiceConfig() {
      return [
         'factories' => [
            Model\EmployeeTable::class => function (    $container) {
               $tableGateway = $container>get( Model\EmployeeTableGateway::class);
               $table = new Model\EmployeeTable($tableGateway);
               return $table;
            },
            Model\EmployeeTableGateway::class => function ($container) {
               $dbAdapter = $container->get(AdapterInterface::class);
               $resultSetPrototype = new ResultSet();
               $resultSetPrototype->setArrayObjectPrototype(new Model\Employee());
               return new TableGateway('employee', $dbAdapter, null, $resultSetPrototype);
            },
         ],
      ];
   }
}

步骤 13:在控制器中添加员工服务

更新 myapp/module/config/module.config.php 中 Employee 模块配置的控制器部分,如下所示。

'controllers' => [
   'factories' => [
      Controller\EmployeeController::class => function($container) {
         return new Controller\EmployeeController(
            $container->get(Model\EmployeeTable::class)
         ); 
      }, 
   ], 
]

步骤 14:为 EmployeeController 添加构造函数

添加带有 EmployeeTable 作为参数的构造函数并编辑以下更改。

<?php  
namespace Employee\Controller; 
use Zend\Mvc\Controller\AbstractActionController; 
use Zend\View\Model\ViewModel;
use Employee\Model\Employee; 
use Employee\Model\EmployeeTable;  

class EmployeeController extends AbstractActionController { 
   private $table;  
   public function __construct(EmployeeTable $table) { 
      $this->table = $table; 
   }  
   public function indexAction() { 
      $view = new ViewModel([ 
         'data' => $this->table->fetchAll(), 
      ]);  
      return $view; 
   } 
} 

步骤 15:在视图脚本“index.phtml”中显示员工信息

转到文件index.phtml并进行以下更改:

<?php 
$title = 'Employee application'; 
$this->headTitle($title); 
?>  

<table class="table"> 
   <tr> 
      <th>Employee Name</th> 
      <th>Employee Job</th> 
      <th>Edit/Delete operations</th>
   </tr> 
   <?php foreach ($data as $empdata) : ?> 
   <tr>  
      <td><?php echo $this->escapeHtml($empdata->emp_name);?></td> 
      <td><?php echo $this->escapeHtml($empdata->emp_job);?></td> 
      <td> 
         <a href="<?php echo $this->url('employee', 
            array('action'=>'edit', 'id' =>$empdata->id));?>">Edit</a> 
         <a href="<?php echo $this->url('employee', 
            array('action'=>'delete', 'id' => $empdata->id));?>">Delete</a> 
      </td> 
   </tr> 
   <?php endforeach; ?> 
</table> 

现在我们已经成功创建了数据库模型,并且可以在应用程序中获取记录。

使用 URL 请求应用程序:https://127.0.0.1:8080/employee

结果

Successful Database

下一步说明了员工模块中的插入、编辑删除数据操作。

步骤 16:创建员工表单

在 myapp/module/Employee/src/Form 目录下创建一个名为 EmployeeForm.php 的文件。它在下面的代码块中描述。

<?php  
namespace Employee\Form; 
use Zend\Form\Form;  

class EmployeeForm extends Form { 
   public function __construct($name = null) { 
      /
      / we want to ignore the name passed 
      parent::__construct('employee');  
      $this->add(array( 
         'name' => 'id', 
         'type' => 'Hidden', 
      )); 
      $this->add(array( 
         'name' => 'emp_name', 
         'type' => 'Text', 
         'options' => array( 
            'label' => 'Name', 
         ), 
      )); 
      $this->add(array( 
         'name' => 'emp_job', 
         'type' => 'Text', 
         'options' => array( 
            'label' => 'Job', 
         ), 
      )); 
      $this->add(array( 
         'name' => 'submit', 
         'type' => 'Submit', 
         'attributes' => array(
            'value' => 'Go', 
            'id' => 'submitbutton', 
         ), 
      )); 
   } 
}                    

步骤 17:更新员工模型

更新员工模型并实现 InputFilterAwareInterface。转到目录 myapp/module/Employee/src/Employee/Model 并将以下更改添加到Employee.php文件中。

<?php  
namespace Employee\Model;  

// Add these import statements 
use Zend\InputFilter\InputFilter; 
use Zend\InputFilter\InputFilterAwareInterface; 
use Zend\InputFilter\InputFilterInterface;  

class Employee implements InputFilterAwareInterface { 
   public $id; 
   public $emp_name; 
   public $emp_job; 
   protected $inputFilter;                         
   public function exchangeArray($data) { 
      $this->id = (isset($data['id'])) ? $data['id'] : null;         
      $this->emp_name = (isset($data['emp_name'])) ? $data['emp_name'] : null;         
      $this->emp_job = (isset($data['emp_job']))  ? $data['emp_job'] : null; 
   }  
    
   // Add content to these methods:
   public function setInputFilter(InputFilterInterface $inputFilter) { 
      throw new \Exception("Not used"); 
   }  
   public function getInputFilter() { 
      if (!$this->inputFilter) { 
         $inputFilter = new InputFilter();  
         $inputFilter->add(array( 
            'name' => 'id', 
            'required' => true, 
            'filters' => array( 
               array('name' => 'Int'), 
            ), 
         ));  
         $inputFilter->add(array( 
            'name' => 'emp_name', 
            'required' => true, 
            'filters' => array( 
               array('name' => 'StripTags'), 
               array('name' => 'StringTrim'), 
            ), 
            'validators' => array( 
               array('name' => 'StringLength', 
                        'options' => array( 
                           'encoding' => 'UTF-8', 
                           'min' => 1, 
                           'max' => 50, 
                        ), 
                    ), 
                ), 
            ));
         $inputFilter->add(array( 
            'name' => 'emp_job', 
            'required' => true, 
            'filters' => array( 
               array('name' => 'StripTags'),  
               array('name' => 'StringTrim'), 
            ), 
            'validators' => array( 
               array('name' => 'StringLength', 
                  'options' => array( 
                     'encoding' => 'UTF-8', 
                     'min' => 1, 
                     'max' => 50, 
                  ), 
                  ), 
               ), 
         ));  
         $this->inputFilter = $inputFilter; 
      } 
      return $this->inputFilter; 
   } 
}             

步骤 18:在 Employee Controller 中添加 addAction

EmployeeController类中添加以下更改。

<?php  
use Zend\Mvc\Controller\AbstractActionController; 
use Zend\View\Model\ViewModel; 
use Employee\Model\Employee;       
use Employee\Model\EmployeeTable;    
use Employee\Form\EmployeeForm;

public function addAction() { 
   $form = new EmployeeForm();  
   $form->get('submit')->setValue('Add');  
   $request = $this->getRequest(); 
   
   if ($request->isPost()) { 
      $employee = new Employee(); 
      $form->setInputFilter($employee->getInputFilter()); 
      $form->setData($request->getPost());  
      
      if ($form->isValid()) { 
         $employee->exchangeArray($form->getData()); 
         $this->table->saveEmployee($employee);  
         
         // Redirect to list of employees 
         return $this->redirect()->toRoute('employee'); 
      } 
   } 
   return array('form' => $form); 
} 

步骤 19:在 EmployeeTable 类中添加保存功能

在 EmployeeTable 类中添加以下两个函数 – myapp/module/Employee/src/Model/EmployeeTable.php

public function getEmployee($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 saveEmployee(Employee $employee) { 
   $data = array (  
      'emp_name' => $employee->emp_name, 
      'emp_job'  => $employee->emp_job, 
   );  
   $id = (int) $employee->id; 
   if ($id == 0) { 
      $this->tableGateway->insert($data); 
   } else { 
      if ($this->getEmployee($id)) { 
         $this->tableGateway->update($data, array('id' => $id)); 
      } else { 
         throw new \Exception('Employee id does not exist'); 
      } 
   } 
}

步骤 20:为 AddAction 方法创建视图脚本 Add.phtml

在 myapp/module/view/employee/employee 中的“Add.phtml”文件中添加以下更改。

<?php 
   $title = 'Add new employee'; 
   $this->headTitle($title); 
?> 
<h1><?php echo $this->escapeHtml($title); ?></h1>  

<?php 
   $form->setAttribute('action', $this->url('employee', array('action' => 'add'))); 
   $form->prepare(); 
   echo $this->form()->openTag($form); 
   echo $this->formHidden($form->get('id')); 
   echo $this->formRow($form->get('emp_name'))."<br>"; 
   echo $this->formRow($form->get('emp_job'))."<br>";   
   echo $this->formSubmit($form->get('submit')); 
   echo $this->form()->closeTag(); 
Request the application using the url, https://127.0.0.1:8080/employee/add 

结果

New Employee

添加数据后,它将重定向到主页。

Redirect Home Page

步骤 21:编辑员工记录

让我们在 Employee 模块中执行编辑数据操作。在Employeecontroller.php中更新以下更改。

public function editAction() { 
   $id = (int) $this->params()->fromRoute('id', 0); 
   if (!$id) { 
      return $this->redirect()->toRoute('employee', array( 
         'action' => 'add' 
      )); 
   }  
   try { 
      $employee = $this->table->getEmployee($id); 
   } catch (\Exception $ex) { 
      return $this->redirect()->toRoute('employee', array( 
         'action' => 'index' 
      )); 
   }  
   $form = new EmployeeForm(); 
   $form->bind($employee); 
   $form->get('submit')->setAttribute('value', 'Edit');  
   $request = $this->getRequest(); 
   
   if ($request->isPost()) { 
      $form->setInputFilter($employee->getInputFilter()); 
      $form->setData($request->getPost());  
      if ($form->isValid()) { 
         $this->table->saveEmployee($employee);  
         
         // Redirect to list of employees 
         return $this->redirect()->toRoute('employee'); 
      } 
   }  
   return array('id' => $id, 'form' => $form,); 
}

在这里,我们查找匹配路由中的id,然后加载员工详细信息以进行编辑操作。

步骤 22:Employee.php

现在在“Employee.php”文件中添加以下更改,该文件位于myapp/module/Employee/src/Employee/Model/目录下。

public function getArrayCopy() { 
   return get_object_vars($this); 
}

这里,Zend\Stdlib\Hydrator\ArraySerializable 期望在模型中找到两个方法:getArrayCopy()exchangeArray()

其中,exchangeArray() 用于迭代。此函数用于绑定来自员工表的数据。

现在,我们需要为editAction()创建一个视图脚本。

步骤 23:创建 Edit.phtml

在 module/Employee/view/employee/employee/edit.phtml 中创建一个视图脚本文件。

<?php 
   $title = 'Edit employee records'; 
   $this->headTitle($title); 
?>  
<h1><?php echo $this->escapeHtml($title); ?></h1>  

<?php 
$form = $this->form;  
$form->setAttribute('action', $this->url( 
   'employee', 
   array('action' => 'edit', 'id' => $this->id,) 
)); 
$form->prepare();  
echo $this->form()->openTag($form); 
echo $this->formHidden($form->get('id')); 
echo $this->formRow($form->get('emp_name'))."<br>"; 
echo $this->formRow($form->get('emp_job'))."<br>"; 
echo $this->formSubmit($form->get('submit')); 
echo $this->form()->closeTag();

编辑员工详细信息如下面的屏幕截图所示。

Edit Record

编辑数据后,它将重定向到主页。

Edited Data

步骤 24:添加 deleteEmployee 方法

在 EmployeeTable 类中添加 deleteEmployee 方法 – myapp/module/Employee/src/Model/EmployeeTable.php

public function deleteEmployee($id) { 
   $this->tableGateway->delete(['id' => (int) $id]); 
}

步骤 25:删除员工记录

现在让我们在 Employee 模块中执行删除数据操作。在 EmployeeController 类中添加以下方法 deleteAction

public function deleteAction() { 
   $id = (int) $this->params()->fromRoute('id', 0); 
   if (!$id) { 
      return $this->redirect()->toRoute('employee'); 
   }  
   $request = $this->getRequest(); 
   if ($request->isPost()) { 
      $del = $request->getPost('del', 'No');  
      if ($del == 'Yes') { 
         $id = (int) $request->getPost('id');
         $this->table->deleteEmployee($id); 
      } 
      return $this->redirect()->toRoute('employee'); 
   }  
   return array( 
      'id' => $id, 
      'employee' => $this->table->getEmployee($id) 
   ); 
}            

这里,deleteEmployee() 方法通过员工的id删除员工,并重定向到员工列表页面(主页)。

现在让我们为 deleteAction() 方法创建相应的视图脚本。

步骤 26:创建视图脚本

在 - myapp/module/Employee/view/employee/employee/delete.phtml 中创建一个名为 delete.phtml 的文件,并在其中添加以下代码。

<?php 
   $title = 'Delete an employee record'; 
   $this->headTitle($title);  
?> 
<h1><?php echo $this->escapeHtml($title); ?></h1>  

'<?php echo $this->escapeHtml($employee->emp_name); ?>' by 
'<?php echo $this->escapeHtml($employee->emp_job); ?&'?  
<?php 
   $url = $this->url('employee', array('action' => 'delete', 'id' => $this->id,)); 
?>  

<form action ="<?php echo $url; ?>" method = "post">
   <div> 
      <input type = "hidden" name = "id" value = "<?php echo (int) $employee->id; ?>" /> 
      <input type = "submit" name = "del" value = "Yes" /> 
      <input type = "submit" name = "del" value = "No" /> 
   </div> 
</form>  

现在,使用主页中的编辑链接删除任何员工,结果将如以下屏幕截图所示。

结果

Deleted Record

我们通过实现所有必要的功能成功完成了 Employee 模块。

结论

在当前竞争激烈的环境中,Zend 框架被开发人员置于首位。它为 PHP 语言中的任何程序或任何类型的应用程序提供抽象。它是一个成熟的框架,并支持现代 PHP 语言特性。它有趣、专业、不断发展,并与当前技术保持同步。

广告