Symfonyフレームワークのベストプラクティス

この記事では、Symfonyの最初の作成者が構想した哲学に適合する SymfonyでWebアプリケーションを開発するためのベストプラクティス について説明します。

これらの推奨事項のいくつかに同意しない場合は、特定のニーズに拡張して適合させる ことができる 開始点 になる可能性があります。それらを完全に無視して、独自のベストプラクティスと方法論を引き続き使用することもできます。 Symfonyはあなたのニーズに適応するのに十分な柔軟性があります。

この記事は、Symfonyアプリケーションの開発経験があることを前提としています。そうでない場合は、最初にドキュメントのセクション はじめに を読んでください。

ちなみに

Symfonyは Symfony Demo と呼ばれるサンプルアプリケーションを提供しています。これはすべてのベストプラクティスに従っており、実際にそれらを体験することができます。

プロジェクトを作成する

Symfonyバイナリを使用してSymfonyアプリケーションを作成する

Symfonyバイナリは、Symfonyをダウンロード したときにマシンで作成される実行可能なコマンドです。新しいSymfonyアプリケーションを作成する最も簡単な方法を含む、複数のユーティリティを提供します:

1
$ symfony new my_project_name

内部的には、このSymfonyバイナリコマンドは現在の安定バージョンに基づいた 新しいSymfonyアプリケーションを作成するために 必要な Composer コマンドを実行します。

デフォルトのディレクトリ構造を使用する

プロジェクトが特定のディレクトリ構造を強制する開発慣行に従っていない限り、デフォルトの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
your_project/
├─ assets/
├─ bin/
│  └─ console
├─ config/
│  ├─ packages/
│  └─ services.yaml
└─ public/
│  ├─ build/
│  └─ index.php
├─ src/
│  ├─ Kernel.php
│  ├─ Command/
│  ├─ Controller/
│  ├─ DataFixtures/
│  ├─ Entity/
│  ├─ EventSubscriber/
│  ├─ Form/
│  ├─ Migrations/
│  ├─ Repository/
│  ├─ Security/
│  └─ Twig/
├─ templates/
├─ tests/
├─ translations/
├─ var/
│  ├─ cache/
│  └─ log/
└─ vendor/

構成

インフラ構成に環境変数を使用する

これらは、あるマシンから別のマシンに(たとえば、開発マシンから本番サーバーに)変更されるオプションですが、アプリケーションの動作は変更されません。

これらのオプションを定義するための プロジェクトで環境変数の使用 し、 環境毎の環境変数を構成するための 複数の .env ファイルを作成します。

機密情報にシークレットを使用する

アプリケーションにAPIキーなどの機密構成がある場合は、それらを secrets を介して安全に保存する必要があります。

アプリケーション構成のパラメーターを使用する

これらは、メール通知の送信者や有効な 機能の切り替え など、アプリケーションの動作を変更するために使用されるオプションです。それらの値はマシンごとに変化しないため、環境変数として定義しないでください。

config/services.yaml ファイルに、 パラメータ としてオプションを定義します。config/services_dev.yamlconfig/services_prod.yaml ファイルで 環境 毎にオプションを上書きできます。

短いプレフィックスのパラメータ名を使用する

Symfonyとサードパーティのbundles/librariesのパラメータとの衝突を避けるため、 パラメータ のプレフィックスに app. を使用することを検討してください 。次に、パラメーターの目的を説明するため1つまたは2つの単語を使用してください:

 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定数として定義します。例:

// src/Entity/Post.php
namespace App\Entity;

class Post
{
    public const NUMBER_OF_ITEMS = 10;

    // ...
}

定数の主な利点は、TwigテンプレートやDoctrineエンティティを含め、定数をどこでも使用できることです。一方、パラメーターは サービスコンテナー にアクセスできる場所からのみ利用できます。

この種の構成値に定数を使用することの唯一の注目すべき欠点は、テストで値を再定義するのが複雑なことです。

ビジネスロジック

アプリケーションロジックを整理するためにバンドルを作成しないでください

Symfony 2.0がリリースされたとき、アプリケーションはコードを論理機能(UserBundle、ProductBundle、InvoiceBundleなど)ごとに分割するため バンドル を使用していました。しかしながら、バンドルはスタンドアロンのソフトウェアとして再利用できるものです。

プロジェクトで一部の機能を再利用する必要がある場合は、そのバンドルを作成します(公開しないように、プライベートリポジトリに作成します)。残りのアプリケーションコードについては、バンドルの代わりにPHP名前空間を使用してコードを整理します。

Autowiringを使用してアプリケーションサービスの構成を自動化する

Service autowiring は、コンストラクター(またはその他のメソッド)の型ヒントを読み取り、正しいサービスを各メソッドに自動的に渡すため、サービスを明示的に構成する必要がなく、アプリケーションのメンテナンスを簡素化します。

Twigエクステンション、イベントサブスクライバーなどの必要なサービスを サービスタグ に追加するために service autoconfiguration と組み合わせて使用します。

サービスは可能な限りプライベートである必要があります

$ container->get() を介してこれらのサービスにアクセスできないようにするため、サービスをプライベートにしてください 。代わりに、適切な依存関係注入を使用する必要があります。

Use the YAML Format to Configure your Own Services

If you use the default services.yaml configuration, most services will be configured automatically. However, in some edge cases you’ll need to configure services (or parts of them) manually.

YAML is the format recommended to configure services because it’s friendly to newcomers and concise, but Symfony also supports XML and PHP configuration.

Use Annotations to Define the Doctrine Entity Mapping

Doctrine entities are plain PHP objects that you store in some “database”. Doctrine only knows about your entities through the mapping metadata configured for your model classes.

Doctrine supports several metadata formats, but it’s recommended to use annotations because they are by far the most convenient and agile way of setting up and looking for mapping information.

Controllers

Make your Controller Extend the AbstractController Base Controller

Symfony provides a base controller which includes shortcuts for the most common needs such as rendering templates or checking security permissions.

Extending your controllers from this base controller couples your application to Symfony. Coupling is generally wrong, but it may be OK in this case because controllers shouldn’t contain any business logic. Controllers should contain nothing more than a few lines of glue-code, so you are not coupling the important parts of your application.

Use Annotations to Configure Routing, Caching and Security

Using annotations for routing, caching and security simplifies configuration. You don’t need to browse several files created with different formats (YAML, XML, PHP): all the configuration is just where you need it and it only uses one format.

Don’t Use Annotations to Configure the Controller Template

The @Template annotation is useful, but also involves some magic. Moreover, most of the time @Template is used without any parameters, which makes it more difficult to know which template is being rendered. It also hides the fact that a controller should always return a Response object.

Use Dependency Injection to Get Services

If you extend the base AbstractController, you can only access to the most common services (e.g twig, router, doctrine, etc.), directly from the container via $this->container->get() or $this->get(). Instead, you must use dependency injection to fetch services by type-hinting action method arguments or constructor arguments.

Use ParamConverters If They Are Convenient

If you’re using Doctrine, then you can optionally use the ParamConverter to automatically query for an entity and pass it as an argument to your controller. It will also show a 404 page if no entity can be found.

If the logic to get an entity from a route variable is more complex, instead of configuring the ParamConverter, it’s better to make the Doctrine query inside the controller (e.g. by calling to a Doctrine repository method).

テンプレート

Use Snake Case for Template Names and Variables

Use lowercased snake_case for template names, directories and variables (e.g. user_profile instead of userProfile and product/edit_form.html.twig instead of Product/EditForm.html.twig).

Prefix Template Fragments with an Underscore

Template fragments, also called “partial templates”, allow to reuse template contents. Prefix their names with an underscore to better differentiate them from complete templates (e.g. _user_metadata.html.twig or _caution_message.html.twig).

フォーム

Define your Forms as PHP Classes

Creating forms in classes allows to reuse them in different parts of the application. Besides, not creating forms in controllers simplify the code and maintenance of the controllers.

Add Form Buttons in Templates

Form classes should be agnostic to where they will be used. For example, the button of a form used to both create and edit items should change from “Add new” to “Save changes” depending on where it’s used.

Instead of adding buttons in form classes or the controllers, it’s recommended to add buttons in the templates. This also improves the separation of concerns, because the button styling (CSS class and other attributes) is defined in the template instead of in a PHP class.

Define Validation Constraints on the Underlying Object

Attaching validation constraints to form fields instead of to the mapped object prevents the validation from being reused in other forms or other places where the object is used.

Use a Single Action to Render and Process the Form

Rendering forms and processing forms are two of the main tasks when handling forms. Both are too similar (most of the times, almost identical), so it’s much simpler to let a single controller action handle everything.

国際化社会

Use the XLIFF Format for Your Translation Files

Of all the translation formats supported by Symfony (PHP, Qt, .po, .mo, JSON, CSV, INI, etc.) XLIFF and gettext have the best support in the tools used by professional translators. And since it’s based on XML, you can validate XLIFF file contents as you write them.

Symfony also supports notes in XLIFF files, making them more user-friendly for translators. At the end, good translations are all about context, and these XLIFF notes allow you to define that context.

Use Keys for Translations Instead of Content Strings

Using keys simplifies the management of the translation files because you can change the original contents in templates, controllers and services without having to update all of the translation files.

Keys should always describe their purpose and not their location. For example, if a form has a field with the label “Username”, then a nice key would be label.username, not edit_form.label.username.

セキュリティ

Define a Single Firewall

Unless you have two legitimately different authentication systems and users (e.g. form login for the main site and a token system for your API only), it’s recommended to have only one firewall to keep things simple.

Additionally, you should use the anonymous key under your firewall. If you require users to be logged in for different sections of your site, use the access_control option.

Use the auto Password Hasher

The auto password hasher automatically selects the best possible encoder/hasher depending on your PHP installation. Currently, it tries to use sodium by default and falls back to bcrypt.

Use Voters to Implement Fine-grained Security Restrictions

If your security logic is complex, you should create custom security voters instead of defining long expressions inside the @Security annotation.

Web Assets

Use Webpack Encore to Process Web Assets

Web assets are things like CSS, JavaScript and image files that make the frontend of your site look and work great. Webpack is the leading JavaScript module bundler that compiles, transforms and packages assets for usage in a browser.

Webpack Encore is a JavaScript library that gets rid of most of Webpack complexity without hiding any of its features or distorting its usage and philosophy. It was originally created for Symfony applications, but it works for any application using any technology.

テスト

Smoke Test your URLs

In software engineering, smoke testing consists of “preliminary testing to reveal simple failures severe enough to reject a prospective software release”. Using PHPUnit data providers you can define a functional test that checks that all application URLs load successfully:

// tests/ApplicationAvailabilityFunctionalTest.php
namespace App\Tests;

use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;

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

        $this->assertResponseIsSuccessful();
    }

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

Add this test while creating your application because it requires little effort and checks that none of your pages returns an error. Later you’ll add more specific tests for each page.

Hardcode URLs in a Functional Test

In Symfony applications it’s recommended to generate URLs using routes to automatically update all links when a URL changes. However, if a public URL changes, users won’t be able to browse it unless you set up a redirection to the new URL.

That’s why it’s recommended to use raw URLs in tests instead of generating them from routes. Whenever a route changes, tests will break and you’ll know that you must set up a redirection.