PHPでYAML設定を検証する:symfony/yamlとopis/json-schemaの使い方
作成日:2026.09.30
symfony/yamlでYAMLを読み込み、opis/json-schemaで必須項目や型、許可する値を検証する方法を紹介します。YAMLのマップをstdClassとして扱う方法や、空のobjectとarrayの違い、信頼できないYAMLを扱う際の注意点も解説します。
目次
PHPで設定ファイルを扱う場合、YAMLは人が読み書きしやすい形式です。ただし、文法として正しくても、アプリケーションが必要とする項目がない、値の型が違うといったことが起こりがちです。YAMLを読み込んだ後にデータの構造まで確認するには、構文解析とスキーマ検証を分けて考えます。
この記事では、symfony/yamlでYAMLを読み込み、opis/json-schemaでJSON Schemaに照らして検証する方法を紹介します。例の確認環境はPHP 8.3.15、symfony/yaml 7.4.18、opis/json-schema 2.6.0です。
YAMLの解析とデータ検証は別の処理
symfony/yamlの役割は、YAMLをPHPで扱える値へ変換することです。例えば、次のYAMLは文法として正しいため、timeout_secondsは文字列として読み込まれます。
integration:
task: summarize
enabled: true
timeout_seconds: many
一方、アプリケーション側でtimeout_secondsに整数を期待しているなら、読み込めただけでは利用できる設定とは言えません。JSON Schemaを使うと、必要な項目や型、許可する値を別に定義して検証できます。
パッケージはComposerで導入できます。
composer require symfony/yaml:^7.4 opis/json-schema:^2.6
YAMLファイルをPHPのobjectとして読み込む
次の設定をconfig.ymlとして保存します。
version: 1
integration:
task: summarize
enabled: true
timeout_seconds: 30
Yaml::parse()で内容を解析します。この例では、YAMLのマップをPHPのstdClassとして読み込むためにPARSE_OBJECT_FOR_MAPを指定しています。これにより、JSON SchemaのobjectとPHPの連想配列の対応を分かりやすくできます。
use Symfony\Component\Yaml\Exception\ParseException;
use Symfony\Component\Yaml\Yaml;
$maxBytes = 1_048_576;
$yaml = file_get_contents(__DIR__ . '/config.yml', false, null, 0, $maxBytes + 1);
if ($yaml === false) {
throw new RuntimeException('設定ファイルを読み込めませんでした');
}
// 上限を超えた場合は、解析する前にエラーにします。
if (strlen($yaml) > $maxBytes) {
throw new RuntimeException('設定ファイルのサイズが上限を超えています');
}
if (!mb_check_encoding($yaml, 'UTF-8')) {
throw new RuntimeException('設定ファイルはUTF-8で保存してください');
}
$flags = Yaml::PARSE_EXCEPTION_ON_INVALID_TYPE
| Yaml::PARSE_EXCEPTION_ON_ALIAS
| Yaml::PARSE_OBJECT_FOR_MAP;
try {
$config = Yaml::parse($yaml, $flags, 32, 0);
} catch (ParseException $e) {
throw new RuntimeException('YAMLの形式を確認してください', 0, $e);
}
if (!($config instanceof stdClass)) {
throw new RuntimeException('設定ファイルの最上位はYAMLマップにしてください');
}
PARSE_EXCEPTION_ON_ALIASはアンカーやエイリアスを含むYAMLを拒否し、PARSE_EXCEPTION_ON_INVALID_TYPEは解析できない型を例外にします。第3引数ではネストの上限を設定しています。UTF-8の確認に使うmb_check_encoding()にはmbstring拡張が必要です。設定内容や入力元に応じて、サイズや深さなどの上限は調整してください。
PARSE_OBJECT_FOR_MAPを指定すると、値の取得方法は配列アクセスではなくプロパティアクセスになります。例えば、読み込んだ後は$config->integration->taskで値を参照します。リスト形式のYAMLは引き続きPHPの配列になります。
JSON Schemaで設定の形を定義する
次のJSON Schemaでは、最上位のversionとintegrationを必須にし、timeout_secondsを1から120までの整数にしています。additionalPropertiesをfalseにすると、Schemaで定義していないキーもエラーになります。
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"version": {
"type": "integer",
"const": 1
},
"integration": {
"type": "object",
"properties": {
"task": {
"type": "string",
"enum": ["summarize", "classify", "extract"]
},
"enabled": {
"type": "boolean"
},
"timeout_seconds": {
"type": "integer",
"minimum": 1,
"maximum": 120
}
},
"required": ["task", "enabled", "timeout_seconds"],
"additionalProperties": false
}
},
"required": ["version", "integration"],
"additionalProperties": false
}
Schemaはconfig.schema.jsonとして保存します。ここではJSON文字列をjson_decode()で読み込み、Opisへ渡します。第2引数を省略するとJSONオブジェクトはstdClassになり、YAMLから読み込んだ設定と同じJSON型で検証できます。
use Opis\JsonSchema\Errors\ErrorFormatter;
use Opis\JsonSchema\Validator;
$schemaJson = file_get_contents(__DIR__ . '/config.schema.json');
if ($schemaJson === false) {
throw new RuntimeException('Schemaファイルを読み込めませんでした');
}
$schema = json_decode($schemaJson, false, 512, JSON_THROW_ON_ERROR);
$validator = new Validator();
$result = $validator->validate($config, $schema);
if (!$result->isValid()) {
$errors = (new ErrorFormatter())->format($result->error());
print_r($errors);
throw new RuntimeException('設定内容がSchemaに適合しません');
}
echo $config->integration->task;
例えばtimeout_seconds: manyにすると型が合わず、timeout_seconds: 180にすると最大値を超えるため検証に失敗します。integrationに未定義のキーを追加した場合や、必須のenabledを省いた場合もSchemaで検出できます。
PHPの配列とJSONのobject・arrayの違い
JSONではobjectとarrayは異なる型ですが、PHPではどちらも配列で表せます。さらに空のPHP配列だけからは、空objectの{}と空arrayの[]のどちらを意図したのか分かりません。
OpisのHelper::toJSON()は連想配列をJSONのobjectとして扱うための方法ですが、空配列をobjectへ変換することはできません。今回の例のようにYAMLのマップを最初からstdClassとして読み込めば、空のマップと空のリストも区別できます。スキーマでobjectを要求する箇所がある場合は、どのPHP型へ変換されているかを確認しましょう。
また、Symfony YAMLは日付のように見える値を既定で数値へ変換する場合があります。文字列として扱いたい値は、YAML側で引用符を付けるなど、パーサーが返すPHP型を確認してください。
Schema検証だけで入力の安全性が決まるわけではない
JSON Schemaで確認できるのは、定義した構造や型、値の条件です。入力元が信頼できるか、値が業務上妥当か、処理を実行してよいかまでは判断しません。権限や業務ルールの確認は、アプリケーション側でも別に行います。
信頼できないYAMLを扱う場合は、Symfonyの公式資料にあるエイリアスの制限を確認し、必要がなければ無効にします。PHPのオブジェクトを復元するPARSE_OBJECTは、内部でデシリアライズを行うため、信頼できない入力では有効にしないでください。ファイルサイズ、UTF-8、ネストの深さなども用途に合わせて制限しましょう。
まとめ
symfony/yamlはYAMLをPHPの値へ解析し、opis/json-schemaはその値がアプリケーションの期待する構造かを検証します。AIとアプリケーションの間で扱う設定や定義でも、二つの責務を分けると、構文エラーと設定内容の誤りを別々に扱えます。
PHP配列を検証に使うときは、JSONのobjectとarrayへの変換にも注意が必要です。YAMLマップをstdClassとして読み込む方法も選択肢になります。実際に導入する際は、パッケージの対象バージョンと、設定ファイルの入力元に合わせた制約を確認してください。
参考資料
奈良市を拠点に、27年以上の経験を持つフリーランスWebエンジニア、阿部辰也です。
これまで、ECサイトのバックエンド開発や業務効率化システム、公共施設の予約システムなど、多彩なプロジェクトを手がけ、企業様や制作会社様のパートナーとして信頼を築いてまいりました。
【制作会社・企業様向けサポート】
Webシステムの開発やサイト改善でお困りの際は、どうぞお気軽にご相談ください。小さな疑問から大規模プロジェクトまで、最適なご提案を心を込めてさせていただきます。
ぜひ、プロフィールやWeb制作会社様向け業務案内、一般企業様向け業務案内もご覧くださいね。
Laravelのコードレビューで遅いSQLの候補を洗い出す
2026.10.02
Laravelのコードレビューで、ループ内のDBアクセスやEloquentのN+1、大量取得、検索条件とインデックスなど、性能問題につながりやすい箇所を候補として洗い出します。Laravelで発行SQLと実行時間を確認し、MySQLのEXPLAINや実データに近い環境での計測へつなげる方法を紹介します。コードレビューだけではSQLの実行時間を断定できない点も説明します。
Windows 11でDocker Composeを使ってLaravel環境を作る:Container・Volume・Networkの基本
2026.10.01
Windows 11のDocker Desktop上に、DockerfileとDocker Composeを使ってLaravel・PHP・MySQL・Mailpitの開発環境を構築します。ImageとContainerの関係、Bind MountとNamed Volumeによるデータの保存、ポート公開とContainer間通信の違いを実例で確認。LaravelからMySQLへ接続するときに、localhostではなくService名を使う理由も説明します。
PHPの正規表現で日本語・改行を扱う:u・m・s修飾子の違い
2026.09.29
PHPの正規表現で日本語や複数行テキストを扱う際の、u・m・s修飾子の違いを解説します。Unicode文字クラスと全角数字、CRLFの行末処理、\A・\zの使い方、Unicode正規化との違いを、PHP 8.3.15・PCRE2 10.42で確認したコード例とともに紹介します。
mPDFで日本語PDFを生成する:フォント登録・改ページ・レイアウト崩れの確認ポイント
2026.09.23
PHPからmPDFで日本語を含むPDFや帳票を生成する際に、Noto Sans JPなどのフォントをプロジェクト側で登録する方法、fontDir・fontdata・tempDirの設定、HTML/CSSの制約、改ページや長い文字列の扱いを解説します。Linuxや本番環境で起きる文字化け・レイアウト崩れを、PHP拡張、フォント、権限、入力HTMLの観点から切り分ける手順も紹介します。