4
2

More than 3 years have passed since last update.

SymfonyのOptionResolverの紹介

Last updated at Posted at 2021-03-23

はじめに

ビルダーパターンなどを使っていると、配列で様々なオプションを渡したくなる時があります。その時に、値の存在確認や、型のチェックなどをスマートにこなせるSymfonyのOptionResolver Componentの紹介です。

問題提起

Carクラス、Engineクラス、Tireクラスの三つがあります。CarクラスはEngineTireのセッターを持ち、EngineTireはそれぞれint型のパワーとサイズをセッターで受け取ります。

実装は以下の通りです。

<?php

class Car
{
    protected Engine $engine;

    protected Tire $tire;

    public function setEngine(Engine $engine): void
    {
        $this->engine = $engine;
    }

    public function setTire(Tire $tire): void
    {
        $this->tire = $tire;
    }
}

#------------------------------------------------------------

class Engine
{
    protected int $power;

    public function setPower(int $power): void
    {
        $this->power = $power;
    }
}

#------------------------------------------------------------

class Tire
{
    private int $size;

    public function setSize(int $size): void
    {
        $this->size = $size;
    }
}

CarBuilderでこれらのパーツを組み合わせてCarを作成します。

CarBuilder.php
class CarBuilder
{
    public function build(): Car
    {
        $car = new Car;
        $tire = new Tire;
        $engine = new Engine;

        $engine->setPower(50);
        $tire->setSize(20);

        $car->setEngine($engine);
        $car->setTire($tire);
        return $car;
    }
}

この状態ではエンジンのパワーとタイヤのサイズが固定されてしまっています。これらの値をオプションで変更できるようにしましょう。

パワーとサイズを変えられるようにしたものが下になります。

CarBuilder.php
class CarBuilder
{
    public function build(array $options): Car
    {
        $car = new Car;
        $tire = new Tire;
        $engine = new Engine;

        $engine->setPower($options['power'] ?? 50);
        $tire->setSize($options['size'] ?? 20);

        $car->setEngine($engine);
        $car->setTire($tire);
        return $car;
    }
}

$optionを引数でもらい、その中にパワーとサイズが入っていればその値を、入っていなければデフォルトの値を使います。
確かにこれで動きます。が、いちいち$optionsに値が入っているか確認するのはイマイチですよね。また、int型以外の値が入って来た場合、エラーになってしまいます。$size$powerをそのまま引数でもらうこともできますが、こちらもオプションの内容が増える度にコードを書き換える必要があり、オプションの数が10や20になった時にとんでもないことになります。

そこで登場するのがOptionResolverです。

OptionResolver版

OptionResolverを使うと、先ほどのCarBuilderはこのように書けます。configureOptions()メソッドを新たに作り、その引数に渡された$optionsResolverに許可するオプションを定義します。
以下では、sizepowerの二つを定義し、デフォルト値はそれぞれ50と20、型はint型としています。

build()メソッドに渡ってくる$optionconfigureOptions()で定義した物を必ず満たすことが保証されます。なので、値の存在確認や型チェックをする必要がなくなります。


use Symfony\Component\OptionsResolver\OptionsResolver;

class CarBuilder
{
    public function build(array $options): Car
    {
        $car = new Car;
        $tire = new Tire;
        $engine = new Engine;

        $engine->setPower($options['power']);
        $tire->setSize($options['size']);

        $car->setEngine($engine);
        $car->setTire($tire);
        return $car;
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults([
            'power' => 50,
            'size' => 20,
        ]);

        $resolver->setAllowedTypes('size', 'int');
        $resolver->setAllowedTypes('power', 'int');
    }
}

どうなってるの?

魔法みたいな仕組みに見えますが、もちろん魔法ではありません。CarBuilderの具体的な使い方をみてみます。

use Symfony\Component\OptionsResolver\OptionsResolver;

$options = ['size' => 10];

$resolver = new OptionsResolver;
$builder = new CarBuilder;

$builder->configureOptions($resolver);
$car = $builder->build($resolver->resolve($options));

まず、OptionResolverのインスタンスの$resolverを作ります。CarBuilderconfigureOptions()メソッドにこの$resolver渡すと、オプションの定義が設定されます。
オプションの定義が設定された$resolverに対して、配列を引数にしてresolve()メソッドを呼ぶと、その配列に対してオプションの定義に沿っているかのバリデーションがかけられます。型が違うなどバリデーション違反の場合は、この時点で例外が投げられます。よって、例外が投げられず、build()メソッドまで到達した場合は、必ずオプションの定義を満たしている、という訳です。

オプションの定義には、ここで用いた物意外にも次のようなものがあります。詳しくはこちらをご覧ください。
https://symfony.com/doc/current/components/options_resolver.html

$resolver->setAllowedValues('optionName1', ['VALUE1', 'VALUE2', 'VALUE3']);
$resolver->setDeprecated('optionName2');
$resolver->setRequired('optionName3')

最後に

今回は、SymfonyのOptionResolverの紹介をしました。こちらは、Symfonyに全く依存しない素のPHPのライブラリとなっているので、Laravelなどのプロジェクトでも利用することが可能です。
Symfonyには便利なコンポーネントが他にもいろいろあるので、これから紹介していこうと思います。

4
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
2