跳到内容

Symfony框架最佳实践

编辑此页面

本文描述了使用 Symfony 开发 Web 应用程序的最佳实践,这些实践符合原始 Symfony 创建者设想的理念。

如果您不同意其中一些建议,它们可能是一个很好的起点,您可以扩展并使其适应您的特定需求。您甚至可以完全忽略它们,并继续使用您自己的最佳实践和方法。Symfony 足够灵活,可以适应您的需求。

本文假设您已经有开发 Symfony 应用程序的经验。如果您没有,请先阅读文档的入门部分。

提示

Symfony 提供了一个名为 Symfony Demo 的示例应用程序,该应用程序遵循所有这些最佳实践,因此您可以在实践中体验它们。

创建项目

使用 Symfony Binary 创建 Symfony 应用程序

Symfony binary 是在您下载 Symfony 时在您的机器上创建的可执行命令。它提供了多种实用程序,包括创建新的 Symfony 应用程序的最简单方法

1
$ symfony new my_project_directory

在底层,此 Symfony binary 命令执行所需的 Composer 命令,以基于当前稳定版本创建一个新的 Symfony 应用程序

使用默认目录结构

除非您的项目遵循强制使用特定目录结构的开发实践,否则请遵循默认的 Symfony 目录结构。它扁平化、不言自明,并且不与 Symfony 耦合

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
your_project/
├─ assets/
├─ bin/
│  └─ console
├─ config/
│  ├─ packages/
│  ├─ routes/
│  └─ services.yaml
├─ migrations/
├─ public/
│  ├─ build/
│  └─ index.php
├─ src/
│  ├─ Kernel.php
│  ├─ Command/
│  ├─ Controller/
│  ├─ DataFixtures/
│  ├─ Entity/
│  ├─ EventSubscriber/
│  ├─ Form/
│  ├─ Repository/
│  ├─ Security/
│  └─ Twig/
├─ templates/
├─ tests/
├─ translations/
├─ var/
│  ├─ cache/
│  └─ log/
└─ vendor/

配置

使用环境变量进行基础设施配置

这些选项的值在一台机器与另一台机器之间发生变化(例如,从您的开发机器到生产服务器),但它们不会修改应用程序的行为。

在您的项目中使用 env vars 定义这些选项,并创建多个 .env 文件来为每个环境配置 env vars

使用密钥存储敏感信息

当您的应用程序具有敏感配置(如 API 密钥)时,您应该通过 Symfony 的密钥管理系统安全地存储这些配置。

使用参数进行应用配置

这些是用于修改应用程序行为的选项,例如电子邮件通知的发送者,或启用的 功能开关。它们的值不会因机器而异,因此不要将它们定义为环境变量。

config/services.yaml 文件中将这些选项定义为参数。您可以在 config/services_dev.yamlconfig/services_prod.yaml 文件中为每个环境覆盖这些选项。

除非应用程序配置被多次重用并且需要严格的验证,否则不要使用 Config 组件来定义选项。

使用简短且带前缀的参数名称

考虑使用 app. 作为您的参数的前缀,以避免与 Symfony 和第三方 bundle/库参数发生冲突。然后,仅使用一两个词来描述参数的目的

1
2
3
4
5
6
7
8
9
10
# config/services.yaml
parameters:
    # don't do this: 'dir' is too generic, and it doesn't convey any meaning
    app.dir: '...'
    # do this: short but easy to understand names
    app.contents_dir: '...'
    # it's OK to use dots, underscores, dashes or nothing, but always
    # be consistent and use the same format for all the parameters
    app.dir.contents: '...'
    app.contents-dir: '...'

使用常量定义极少更改的选项

诸如在某些列表中要显示的条目数之类的配置选项很少更改。与其将它们定义为配置参数,不如在相关类中将它们定义为 PHP 常量。示例

1
2
3
4
5
6
7
8
9
// src/Entity/Post.php
namespace App\Entity;

class Post
{
    public const NUMBER_OF_ITEMS = 10;

    // ...
}

常量的主要优点是您可以在任何地方使用它们,包括 Twig 模板和 Doctrine 实体,而参数仅可从可以访问服务容器的位置访问。

对于此类配置值,使用常量的唯一显着缺点是,在测试中重新定义它们的值很复杂。

业务逻辑

不要创建任何 Bundle 来组织您的应用程序逻辑

当 Symfony 2.0 发布时,应用程序使用 bundles 将其代码划分为逻辑功能:UserBundle、ProductBundle、InvoiceBundle 等。但是,bundle 旨在成为可以作为独立软件重用的东西。

如果您需要在您的项目中重用某些功能,请为其创建一个 bundle(在私有仓库中,不要使其公开可用)。对于您的应用程序代码的其余部分,请使用 PHP 命名空间来组织代码,而不是 bundle。

使用自动连线来自动化配置应用程序服务

服务自动连线是一项功能,它可以读取您构造函数(或其他方法)上的类型提示,并自动将正确的服务传递给每个方法,从而无需显式配置服务并简化应用程序维护。

将其与服务自动配置结合使用,还可以将 服务标签添加到需要它们的那些服务,例如 Twig 扩展、事件订阅者等。

服务应尽可能设为私有

将服务设为私有,以防止您通过 $container->get() 访问这些服务。相反,您将需要使用适当的依赖注入。

使用 YAML 格式配置您自己的服务

如果您使用默认的 services.yaml 配置,则大多数服务将自动配置。但是,在某些边缘情况下,您需要手动配置服务(或其中的一部分)。

YAML 是推荐的配置服务格式,因为它对新手友好且简洁,但 Symfony 也支持 XML 和 PHP 配置。

使用属性定义 Doctrine 实体映射

Doctrine 实体是您存储在某个“数据库”中的普通 PHP 对象。Doctrine 仅通过为您模型类配置的映射元数据来了解您的实体。

Doctrine 支持多种元数据格式,但建议使用 PHP 属性,因为它们是设置和查找映射信息的最便捷和最灵活的方式。

控制器

使您的控制器继承 AbstractController 基类控制器

Symfony 提供了一个基类控制器,其中包括最常见需求的快捷方式,例如渲染模板或检查安全权限。

从这个基类控制器扩展您的控制器会将您的应用程序耦合到 Symfony。耦合通常是错误的,但在这种情况下可能是可以接受的,因为控制器不应包含任何业务逻辑。控制器应仅包含几行胶水代码,因此您不会耦合应用程序的重要部分。

使用属性配置路由、缓存和安全性

使用属性进行路由、缓存和安全配置简化了配置。您无需浏览使用不同格式(YAML、XML、PHP)创建的多个文件:所有配置都正好在您需要它的地方,并且仅使用一种格式。

使用依赖注入获取服务

如果您扩展了基类 AbstractController,您只能通过 $this->container->get() 直接从容器访问最常用的服务(例如 twigrouterdoctrine 等)。相反,您必须使用依赖注入,通过类型提示操作方法参数或构造函数参数来获取服务。

如果实体值解析器方便,则使用它们

如果您正在使用 Doctrine,那么您可以可选地使用 EntityValueResolver 来自动查询实体并将其作为参数传递给您的控制器。如果找不到实体,它还将显示 404 页面。

如果从路由变量获取实体的逻辑更复杂,则最好在控制器内部进行 Doctrine 查询(例如,通过调用 Doctrine 仓库方法),而不是配置 EntityValueResolver。

模板

模板名称和变量使用蛇形命名法

模板名称、目录和变量使用小写蛇形命名法(例如,user_profile 而不是 userProfile,以及 product/edit_form.html.twig 而不是 Product/EditForm.html.twig)。

模板片段以一个下划线作为前缀

模板片段,也称为“局部模板”,允许重用模板内容。以一个下划线作为前缀命名它们,以便更好地区分它们与完整模板(例如,_user_metadata.html.twig_caution_message.html.twig)。

表单

将您的表单定义为 PHP 类

在类中创建表单允许在应用程序的不同部分重用它们。此外,不在控制器中创建表单简化了控制器的代码和维护。

在模板中添加表单按钮

表单类应该与它们将在何处使用无关。例如,用于创建和编辑条目的表单的按钮应根据其使用位置,从“添加新的”更改为“保存更改”。

建议在模板中添加按钮,而不是在表单类或控制器中添加按钮。这还提高了关注点分离,因为按钮样式(CSS 类和其他属性)是在模板中定义的,而不是在 PHP 类中定义的。

但是,如果您创建了一个带有多个提交按钮的表单,则应在控制器中而不是在模板中定义它们。否则,您将无法在控制器中处理表单时检查单击了哪个按钮。

在底层对象上定义验证约束

验证约束附加到表单字段而不是映射对象,会阻止验证在其他表单或对象使用的其他位置被重用。

使用单个操作来渲染和处理表单

渲染表单处理表单是处理表单时的两个主要任务。两者非常相似(大多数时候几乎相同),因此让单个控制器操作处理两者会简单得多。

国际化

为您的翻译文件使用 XLIFF 格式

在 Symfony 支持的所有翻译格式(PHP、Qt、.po.mo、JSON、CSV、INI 等)中,XLIFFgettext 在专业翻译人员使用的工具中具有最佳支持。而且由于它基于 XML,因此您可以在编写 XLIFF 文件内容时对其进行验证。

Symfony 还支持 XLIFF 文件中的注释,使其对翻译人员更友好。最终,好的翻译都与上下文有关,而这些 XLIFF 注释允许您定义该上下文。

翻译使用键而不是内容字符串

使用键简化了翻译文件的管理,因为您可以更改模板、控制器和服务中的原始内容,而无需更新所有翻译文件。

键应始终描述其目的而不是其位置。例如,如果表单有一个标签为“用户名”的字段,那么一个好的键将是 label.username而不是 edit_form.label.username

安全

定义单个防火墙

除非您有两个合法不同的身份验证系统和用户(例如,主站点的表单登录和仅用于您的 API 的令牌系统),否则建议仅使用一个防火墙以保持简单。

此外,您应该在防火墙下使用 anonymous 键。如果您要求用户在您站点的不同部分登录,请使用 access_control 选项。

使用 auto 密码哈希器

auto 密码哈希器根据您的 PHP 安装自动选择最佳可能的编码器/哈希器。目前,默认的 auto 哈希器是 bcrypt

使用 Voter 实现细粒度的安全限制

如果您的安全逻辑很复杂,您应该创建自定义安全 Voter,而不是在 #[Security] 属性中定义长表达式。

Web 资源

使用 AssetMapper 管理 Web 资源

Web 资源是使您网站的前端看起来和工作起来都很棒的 CSS、JavaScript 和图像文件。AssetMapper 让您可以编写现代 JavaScript 和 CSS,而无需使用诸如 Webpack(直接或通过 Webpack Encore)之类的打包器。

测试

冒烟测试您的 URL

在软件工程中,冒烟测试包括“初步测试,以揭示严重到足以拒绝预期软件版本的简单故障”。使用 PHPUnit 数据提供器,您可以定义一个功能测试,以检查所有应用程序 URL 是否成功加载

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
// tests/ApplicationAvailabilityFunctionalTest.php
namespace App\Tests;

use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;

class ApplicationAvailabilityFunctionalTest extends WebTestCase
{
    /**
     * @dataProvider urlProvider
     */
    public function testPageIsSuccessful($url): void
    {
        $client = self::createClient();
        $client->request('GET', $url);

        $this->assertResponseIsSuccessful();
    }

    public function urlProvider(): \Generator
    {
        yield ['/'];
        yield ['/posts'];
        yield ['/post/fixture-post-1'];
        yield ['/blog/category/fixture-category'];
        yield ['/archives'];
        // ...
    }
}

在创建应用程序时添加此测试,因为它几乎不需要任何工作,并且可以检查您的页面是否都没有返回错误。稍后,您将为每个页面添加更具体的测试。

在功能测试中硬编码 URL

在 Symfony 应用程序中,建议使用路由生成 URL,以便在 URL 更改时自动更新所有链接。但是,如果公共 URL 更改,则除非您设置重定向到新 URL,否则用户将无法浏览它。

这就是为什么建议在测试中使用原始 URL 而不是从路由生成它们的原因。每当路由更改时,测试都会失败,您就会知道您必须设置重定向。

本作品,包括代码示例,均在 Creative Commons BY-SA 3.0 许可协议下获得许可。
目录
    版本