JsonSchemaBundleを作った話

JsonSchemaBundleを作った話

December 2, 2019,
tags: symfony bundle JsonSchema


このエントリーをはてなブックマークに追加

Symfony Advent Calendar 2019 2日目の記事です。
昨日は@77webさんのSymfony4.4正式リリース前に4.4.x-devで開発し始めたプロジェクトを4.4.0正式版にアップデートするでした。

ptyhard/JsonSchemaBundle

ptyhard/JsonSchemaBundleを作りました。
@kojirock5260さんの LaravelでJsonSchema使いたい を読んで刺激を受けて、作り始めました。

SymfonyでJsonSchemaを利用したValidationを行う場合、いくつかのBundleを使えば簡単にJsonSchema Validationができるかと思います。
使用したことはありませんがApi Platformでもサポートしているようです。

ただDoctrineとべったりな感じが個人的にはあまり好きにはなれません。

ptyhard/JsonSchemaBundleのコンセプト

  • JsonSchemaをアノテーションで表現する
  • 入力値をクラスにマッピングする

この2つの機能を実現するために作っています。

JsonSchemaをアノテーションで表現する

ptyhard/JsonSchemaBundleはjsonSchemaをAnnotationで表現できます。

  • ArrayProperty
  • CollectionProperty
  • NumberProperty
  • ObjectProperty
  • StringProperty

をサポートしています。
(なぜBooleanのサポートがないのか???あとで対応しておきます)

<?php

namespace App\Schema;


use Ptyhard\JsonSchemaBundle\Annotations\Property as SchemaProperty;
use Ptyhard\JsonSchemaBundle\Annotations\SchemaClass;

/**
 * @SchemaClass({ required: {"id"} })
 */
class User
{
    /**
     * @SchemaProperty\NumberProperty()
     * @var int
     */
    private $id;

    /**
     * @SchemaProperty\StringProperty(minLength=1, maxLength=125)
     * @var string
     */
    private $name;

    /**
     * @SchemaProperty\StringProperty()
     * @var string
     */
    private $description;

    /**
     * @return int
     */
    public function getId(): int
    {
        return $this->id;
    }

    /**
     * @return string
     */
    public function getName(): string
    {
        return $this->name;
    }

    /**
     * @return string
     */
    public function getDescription(): string
    {
        return $this->description;
    }

}

入力値をオブジェクトにマッピングする

クライアントから送信されたデータをオブジェクトにマッピングします。
コントローラのアクションの引数にスキーマを定義したクラスを指定すれば勝手にマッピングされるようになっています。

<?php

namespace App\Controller;

use App\Schema\User;
use Polidog\SimpleApiBundle\Annotations\Api;
use Symfony\Component\Routing\Annotation\Route;

class UserController
{
    /**
     * @Route("/user", methods={"POST"})
     * @Api(statusCode=200)
     * @param User $user
     * @return User
     */
    public function __invoke(User $user): User
    {
    }
}

上記のようにUserクラスを引数で渡せば実行時にオブジェクトに値をマッピングします。 requestクラスからめんどくさいオブジェクトへの値マッピングの実装をしなくていいので便利ですよね。

ファイルを指定することもできる

スキーマ定義のjsonファイルをを用意している場合には@SchemaFileを使ってバリデーションを行う事もできます。
public/json_schemaのディレクトリにschema.jsonを入れてもらいクラスファイルに以下のように書いてください。
それから、Controllerのアクションに利用するjsonSchemaのファイルを指定します。

<?php

namespace App\Controller;

use Polidog\SimpleApiBundle\Annotations\Api;
use Ptyhard\JsonSchemaBundle\Annotations\SchemaFile;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\Routing\Annotation\Route;

class UserController
{
    /**
     * @Route("/user", methods={"POST"})
     * @Api(statusCode=200)
     * @SchemaFile(file="user.json")
     */
    public function __invoke(): JsonResponse
    {
        return new JsonResponse(['status' => 'ok']);
    }
}

jsonのファイルを置く位置を変更することもできます。

// config/ptyhard_json_schema.yaml

ptyhard_json_schema:
    json_file_directory: '/public/schema'

レスポンスする値を検証する

レスポンスする値に対してバリデーションを行う事もできます。 @SchemaObject の場合は戻り地の型がSchemaObjectのAnnotationがあるオブジェクトであれば勝手にバリデーションします。 (オブジェクトになっているのでバリデーションいらない気もしますが)

@SchemaFile場合は設定が必要になります。以下のようにAnnotationに記述すればレスポンスする値も検証できます。

@SchemaFile(file="user.json", type={"response"})

このようにtypeにrequest,responseを定義すると指定したタイミングでバリデーションができます。

最後に

小さなSPA的なアプリケーションでpolidog/simple-api-bundle と組み合わせ使いましたが、なかなかの使いやすさがありました。

まだまだBundle自体にはバグがあるとは思いますが、ぜひ使ってみてフィードバックやPull Requestいただけたら嬉しいです。

comments powered by Disqus