深入教程

介绍博客模块

现在我们已经知晓了 zend-mvc 框架应用的基本操作, 接下来让我们继续创建我们自己的模块,我们将创建一个名为 "Blog" 的模块。 这个模块将会显示一个表示单个博客帖子的数据库条目的列表,每个帖子将会有三个属性 id, text, and title。我们将会创建一个表单用来天机的新的帖子到数据库中 以及用来编辑一个存在的帖子。此外我们将使用最佳方案来实现这个教程。

编写一个新的模块

让我们在 /module 目录下创建一个名为 Blog 的新目录,并建立如下的目录结构:

module/
    Blog/
        config/
        src/
        view/

在能够被 ModuleManager, 识别为一个模块之前,我们需要做三件事:

  • 告诉 Composer 怎么自动去加载我们新模块的类。
  • Blog 命名空间内创建一个 Module 类。
  • 将新模块告知给应用。

让我们告知 Composer 我们的新模块。打开项目根目录中的 composer.json 文件, 编辑 autoload 选项,为 Blog 模块添加一个新的 PSR-4 实体;完成后将会和如下格式一样:

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

一旦完成上面的操作,我们就需要跟新 Composer 自动加载项:

$ composer dump-autoload

接下来,我们将在 Blog 命名空间下创建一个 Module 类。创建一个如下内容的文件 module/Blog/src/Module.php

<?php
namespace Blog;

class Module
{
}

现在我们已经配置好模块可以被 ModuleManager. 检测到了。接下来我们将模块添加到应用中。尽管我们的模块现在不能进行任何操作, 同时也仅仅只存在 Module.php 类能够被模块管理器加载。 我们需要在 config/modules.config.php 文件中添加 Blog 实体到模块数组中:

<?php
// In config/modules.config.php:

return [
    /* ... */
    'Application',
    'Album',
    'Blog',
];

如果你现在刷新你的应用你讲不会看见任何的改变(但是也没有任何的错误出现)。

这时我们可以退一步来讨论下什么事模块。简单来说,模块就是对你应用一个功能集合的封装。 正如你看见的那样一个模块可以向应用中添加一个功能,就像我们的 Blog 模块那样; 或者他可以一个后台的功能共应用中其他的模块来调用,就像与第三方 API 交互那样。

组织你的代码到模块中使其在其他应用中可以很方便的重用,或者使用由社区编写的模块。

配置模块

接下来我们将为我们的应用添加一个路由,以便我们可以通过链接 localhost:8080/blog 来访问我们的模块。我们通过在添加路由配置到我们的模块中来实现这个需求,但是首先我们需要告知 ModuleManager 以及配置好了需要加载。

我们通过在 Module 类中添加一个 getConfig() 方法来返回配置信息。 (这个方法已经在 ConfigProviderInterface 中定义,虽然实现这个接口是可选的。) 这个方法需要返回一个 array 或者一个 Traversable 对象。 我们继续编辑 module/Blog/src/Module.php

// In /module/Blog/Module.php:
class Module
{
    public function getConfig()
    {
        return [];
    }
}

这样,我们就可以开始配置我们的模块了。配置文件可能变得比较庞大,而且在所有的配置都放在 getConfig() 方法中也不是一个最优的解决方案。为了保证我们项目的可维护性, 我们将将我们的配置文件放入一个独立的文件中。创建 module/Blog/config/module.config.php 文件:

<?php
return [];

现在我们重写 getConfig() 函数来包含我们新创建的文件来返回一个数组:

<?php
// In /module/Blog/Module.php:

public function getConfig()
{
    return include __DIR__ . '/../config/module.config.php';
}

刷新你的引用,你任然还是看不见任何的变化。接下来我们将添加一个新的路由到我们的配置文件中:

// In /module/Blog/config/module.config.php:
namespace Blog;

use Laminas\Router\Http\Literal;

return [
    // 该行开始为路由管理器配置路由信息
    'router' => [
        // 开始配置所有可能的路由
        'routes' => [
            // 定义一个名为 "blog" 的路由
            'blog' => [
                // 定义路由类型为 "literal" :
                'type' => Literal::class,
                // 配置路由信息
                'options' => [
                    // 监听 "/blog" :
                    'route' => '/blog',
                    // 定义当路由匹配的时候默认的控制器和操作
                    'defaults' => [
                        'controller' => Controller\ListController::class,
                        'action'     => 'index',
                    ],
                ],
            ],
        ],
    ],
];

我们定义了一个名为 blog 的路由来监听链接 localhost:8080/blog。 当有人访问这个路由的时候,Blog\Controller\ListController 类的 indexAction() 函数将会被执行。然而现在这个控制器还不存在, 因此当你刷星这个页面的时候,你将会看见如下的错误信息:

A 404 error occurred
Page not found.
The requested controller could not be mapped by routing.

Controller:
Blog\Controller\ListController(resolves to invalid controller class or alias: Blog\Controller\ListController)

现在我们需要告诉我们的模块在哪里可以找到名 Blog\Controller\ListController 的控制器。 我们需要在 module/Blog/config/module.config.php 这个配置文件中添加 controllers 这个配置信息来实现这个功能。

namespace Blog;

use Laminas\ServiceManager\Factory\InvokableFactory;

return [
    'controllers' => [
        'factories' => [
            Controller\ListController::class => InvokableFactory::class,
        ],
    ],
    /* ... */
];

这个配置使用 zend-servicemanager InvokableFactoryBlog\Controller\ListController 控制器类定义了一个工厂 (内部会对类进行一个不带参数的实例化)。重载这个页面我们将会看见如下信息:

Fatal error: Class 'Blog\Controller\ListController' not found in {projectPath}/vendor/laminas/laminas-servicemanager/src/Factory/InvokableFactory.php on line 32

这个错误信息告诉我们应用已经知晓需要去加载这个类了,但是不能找到这个类。 在这个实例中,我们已经设置了自动加载,但是我们还没有定义这个控制器类!

根据如下内容创建文件 module/Blog/src/Controller/ListController.php

<?php
namespace Blog\Controller;

class ListController
{
}

重载页面我们将会看见一个新的页面,这个心得错误信息如下:

A 404 error occurred
Page not found.
The requested controller was not dispatchable.

Controller:
Blog\Controller\List(resolves to invalid controller class or alias: Blog\Controller\List)

Additional information:
Laminas\ServiceManager\Exception\InvalidServiceException

File:
{projectPath}/vendor/laminas/laminas-mvc/src/Controller/ControllerManager.php:{lineNumber}

Message:
Plugin of type "Blog\Controller\ListController" is invalid; must implement Laminas\Stdlib\DispatchableInterface

这是因为我们的控制器必须继承接口 DispatchableInterface 以便 laminas-mvc 分发(或者运行)。 laminas-mvc 提供了一个基本的接口实现 AbstractActionController, 供我们使用。现在我们修改我们的控制器:

// In /module/Blog/src/Blog/Controller/ListController.php:

namespace Blog\Controller;

use Laminas\Mvc\Controller\AbstractActionController;

class ListController extends AbstractActionController
{
}

让我们再次刷新站点,你将会看见如下的错误信息:

An error occurred

An error occurred during execution; please try again later.

Additional information:

Laminas\View\Exception\RuntimeException

File:
{projectPath}/vendor/laminas/laminas-view/src/Renderer/PhpRenderer.php:{lineNumber}

Message:
Laminas\View\Renderer\PhpRenderer::render: Unable to render template "blog/list/index"; resolver could not resolve to a file

现在应用告诉你不能渲染视图脚本,这是预料中的,因为我们还没有创建视图脚本文件。 应用默认的位置是 module/Blog/view/blog/list/index.phtml。 让我们创建这个文件,并且添加啊一些输出信息:

<!-- Filename: module/Blog/view/blog/list/index.phtml -->
<h1>Blog\Controller\ListController::indexAction()</h1>

在我们继续下一步之前,让我们看看这个文件所处的位置。提示信息显示的是文件不能在 /view 子目录下被找到,不是作为一个存在于 /src 目录下的 PHP 的类文件, 而是作为一个被渲染为 HTML 的模板文件。然而,这里需要做一些说明。 首先是瞎写的命名空间名称,其次是小写的控制器名称(不带 'controller' 后缀), 最后紧接的是我访问的操作名(不包含 'action' 后缀)。你可以认为他是 view/{namespace}/{controller}/{action}.phtml 这样的模板字符串。 这是一个社区的标准,但是如果你需要的话你也可以自定去定义路径。

然而单单创建这个文件是不足以完成这部分教程的。我们需要让我们的应用知晓在哪里去寻找视图文件。 我们需要在 module.config.php 配置文件中去实现这个配置。

// In module/Blog/config/module.config.php:

return [
    'controllers' => [ /** Controller Configuration */ ],
    'router'      => [ /** Route Configuration */ ]
    'view_manager' => [
        'template_path_stack' => [
            __DIR__ . '/../view',
        ],
    ],
];

上面的配置信息告诉应用 module/Blog/view/ 中存在满足 view/{namespace}/{controller}/{action}.phtml 规则的视图文件。 值得注意的是 view_manager 配置不仅仅允许你为模块配置视图文件, 还可以重写其他模块的视图文件。

刷新站点。最终我们将会看见一个没有错误信息的显示了! 你将会在视图中看见 Blog\Controller\ListController::indexAction()

祝贺你, 你不但创建一个简单的 "Hello World" 模块,同时你也知晓了一些常见的错误以及其处方法。 如果你学起来不是那么费劲的话,就跟着教程,创建一个能做真实事情的模块。

发现错误或者想为此文档做贡献? 来 GitHub 编辑!