Yamlコンポーネントを使ってみる

この記事は、Zennに投稿した以下の記事の再投稿になります。

Yamlコンポーネントについて

Symfonyの公式Docsでは次のように説明されています。

Symfony Yaml コンポーネントは、YAML 文字列を解析して PHP 配列に変換します。PHP 配列を YAML 文字列に変換することもできます。

YAML文字列というのは、以下のようなやつです。

"hoge: fuga"

Yamlコンポーネントを使うことで、このYAML文字列を以下のようなPHP配列に直すことができます。

['hoge'=>'fuga']

拡張子yamlのファイルを使って書くと、もうちょっと綺麗に書けます。

hoge: fuga
piyo: hogera

# PHP配列に変換すると以下になる
# [
#   'hoge'=>'fuga',
#   'piyo'=>'hogera'
# ]

動作環境・前提

バージョン
Mac 12.x
PHP 7.4.33
Symfony 5.4.21

本題

早速ですが、以下に簡単な実用例を示します。

まずはディレクトリ構造です。

/
├ src
| ├ Controller
| | └ SampleController.php
| └ Resource
|   └ locale
|     ├ sample_message.ja.yaml
|     └ sample_message.en.yaml
└ templates
  └ sample
    └ index.html.twig

次に、それぞれのファイルを見てきます。

  • yamlファイル
out_of_stock: 在庫が不足しています
out_of_stock: Out of stock.
  • twigファイル
{# ...省略 #}
{% block body %}
  <p>{{ message }}</p>
{% endblock %}
  • phpファイル
/* ...省略 */
require_once __DIR__.'/../../vendor/autoload.php';

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Yaml\Yaml;

class SampleController extends AbstractController
{
    /**
     * @Route("/sample/{id}/{la}", name="sample_index")
     */
    public function index(string $id, string $la="ja"): Response
    {
        $messageFile_ja = __DIR__.'/../Resource/locale/message.ja.yaml';
        $messageFile_en = __DIR__.'/../Resource/locale/message.en.yaml';

        if ($la==="ja") {
            $messages = Yaml::parse(file_get_contents($messageFile_ja));
        } elseif ($la==="en") {
            $messages = Yaml::parse(file_get_contents($messageFile_en));
        }
        $message = $messages[$id];

        return $this->render('sample/index.html.twig', [
            'message' => $message,
        ]);
    }
}

では、以下でそれぞれのファイルについて説明していきます。

yamlファイルについて

今回は、sample_message.ja.yamlsample_message.en.yamlの2つを用意しました。

ファイル名の通りではありますが、前者は日本語のメッセージを、後者は英語のメッセージを格納しています。

どちらのファイルでも、out_of_stockというキー名を使用しています。

twigファイルについて

次にテンプレートエンジンのtwigファイルですが、こちらは大したことはしていません。
messageという名前で渡ってきたデータをpタグで表示するだけです。

PHPファイルについて

SampleController.phpというコントローラを実装しています。

indexアクションの冒頭で、次のようなコードが書かれています。

$messageFile_ja = __DIR__.'/../Resource/locale/message.ja.yaml';
$messageFile_en = __DIR__.'/../Resource/locale/message.en.yaml';

先ほど説明した、日本語用のyamlファイルと英語用のyamlファイルのPathを変数に格納しています。

この変数に格納したファイルPathがYaml文字列を取り出し、Yamlコンポーネントに渡しているのが次のコードです。

if ($la==="ja") {
    $messages = Yaml::parse(file_get_contents($messageFile_ja));
} elseif ($la==="en") {
    $messages = Yaml::parse(file_get_contents($messageFile_en));
}

Yamlクラスのparse()というメソッドを利用していますね。
このメソッドにYaml文字列を引数として渡すと、PHP配列に変換してくれます。
(parseは「解析」という意味があるそうです。)

file_get_contents()メソッドでYamlファイルからYaml文字列を取り出し、parse()メソッドに渡している、ということです。

また、ここでは$laという変数が"ja"という文字列なら日本語用のファイルを、"en"という文字列なら英語用のファイルを渡すように分岐させています。
変数$laは、GETリクエストで値が渡ってくるようにしています。初期値は"ja"です。

/**
 * @Route("/sample/{id}/{la}", name="sample_index")
 */
public function index(string $id, string $la="ja"): Response

最後に、次のコードでメッセージを取得し、twigファイルに渡しています。

// メッセージを取得
$message = $messages[$id];

// twigファイルにmessageを渡す
return $this->render('sample/index.html.twig', [
    'message' => $message,
]);

先ほど、Yamlコンポーネントを使って変換されたPHP配列は、$messagesという変数に格納されていました。
ここでは、変数$idに格納されたキー名を使って、その配列から1つだけメッセージを取り出すようにしています。
変数$idも、$laと同様にGETリクエストで値が渡ってきます。

各ファイルのコードについての説明は以上です。
これで具体的にどうなるかというと、例えば以下のスクショのようにURLの{la}の値を書き換えるだけで、日本語と英語の切り替えができるようになります。

  • localhost:xxxx/sample/out_of_stock/ja
    issue_7_1.png

  • localhost:xxxx/sample/out_of_stock/en
    issue_7_2.png

EC CUBEのエラーメッセージ等もこの方法で管理されていますね。

結局これの何が便利なのか

ここまでで、Yamlコンポーネントの簡単な使い方は説明しきれたかと思います。
ですが、結局これの何が便利なのでしょうか。自分なりにどういう使い方をすれば便利さを実感できるかを考えてみました。

先ほどのサンプルと同様に、2つのyamlファイルを例に考えてみます。

out_of_stock: 在庫が不足しています
failed_order_complete: 注文に失敗しました
access_disabled: このページにアクセスすることはできません
out_of_stock: Out of stock.
failed_order_complete: Order failed.
access_disabled: This page cannot be accessed.

では、そもそもこれをYamlファイルを使わずPHPの連想配列として実装するとどうなるでしょうか。

$sampleMessage_ja = [
    "out_of_stock" => "在庫が不足しています",
    "failed_order_complete" => "注文に失敗しました",
    "access_disabled" => "このページにアクセスすることはできません",
];

$sampleMessage_en = [
    "out_of_stock" => "Out of stock.",
    "failed_order_complete" => "Order failed.",
    "access_disabled" => "This page cannot be accessed.",
];

はい、既にとても見にくくなっていることがわかると思います。

この例ではエラーメッセージの数は3つずつなのでこんなものですが、実際のサービスではもっとたくさんのメッセージが必要になってくるはずです。
それが、PHPファイルに連想配列でブワァ〜っと並ぶわけです。想像しただけで恐ろしいですね。

もしくは、こうやって連想配列にまとめることすらしないのかもしれません。
要するに、こんな感じでコードの中にエラーメッセージを直書きしていくということですね。

/**
 * @Route("/管理者用ページ")
 */
public function index()
{
    $user = 一般ユーザ;

    if ($user !== 管理者) {
        throw new ErrorException("このページにアクセスすることはできません");
    }
}

もちろん、こういう書き方が絶対にNGというわけではないと思います。
ただ、これだと保守の面で不安が残ります。

説明するために、もう1つコントローラを追加してみましょう。

/**
 * @Route("/管理者用ページ")
 */
public function index()
{
    $user = 一般ユーザ;

    if ($user !== 管理者) {
        echo "このページにアクセスすることはできません":
    }
}
/**
 * @Route("/管理者用ページ/edit")
 */
public function edit()
{
    $user = 一般ユーザ;

    if ($user !== 管理者) {
        echo "管理者でログインしてください";
    }
}

2つのコントローラは、それぞれ別のプログラマが書いたものとします。

さてここで、これらのエラーメッセージについてのマニュアルを作成するとします。
マニュアル作成は、edit()アクションを作成したプログラマが担当しました。

# エラーメッセージについて

## 「管理者でログインしてください」と表示された場合
- 現在のログインユーザが管理者でない場合に表示されます。
  管理者でログインし直して、再度アクセスしてください。
- 管理者でログインし直してもエラーが消えない場合、キャッシュが原因でページにアクセスできていない
  可能性があります。以下の手順に従って、キャッシュクリアを行ってください。

(省略)

上記の方法でエラーが解決しない、または発生したエラーが上記のどれにも該当しない場合、
下記のアドレスにお問い合わせください。
TEL: xxx-xxxx
E-MAIL: xxx@xxx.xx

このプログラマは、管理者ページに管理者以外がアクセスしたときのエラーメッセージが全て「管理者でログインしてください」であると思い込んでいます。

今回は、上記の2つしかアクションがないのでコードを確認して別のエラーメッセージを探すこともできますが、実際はもっとたくさんのControllerがあり、その中にたくさんのエラーメッセージが含まれています。
それらを全て完璧にさらってマニュアルにまとめるのは、ほぼ不可能に近いでしょう。結果、このような漏れが生じ得ます。

この後に起こることは、容易に想像できると思います。
実際にこのサービスを利用し、「このページにアクセスすることはできません」というエラーが表示されてしまったユーザからの問い合わせが殺到するのです。

Yamlコンポーネントを使うようにすれば、この問題は解決できます。
例えば、次のようなYamlファイルを作成します。

access_disabled: このページにアクセスすることはできません
login_admin: 管理者でログインしてください

プログラマには、エラーメッセージは必ずこのYamlファイルにまとめるように釘を刺しておきます。

そして、マニュアル作成を担当する人に、
「messageディレクトリの中のerror_message.yamlファイルにあるエラーメッセージのユーザ向けの対処方法をまとめたマニュアルを作成してください。」
と頼みます。
そうすれば、少なくとも漏れは無くなります。

さらに、このファイルを見てからPHPのコード内で実際に使われてる場所を確認したプログラマはこう思うはずです。
「『このページにアクセスすることはできません』と『管理者でログインしてください』って、エラーの内容は全く同じじゃないか、、?」
と。

そうなれば、エラーメッセージをどちらか1つに統合することもできそうですね。

最後に

EC CUBEでこのYamlコンポーネントを使ったコードを見かけて、「これって自分で書くときはどう使うんだろう?」と思ってあれこれ試していたものをブログ化しました。

使い方自体は簡単だったんですが、「じゃあこれの何が便利なの?」というところを説明しようとした時にめっちゃ苦労しました。
便利なんだけど、具体的にどこがどう便利で〜みたいな話をしようとするとすごく難しかったですね。

Romy(ろみぃ)

歌とゲームと本が好きな人間。ドラゴンになりたい。
不定期で記事を更新していきます。
今後、ブログ以外にもコンテンツ追加していく予定。

© Romy 2024