开放的编程资料库

当前位置:我爱分享网 > Python教程 > 正文

开始编写 ZF2 模块

在今年的ZendCon期间,我们发布了ZendFramework2.0.0beta1。该版本的关键故事是创建一个新的MVC层,并且为了使故事更甜美,添加了一个模块化的应用程序架构。

“模块化?那是什么意思?”对于ZF2,“模块化”意味着您的应用程序由一个或多个“模块”构建。在我们的IRC会议期间商定的词典中,模块是解决应用程序或网站的特定原子问题的代码和其他文件的集合。

例如,考虑技术领域中的典型公司网站。你可能有:

  • 一个主页
  • 产品和其他营销页面
  • 一些论坛
  • 一个企业博客
  • 一个知识库/常见问题区域
  • 联系方式

这些可以分为离散的模块:

  • 主页、产品和营销页面的“页面”模块
  • “论坛”模块
  • “博客”模块
  • 一个“faq”或“kb”模块
  • 一个“contact”模块

此外,如果这些开发得当且独立,它们可以在不同的应用程序之间重用

那么,让我们深入了解ZF2模块吧!

什么是模块?

在ZF2中,模块只是一个命名空间目录,其下有一个Module类;不多也不少。

举个例子:

modules/
    FooBlog/
        Module.php
    FooPages/
        Module.php

上面显示了两个模块,FooBlogFooPagesModule.php文件分别包含一个Module类,按模块命名空间:FooBlog\ModuleFooPages\Module,分别。

这是模块唯一的要求;您可以从这里根据需要构建它们。但是,我们确实有一个推荐的目录结构:

modules/
    SpinDoctor/
        Module.php
        configs/
            module.config.php
        public/
            images/
            css/
                spin-doctor.css
            js/
                spin-doctor.js
        src/
            SpinDoctor/
                Controller/
                    SpinDoctorController.php
                    DiscJockeyController.php
                Form/
                    Request.php
        tests/
            bootstrap.php
            phpunit.xml
            SpinDoctor/
                Controller/
                    SpinDoctorControllerTest.php
                    DiscJockeyControllerTest.php

上面的重要部分:

  • 配置在configs目录中。
  • 公共资产,例如javascript、CSS和图像,在public
  • PHP源代码放在src目录中;该目录下的代码应遵循PSR-0标准结构。
  • 单元测试应放在tests目录中,该目录还应包含您的PHPUnit配置和引导程序。

同样,以上只是一个建议。该结构中的模块清楚地说明了每个子树的用途,使开发人员可以轻松地自省它们。

模块类

现在我们已经讨论了创建模块及其结构的最低要求,让我们讨论最低要求:Module类。

如前所述,模块类应该存在于模块的命名空间中。通常这将等同于模块的目录名称。然而,除此之外,没有真正的要求,除了构造函数不应该需要任何参数。

namespace FooBlog;

class Module
{
}

那么,模块类是做什么的呢?

模块管理器(类Zend\Module\Manager)实现三个关键目的:

  • 它聚合已启用的模块(允许您手动遍历类)。
  • 它聚合每个模块的配置。
  • 它触发模块初始化(如果有).

我将跳过第一项,直接转到配置方面。

大多数应用程序都需要某种配置。在MVC应用程序中,这可能包括路由信息,并且可能包括一些依赖注入配置。在这两种情况下,您可能不想配置任何东西,直到您拥有可用的完整配置——这意味着必须加载所有模块。

模块管理器为您做这件事。它遍历它知道的所有模块,然后将它们的配置合并到一个配置对象中。为此,它检查每个模块类的getConfig()方法。

getConfig()方法只需要返回一个arrayTraversable对象。这个数据结构应该在顶层有“环境”——你习惯于使用ZF1和Zend_Config的“production”、“staging”、“testing”和“development”键。返回后,模块管理器会将其与其主配置合并,以便您稍后可以再次获取它。

通常,您应该在配置中提供以下内容:

  • 依赖注入配置
  • 路由配置
  • 如果您有超出这些配置的特定于模块的配置,则使用特定于模块的配置。我们建议在模块名称之后命名这些键:foo_blog.apikey="..."

提供配置的最简单方法?将它定义为一个数组,并从一个PHP文件返回它——通常是您的configs/module.config.php文件。然后您的getConfig()方法可以非常简单:

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

在涵盖模块管理器目的的原始要点中,第三个要点是关于模块初始化的。一旦了解了完整的配置并引导了应用程序,您通常可能需要提供额外的初始化——这意味着路由器和定位器已准备就绪。您可能会做的一些事情示例:

  • 设置事件侦听器。通常,这些需要配置对象,因此需要访问定位器。
  • 配置插件。通常,您可能需要使用定位器管理的对象来注入插件。例如,url()视图助手需要一个已配置的路由器才能工作。

完成这些任务的方法是订阅引导程序对象的“引导程序”事件:

$events = StaticEventManager::getInstance();
$events->attach('bootstrap', 'bootstrap', array($this, 'doMoarInit'));

该事件获取应用程序和模块管理器对象作为参数,这使您可以访问您可能需要的所有内容。

问题是:我在哪里做这个?答案:如果找到,模块管理器将调用模块类的init()方法。因此,有了它,您将拥有以下内容:

namespace FooBlog;

use Zend\EventManager\StaticEventManager,
    Zend\Module\Manager as ModuleManager

class Module
{
    public function init(ModuleManager $manager)
    {
        $events = StaticEventManager::getInstance();
        $events->attach('bootstrap', 'bootstrap', array($this, 'doMoarInit'));
    }
    
    public function doMoarInit($e)
    {
        $application = $e->getParam('application');
        $modules     = $e->getParam('modules');
        
        $locator = $application->getLocator();
        $router  = $application->getRouter();
        $config  = $modules->getMergedConfig();
        
        // do something with the above!
    }
}

如您所见,当引导事件被触发时,您可以访问Zend\Mvc\Application实例以及Zend\Module\Manager实例,让您可以访问您配置的定位器和路由器,以及来自所有模块的合并配置!基本上,您可能想要访问的所有内容都触手可及。

init()期间您还想做什么?一件非常非常重要的事情:为模块中的PHP类设置自动加载!

ZF2提供多种不同的自动加载器,以提供不同的策略,以简化开发和生产速度。对于beta1,对它们进行了轻微重构以使其更加有用。主要的变化是对AutoloaderFactory,以允许它保留每个autoloaderithandles的单个实例,从而允许为每个指定额外的配置。因此,这意味着如果您使用AutoloaderFactory,您将只有一个ClassMapAutoloaderStandardAutoloader的实例——这意味着每个模块可以简单地添加到他们的配置中。

因此,这是一个典型的自动加载样板文件:

namespace FooBlog;

use Zend\EventManager\StaticEventManager,
    Zend\Loader\AutoloaderFactory,
    Zend\Module\Manager as ModuleManager

class Module
{
    public function init(ModuleManager $manager)
    {
        $this->initializeAutoloader();
        // ...
    }
    
    public function initializeAutoloader()
    {
        AutoloaderFactory::factory(array(
            'Zend\Loader\ClassMapAutoloader' => array(
                include __DIR__ .  '/autoload_classmap.php',
            ),
            'Zend\Loader\StandardAutoloader' => array(
                'namespaces' => array(
                    __NAMESPACE__ => __DIR__ . '/src/' .  __NAMESPACE__,
                ),
            ),
        ));
    }

在开发过程中,您可以让autoload_classmap.php返回一个空数组,但是在生产过程中,您可以根据模块中的类生成它。通过使用StandardAutoloader,您可以在更新类映射之前拥有一个备份解决方案。

现在您知道了您的模块如何提供配置,以及它如何与引导程序相关联,我终于可以涵盖原点:模块管理器聚合启用的模块。这允许模块“选择加入”应用程序的附加功能。例如,您可以使模块“感知ACL”,并让“安全”模块获取特定于模块的ACL:

   public function initializeAcls($e)
   {
       $this->acl = new Acl;
       $modules   = $e->getParam('modules');
       foreach ($modules->getLoadedModules() as $module) {
           if (!method_exists($module, 'getAcl')) {
               continue;
           }
           $this->processModuleAcl($module->getAcl());
       }
   }

这是一项非常强大的技术,我相信我们将来会看到它的许多创造性用途!

将模块组合到您的应用程序中

那么,编写模块应该很容易,对吧?对吧?!?!?

那么,另一个技巧是将您的模块告诉模块管理器。我使用诸如“启用的模块”“它[模块管理器]知道的模块”之类的短语是有原因的:模块管理器是选择加入的。您必须告诉它将加载哪些模块。

有人可能会说,“为什么?这不是反对快速应用开发吗?”嗯,是和不是。考虑一下:如果您发现模块中存在安全问题怎么办?当然,您可以将其从存储库中完全删除。或者您可以简单地更新模块管理器配置,使其不加载它,然后开始测试并就地修补它;完成后,您需要做的就是重新启用它。

加载模块是一个两阶段的过程。首先,系统需要知道在哪里以及如何定位模块类。其次,它需要实际加载它们。我们有两个组件围绕着它:

  • Zend\Loader\ModuleAutoloader
  • Zend\Module\Manager

ModuleAutoloader获取路径列表或模块名称与路径的关联,并使用该信息来解析Module类。通常,模块将位于单个目录下,配置就像这样简单:

$loader = new Zend\Loader\ModuleAutoloader(array(
    __DIR__ . '/../modules',
));
$loader->register();

您可以指定多个路径,或明确的模块:目录对:

$loader = new Zend\Loader\ModuleAutoloader(array(
    __DIR__ . '/../vendors',
    __DIR__ . '/../modules',
    'User' => __DIR__ . '/../vendors/EdpUser-0.1.0',
));
$loader->register();

在上面,最后一个将在文件vendors/EdpUser-0.1.0/Module.php中寻找一个User\Module类,但期望模块在指定的其他两个目录中找到的目录名称和模块命名空间之间始终具有1:1的相关性。

一旦您的ModuleAutoloader就位,您就可以调用模块管理器,并通知它应该加载哪些模块。假设我们有以下模块:

modules/
    Application/
        Module.php
    Security/
        Module.php
vendors/
    FooBlog/
        Module.php
    SpinDoctor/
        Module.php

并且我们想要加载ApplicationSecurityFooBlog模块。我们还假设我们已经配置了ModuleAutoloader已经正确了。然后我们可以这样做:

$manager = new Zend\Module\Manager(array(
    'Application',
    'Security',
    'FooBlog',
));
$manager->loadModules();

我们完成了!如果此时您要进行一些分析和反省,您会发现“SpinDoctor”模块不会被表示——只有我们配置的那些模块。

为了简化故事并减少样板代码,ZendSkeletonApplication存储库在public/index.php中为您提供了一个基本的引导程序。此文件使用configs/application.config.php,您在其中指定两个键,module_pathsmodules

return array(
    'module_paths' => array(
        realpath(__DIR__ . '/../modules'),
        realpath(__DIR__ . '/../vendors'),
    ),
    'modules' => array(
        'Application',
        'Security',
        'FooBlog',
    ),
);

此时并没有变得更简单。

提示与技巧

我学到的一个技巧涉及如何以及何时加载模块。在上一节中,我介绍了模块管理器以及它如何获知我们在此应用程序中组合了哪些模块。一件有趣的事情是,模块按照它们在您的配置中提供的顺序进行处理。这意味着配置也按该顺序合并。

技巧是这样的:如果你想覆盖配置设置,不要在模块中做;创建一个最后加载的特殊模块来执行此操作!

所以,考虑这个模块类:

namespace Local;

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

然后我们在configs/module.config.php中创建一个配置文件,并在其中指定我们想要的任何配置覆盖!

return array(
    'production' => array(
        'di' => 'alias' => array(
            'view' => 'My\Custom\Renderer',
        ),
    ),
);

然后,在我们的configs/application.config.php中,我们只需将此模块启用为我们列表中的最后一个:

return array(
    // ...
    'modules' => array(
        'Application',
        'Security',
        'FooBlog',
        'Local',
    ),
);

完成!

ZF2中的模块非常灵活且功能强大。我什至没有介绍其中的一些特性——例如使用phar文件(或任何格式的phar支持)作为模块的能力,或者缓存模块配置的能力等。不过,希望我已经为您概述了它们的简单性,这样您就可以开始为自己利用它们的力量了!

免责声明

ZF2目前处于beta阶段,ZendFramework不保证beta版本之间的BC。如果您选择在ZF2上进行测试或构建,请注意您可能需要在不同版本之间进行更改。也就是说,请进行测试,并提供您的反馈!

更新

  • 2011-11-0714:30CST:更新配置FooBlog.apikey以读取foo_blog.apikey,根据ZF2配置命名标准
未经允许不得转载:我爱分享网 » 开始编写 ZF2 模块

感觉很棒!可以赞赏支持我哟~

赞(0) 打赏