可重用 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
子命名空间中。
Doctrine 实体/文档
如果 bundle 包含 Doctrine ORM 实体和/或 ODM 文档,建议使用存储在 config/doctrine/
中的 XML 文件定义其映射。这允许使用标准 Symfony 机制来覆盖 bundle 部分来覆盖该映射。当使用属性定义映射时,这是不可能的。
测试
bundle 应附带使用 PHPUnit 编写并存储在 tests/
目录下的测试套件。测试应遵循以下原则
- 测试套件必须可以通过从示例应用程序运行的简单
phpunit
命令执行; - 功能测试应仅用于测试响应输出以及您拥有的一些分析信息;
- 测试应至少覆盖 95% 的代码库。
注意
测试套件不得包含 AllTests.php
脚本,但必须依赖 phpunit.xml.dist
文件的存在。
持续集成
持续测试 bundle 代码,包括其所有提交和拉取请求,是一种称为持续集成的良好实践。有几个服务为开源项目免费提供此功能,例如 GitHub Actions 和 Travis CI。
bundle 至少应测试
- 其依赖项的下限(通过运行
composer update --prefer-lowest
); - 支持的 PHP 版本;
- 所有支持的主要 Symfony 版本(例如,如果声明同时支持
4.x
和5.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.rst
或 docs/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 别名作为前缀,而不是像在项目服务中那样使用完全限定的类名。例如,AcmeBlogBundle 服务必须以 acme_blog
为前缀。原因是 bundle 不应依赖诸如服务自动装配或自动配置之类的功能,以避免在编译应用程序服务时产生开销。
此外,不打算直接供应用程序使用的服务应定义为私有。对于公共服务,应从接口/类到服务 ID 创建别名。例如,在 MonologBundle 中,从 Psr\Log\LoggerInterface
到 logger
创建了一个别名,以便 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 包的官方仓库。