可复用Bundle的最佳实践

3.4 版本
维护中的版本

有两种类型的bundles:

  • 特定程序的bundles:仅用于构建你自己的程序;

  • 可复用的bundles:意味着可以分享到多个项目中。

本文讲的全部是关于如何构建可复用bundles,以便它们能够被配置和扩展。许多推荐做法并不适用于程序专用bundles,因为你希望程序bundle尽可能简单才好。程序专用bundle只需遵循“指南”中的内容就好。

程序专用bundle的最佳实践,在Symfony框架最佳实践中进行了讨论。

Bundle命名 

一个bundle同时也是一个PHP命名空间。这个命名空间,必须遵循为PHP命名空间和类名所制定的PSR-0PSR-4互用性标准:它应起于一个vendor segment即“开发商”区段,跟着的是零或多个“类别”区段,结束于一个“必须以Bundle后缀结尾”的短名。

一个命名空间在你添加了bundle类之后将立即成为bundle。budnle的类名受以下规则约束:

  • 只能使用字母和下划线;

  • 使用驼峰命名法;

  • 使用描述性的短名称(不能超过两个单词);

  • 类名前缀可以是开发商(和可选的类别命名)的连接组合;

  • 使用Bundle作为类名后缀。

以下是有效的bundle命名空间和类名:

Namespace(bundle的命名空间) Bundle Class Name(bundle的类名称)
Acme\Bundle\BlogBundle AcmeBlogBundle
Acme\BlogBundle AcmeBlogBundle

根据约定,bundle类的getName()方法必须返回类名。

如果你公开发布自己的bundle,你必须使用bundle类名作为repository的名字(比如,是AcmeBlogBundle而不是BlogBundle)。

Symfony核心bundles不在Bundle类的名称中添加Symfony前缀,但是有Bundle子命名空间。例如:FrameworkBundle

每个bundle都有一个假名,是类bundle名称使用下划线的小写短版(AcmeBlogBundle就是acme_blog)。假名用于在项目内(使bundle)强制唯一,同时还用于定义bundle的配置选项(参见下文中示例)。

目录结构 

AcmeBlogBundle的基本目录结构必须像下面这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
<your-bundle>/
├─ AcmeBlogBundle.php
├─ Controller/
├─ README.md
├─ LICENSE
├─ Resources/
│   ├─ config/
│   ├─ doc/
│   │  └─ index.rst
│   ├─ translations/
│   ├─ views/
│   └─ public/
└─ Tests/

下列文件是必须的,因为它们可以确保(满足)结构上的约定以便(框架底层的)自动工具可以依赖:

  • AcmeBlogBundle.php:这就是可以把平面目录转换成一个Symfony bundle(可改为你的bundle名称)的类;

  • README.md:这个文件包含了bundle的基本描述信息,通常用于显示基本用法并且链接到完整文档(它可以使用任何受到GitHub支持的markup格式,比如README.rst);

  • LICENSE:代码所使用的授权之完整内容。多数三方bundles使用的是MIT license,但你也可以选择任何一个license

  • Resources/doc/index.rst:Bundle文档中的根文件。

对于经常被用到的类和文件来说,子目录的层数应尽可能保持在最低的程度(两级是最大了)。

bundle的目录是只读的。如果你需要写入临时文件,把它们存放到程序级别的cache/log/目录下。使用工具可以生成bundle目录结构中的文件,但只有那些会成为repository一部分的文件才会被生成。

下面这些类和文件有特殊的位置(其中一些是必须的,另外一些则只是多数开发者所遵循的约定):

类型 目录 必须?
Commands Command/
Controllers Controller/
Service Container Extensions DependencyInjection/
Event Listeners EventListener/
Model classes [1] Model/
Configuration Resources/config/
Web Resources (CSS, JS, images) Resources/public/
Translation files Resources/translations/
Templates Resources/views/
Unit and Functional Tests Tests/

[1] 参考如何为多个Doctrine实现提供Model类以了解如何使用compiler pass来操作映射。

类 

bundle的目录结构会被作为命名空间层级(hierarchy)来使用。例如,一个ContentController控制器就是存放在Acme/BlogBundle/Controller/ContentController.php之中并且其FQCN类名是Acme\BlogBundle\Controller\ContentController

bundle中所有的类和文件,必须遵循Symfony coding standardsSymfony编码标准。

一些作为主力被看到的类,类名应尽可能短,像是Commands(命令行)、Helpers(助手)、Listeners(监听)以及Controllers(控制器)这些。

连接EventDispatcher的类应该加上Listener后缀。

异常类(Exception classes)应该被存放在Exception子命名空间下。

开发商 

一个bundle一定不能嵌入第三方PHP类库,而应当依靠Symfony的自动加载。

一个bundle一定不能嵌入第三方Javascript、CSS或其他语言的类库。

测试 

每个bundle都应该自带一个用PHPUnit写就的测试包(test suite),并存放在Test\目录下。测试应遵循以下原则:

  • 测试包要能在一个样例程序中通过phpunit命令来执行;

  • 功能测试应当仅用于测试响应输出,以及一些可能存在的分析信息(profiling infomation)。

  • 测试应当覆盖至少95%的基础代码范围。

测试包一定不能包含AllTest.php脚本,而是必须依靠一个已经存在的phpunit.xml.dist文件。

文档 

所有的类和方法必须自带完整的PHPDoc。

可扩展的文档应该使用reStructuredText格式来提供,放在Resources/doc/目录下;Resources/doc/index.rst文件必须存在,而且它是文档的入口点(entry point)。

安装介绍 

为了能轻松安装第三方bundles,考虑在你的README.md文件中使用下列标准格式的简介。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
Installation
============
 
Step 1: Download the Bundle
---------------------------
 
Open a command console, enter your project directory and execute the
following command to download the latest stable version of this bundle:
 
```console
$ composer require <package-name> "~1"
```
 
This command requires you to have Composer installed globally, as explained
in the [installation chapter](https://getcomposer.org/doc/00-intro.md)
of the Composer documentation.
 
Step 2: Enable the Bundle
---------------------------
 
Then, enable the bundle by adding it to the list of registered bundles
in the `app/AppKernel.php` file of your project:
 
```php
<?php
// app/AppKernel.php
 
// ...
class AppKernel extends Kernel
{
    public function registerBundles()
    {
        $bundles = array(
            // ...
 
            new <vendor>\<bundle-name>\<bundle-long-name>(),
        );
 
        // ...
    }
 
    // ...
}
```
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
Installation
============
 
Step 1: Download the Bundle
---------------------------
 
Open a command console, enter your project directory and execute the
following command to download the latest stable version of this bundle:
 
.. code-block:: terminal
 
    $ composer require <package-name> "~1"
 
This command requires you to have Composer installed globally, as explained
in the `installation chapter`_ of the Composer documentation.
 
Step 2: Enable the Bundle
---------------------------
 
Then, enable the bundle by adding it to the list of registered bundles
in the ``app/AppKernel.php`` file of your project:
 
.. code-block:: php
 
    <?php
    // app/AppKernel.php
 
    // ...
    class AppKernel extends Kernel
    {
        public function registerBundles()
        {
            $bundles = array(
                // ...
 
                new <vendor>\<bundle-name>\<bundle-long-name>(),
            );
 
            // ...
        }
 
        // ...
    }
 
.. _`installation chapter`: https://getcomposer.org/doc/00-intro.md

这个例子假设你正在安装bundle的最新稳定版,此时你毋须提供package的版本号(比如 composer require friendsofsymfony/user-bundle)。如果安装简介里引用了一些过去的bundle版本,或者是一些不稳定版本,这时应当把版本约束包容进来(composer require friendsofsymfony/user-bundle "~2.0@dev")。

可选地,你可以添加更多的安装步骤(Step 3, Step 4 等等)以便解释其他所需之安装任务,比如注册路由,或是剥离出资源(dumping assets)。

路由 

如果bundle提供了路由,它们必须使用bundle的假名作为前缀。例如,如果你的bundle叫AcmeBlogBundle,则所有它的路由必须添加acme_blog_前缀。

模板 

如果bundle提供了模板,它们必须使用Twig。一个bundle一定不能提供一个主要布局,除非该bundle提供的是一套全功能程序。

翻译文件 

如果bundle提供了对信息的翻译,翻译文件必须定义为XLIFF格式;翻译域(translation domain)则应该放在bundle名称之后(acme_blog)。

一个bundle一定不能对其他bundle的已有翻译信息进行覆写。

配置 

要提供更大的灵活性,bundle可以通过使用Symfony内置的架构来给出可配置的选项。

对于简单的配置项,可以使用Symfony配置体系中的默认parameters参数键。Symfony参数是简单的键/值对;值可以是任何PHP有效值。每个参数的名称,应该起于所属bundle的假名,虽然这只是最佳实践所给出的建议(译注:并不强制)。参数名中的其他部分可以使用英文句号(.)来分隔不同的部分(比如acme_blog.author.email)。

末级用户可以在任何一种配置文件中提供所需的值:

1
2
3
# app/config/config.yml
parameters:
    acme_blog.author.email: 'fabien@example.com'
1
2
3
4
<!-- app/config/config.xml -->
<parameters>
    <parameter key="acme_blog.author.email">fabien@example.com</parameter>
</parameters>
1
2
// app/config/config.php
$container->setParameter('acme_blog.author.email', 'fabien@example.com');

在你的控制器里通过容器取出参数的值:

1
$container->getParameter('acme_blog.author.email');

尽管这种架构极其简单,你应该考虑使用更加先进的语义化bundle配置

版本 

Bundles必须按照Semantic Versioning Standard实施语义化版本。

服务 

如果在bundle中定义了服务,它们必须以bundle假名作为前缀。例如,AcmeBlogBundle的服务要以acme_blog当作前缀。

此外,服务即意味着被程序直接使用,应当定义成private

你可以了解更多关于服务在bundle中被加载的细节,参考如何在Bundle内加载服务配置

Composer元数据 

composer.json文件中应当包括如下metadata(元数据):

name
它是vendor开发商和bundle短名的组合。如果你是自己发布bundle而不是代表公司,使用你自己的名字(比如johnsmith/blog-bundle)。bundle短名中剔除了vendor名,并使用中杠(hyphen)来划分单词。例如:AcmeBlogBundle被转换为blog-bundle,而AcmeSocialConnectBundle被转换为social-connect-bundle
description
对bundle功能的一个简短解释。
type
使用symfony-bundle这个值。
license
MIT是Symfony bundle的首选授权方式,但你也可以使用其他的license。
autoload
这个信息在Symfony加载bundle中的类时被用到。PSR-4自动加载标准被现代bundles所推荐,但是PSR-0标准同样被支持。

为了让其他开发者易于找到你的bundle,把它注册到Packagist,这是Composer包的官方宝库。

本文,包括例程代码在内,采用的是 Creative Commons BY-SA 3.0 创作共用授权。

登录symfonychina 发表评论或留下问题(我们会尽量回复)