跳到内容

可重用 Bundle 的最佳实践

编辑此页

本文全部关于如何构建您的可重用 bundle,使其可配置和可扩展。可重用 bundle 是那些旨在在多个公司项目之间私下共享或公开共享,以便任何 Symfony 项目都可以安装它们的 bundle。

Bundle 名称

Bundle 也是一个 PHP 命名空间。命名空间必须遵循 PSR-4 PHP 命名空间和类名的互操作性标准:它以供应商段开头,后跟零个或多个类别段,并以命名空间短名称结尾,该名称必须以 Bundle 结尾。

只要您向命名空间添加“bundle 类”(即扩展 Bundle 的类),命名空间就变成了一个 bundle。bundle 类名必须遵循以下规则

  • 仅使用字母数字字符和下划线;
  • 使用 StudlyCaps 名称(即首字母大写的 camelCase);
  • 使用描述性且简短的名称(不超过两个单词);
  • 使用供应商(以及可选的类别命名空间)的串联作为名称的前缀;
  • 在名称后添加 Bundle 后缀。

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

命名空间 Bundle 类名
Acme\Bundle\BlogBundle AcmeBlogBundle
Acme\BlogBundle AcmeBlogBundle

按照惯例,bundle 类的 getName() 方法应返回类名。

注意

如果您公开共享您的 bundle,则必须使用 bundle 类名作为存储库的名称(例如 AcmeBlogBundle 而不是 BlogBundle)。

注意

Symfony 核心 Bundle 不以 Symfony 作为 Bundle 类的前缀,并且始终添加 Bundle 子命名空间;例如:FrameworkBundle

每个 bundle 都有一个别名,它是 bundle 名称的小写短版本,使用下划线 (AcmeBlogBundle 的 acme_blog)。此别名用于强制项目中的唯一性以及定义 bundle 的配置选项(有关一些用法示例,请参见下文)。

目录结构

以下是 AcmeBlogBundle 的推荐目录结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<your-bundle>/
├── assets/
├── config/
├── docs/
│   └─ index.md
├── public/
├── src/
│   ├── Controller/
│   ├── DependencyInjection/
│   └── AcmeBlogBundle.php
├── templates/
├── tests/
├── translations/
├── LICENSE
└── README.md

注意

当您的 bundle 类扩展推荐的 AbstractBundle 时,默认使用此目录结构。如果您的 bundle 扩展了 Bundle 类,则必须按如下方式覆盖 getPath() 方法

1
2
3
4
5
6
7
8
9
use Symfony\Component\HttpKernel\Bundle\Bundle;

class AcmeBlogBundle extends Bundle
{
    public function getPath(): string
    {
        return \dirname(__DIR__);
    }
}

以下文件是强制性的,因为它们确保了自动化工具可以依赖的结构约定

  • src/AcmeBlogBundle.php:这是将普通目录转换为 Symfony bundle 的类(将其更改为您的 bundle 的名称);
  • README.md:此文件包含 bundle 的基本描述,通常显示一些基本示例和指向其完整文档的链接(它可以使用 GitHub 支持的任何标记格式,例如 README.rst);
  • LICENSE:代码使用的许可证的完整内容。大多数第三方 bundle 都在 MIT 许可证下发布,但您可以选择任何许可证
  • docs/index.md:Bundle 文档的根文件。

对于最常用的类和文件,子目录的深度应保持在最低限度。最多两个级别。

bundle 目录是只读的。如果您需要写入临时文件,请将其存储在宿主应用程序的 cache/log/ 目录下。工具可以在 bundle 目录结构中生成文件,但前提是生成的文件将成为存储库的一部分。

以下类和文件具有特定的位置(有些是强制性的,有些只是大多数开发人员遵循的约定)

类型 目录
命令 src/Command/
控制器 src/Controller/
服务容器扩展 src/DependencyInjection/
Doctrine ORM 实体 src/Entity/
Doctrine ODM 文档 src/Document/
事件监听器 src/EventListener/
配置(路由、服务等) config/
Web 资源(编译后的 CSS 和 JS,图像) public/
Web 资源来源(.scss.ts、Stimulus) assets/
翻译文件 translations/
验证(当不使用属性时) config/validation/
序列化(当不使用属性时) config/serialization/
模板 templates/
单元和功能测试 tests/

bundle 目录结构用作命名空间层次结构。例如,存储在 src/Controller/ContentController.php 中的 ContentController 控制器的完全限定类名将为 Acme\BlogBundle\Controller\ContentController

所有类和文件都必须遵循 Symfony 编码标准

一些类应被视为外观,并且应尽可能简短,例如命令、助手、监听器和控制器。

连接到事件调度器的类应以 Listener 为后缀。

异常类应存储在 Exception 子命名空间中。

供应商

bundle 不得嵌入第三方 PHP 库。它应依赖标准的 Symfony 自动加载。

bundle 也不应嵌入用 JavaScript、CSS 或任何其他语言编写的第三方库。

Doctrine 实体/文档

如果 bundle 包含 Doctrine ORM 实体和/或 ODM 文档,建议使用存储在 config/doctrine/ 中的 XML 文件定义其映射。这允许使用标准 Symfony 机制来覆盖 bundle 部分来覆盖该映射。当使用属性定义映射时,这是不可能的。

测试

bundle 应附带使用 PHPUnit 编写并存储在 tests/ 目录下的测试套件。测试应遵循以下原则

  • 测试套件必须可以通过从示例应用程序运行的简单 phpunit 命令执行;
  • 功能测试应仅用于测试响应输出以及您拥有的一些分析信息;
  • 测试应至少覆盖 95% 的代码库。

注意

测试套件不得包含 AllTests.php 脚本,但必须依赖 phpunit.xml.dist 文件的存在。

持续集成

持续测试 bundle 代码,包括其所有提交和拉取请求,是一种称为持续集成的良好实践。有几个服务为开源项目免费提供此功能,例如 GitHub ActionsTravis CI

bundle 至少应测试

  • 其依赖项的下限(通过运行 composer update --prefer-lowest);
  • 支持的 PHP 版本;
  • 所有支持的主要 Symfony 版本(例如,如果声明同时支持 4.x5.x)。

因此,支持 PHP 7.3、7.4 和 8.0 以及 Symfony 4.4 和 5.x 的 bundle 应至少具有以下测试矩阵

PHP 版本 Symfony 版本 Composer 标志
7.3 4.* --prefer-lowest
7.4 5.*  
8.0 5.*  

提示

测试应在将 SYMFONY_DEPRECATIONS_HELPER 环境变量设置为 max[direct]=0 的情况下运行。这确保 bundle 中没有代码直接使用已弃用的功能。

最低依赖项测试可以使用设置为 disabled=1 的此变量运行。

需要特定的 Symfony 版本

您可以将特殊的 SYMFONY_REQUIRE 环境变量与 Symfony Flex 一起使用以安装特定的 Symfony 版本

1
2
3
4
5
6
7
8
9
10
11
12
# this requires Symfony 5.x for all Symfony packages
export SYMFONY_REQUIRE=5.*
# alternatively you can run this command to update composer.json config
# composer config extra.symfony.require "5.*"

# install Symfony Flex in the CI environment
composer global config --no-plugins allow-plugins.symfony/flex true
composer global require --no-progress --no-scripts --no-plugins symfony/flex

# install the dependencies (using --prefer-dist and --no-progress is
# recommended to have a better output and faster download time)
composer update --prefer-dist --no-progress

警告

如果您想缓存您的 Composer 依赖项,请勿缓存 vendor/ 目录,因为这会产生副作用。而是缓存 $HOME/.composer/cache/files

安装

Bundle 应在其 composer.json 文件中设置 "type": "symfony-bundle"。这样,Symfony Flex 将能够在安装 bundle 时自动启用它。

如果您的 bundle 需要任何设置(例如配置、新文件、更改 .gitignore 等),则应创建 Symfony Flex recipe

文档

所有类和函数都必须附带完整的 PHPDoc。

广泛的文档也应在 docs/ 目录中提供。索引文件(例如 docs/index.rstdocs/index.md)是唯一强制性的文件,并且必须是文档的入口点。reStructuredText (rST) 是用于在 Symfony 网站上呈现文档的格式。

安装说明

为了简化第三方 bundle 的安装,请考虑在您的 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
Installation
============

Make sure Composer is installed globally, as explained in the
[installation chapter](https://getcomposer.org.cn/doc/00-intro.md)
of the Composer documentation.

Applications that use Symfony Flex
----------------------------------

Open a command console, enter your project directory and execute:

```console
composer require <package-name>
```

Applications that don't use Symfony Flex
----------------------------------------

### 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>
```

### Step 2: Enable the Bundle

Then, enable the bundle by adding it to the list of registered bundles
in the `config/bundles.php` file of your project:

```php
// config/bundles.php

return [
    // ...
    <vendor>\<bundle-name>\<bundle-long-name>::class => ['all' => true],
];
```

上面的示例假定您正在安装 bundle 的最新稳定版本,在该版本中您不必提供包版本号(例如 composer require friendsofsymfony/user-bundle)。如果安装说明引用了某些过去的 bundle 版本或某些不稳定版本,请包含版本约束(例如 composer require friendsofsymfony/user-bundle "~2.0@dev")。

您可以选择添加更多安装步骤(步骤 3步骤 4 等)来解释其他所需的安装任务,例如注册路由或转储资源。

路由

如果 bundle 提供路由,则路由必须以 bundle 别名作为前缀。例如,如果您的 bundle 名为 AcmeBlogBundle,则其所有路由都必须以 acme_blog_ 为前缀。

模板

如果 bundle 提供模板,则必须使用 Twig。bundle 不得提供主布局,除非它提供完整的工作应用程序。

翻译文件

如果 bundle 提供消息翻译,则必须以 XLIFF 格式定义它们;域应以 bundle 名称 (acme_blog) 命名。

bundle 不得覆盖来自另一个 bundle 的现有消息。

配置

为了提供更大的灵活性,bundle 可以使用 Symfony 内置机制提供可配置的设置。

对于简单的配置设置,请依赖 Symfony 配置的默认 parameters 条目。Symfony 参数是简单的键/值对;值可以是任何有效的 PHP 值。每个参数名称都应以 bundle 别名开头,但这只是最佳实践建议。参数名称的其余部分将使用句点 (.) 分隔不同的部分(例如 acme_blog.author.email)。

最终用户可以在任何配置文件中提供值

1
2
3
# config/services.yaml
parameters:
    acme_blog.author.email: '[email protected]'

从容器中检索代码中的配置参数

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

虽然此机制需要最少的努力,但您应考虑使用更高级的语义 bundle 配置,以使您的配置更加健壮。

版本控制

Bundle 必须遵循 语义版本控制标准进行版本控制。

服务

如果 bundle 定义了服务,则它们必须以 bundle 别名作为前缀,而不是像在项目服务中那样使用完全限定的类名。例如,AcmeBlogBundle 服务必须以 acme_blog 为前缀。原因是 bundle 不应依赖诸如服务自动装配或自动配置之类的功能,以避免在编译应用程序服务时产生开销。

此外,不打算直接供应用程序使用的服务应定义为私有。对于公共服务,应从接口/类到服务 ID 创建别名。例如,在 MonologBundle 中,从 Psr\Log\LoggerInterfacelogger 创建了一个别名,以便 LoggerInterface 类型提示可以用于自动装配。

服务不应使用自动装配或自动配置。相反,所有服务都应显式定义。

提示

如果最终用户不打算使用服务 ID,则可以通过在服务 ID 前面添加点 (例如 .acme_blog.logger) 将其标记为隐藏。这可以防止该服务列在默认的 debug:container 命令输出中。

另请参阅

您可以在本文中了解有关 bundle 中服务加载的更多信息:如何在 Bundle 中加载服务配置

Composer 元数据

composer.json 文件应至少包含以下元数据

name
由供应商和短 bundle 名称组成。如果您代表自己而不是代表公司发布 bundle,请使用您的个人姓名(例如 johnsmith/blog-bundle)。从 bundle 短名称中排除供应商名称,并用连字符分隔每个单词。例如:AcmeBlogBundle 转换为 blog-bundle,AcmeSocialConnectBundle 转换为 social-connect-bundle
description
bundle 用途的简要说明。
type
使用 symfony-bundle 值。
license
带有有效许可证标识符的字符串(或字符串数组),例如 MIT
autoload

此信息供 Symfony 用于加载 bundle 的类。建议使用 PSR-4 自动加载标准:使用命名空间作为键,bundle 主类的位置(相对于 composer.json)作为值。由于主类位于 bundle 的 src/ 目录中

1
2
3
4
5
6
7
8
9
10
11
12
{
    "autoload": {
        "psr-4": {
            "Acme\\BlogBundle\\": "src/"
        }
    },
    "autoload-dev": {
        "psr-4": {
            "Acme\\BlogBundle\\Tests\\": "tests/"
        }
    }
}

为了方便开发者找到你的程序包,请将其注册到 Packagist,Composer 包的官方仓库。

资源

如果 bundle 引用了任何资源(配置文件、翻译文件等),你可以使用物理路径(例如 __DIR__/config/services.xml)。

过去,我们建议仅使用逻辑路径(例如 @AcmeBlogBundle/config/services.xml)并使用 Symfony 内核提供的 资源定位器 来解析它们,但这已不再是推荐的做法。

本作品,包括代码示例,根据 Creative Commons BY-SA 3.0 许可获得许可。
目录
    版本