EasyFormsの入門と応用

# EasyForms

基本的な使い方と応用を紹介します。

# EasyFormsとは

https://github.com/Frago9876543210/EasyForms

Frago9876543210さんが開発しているForm系のライブラリです。
[FormAPI](https://poggit.pmmp.io/p/FormAPI)よりもオブジェクト指向を使った開発を行えることにより、より柔軟にフォームを作ることができます。virionライブラリではなくプラグイン形式で機能を提供しており、初心者でも導入しやすくなっています。

## EasyFormsを使うメリット

`Player#sendForm()`を用いることで、PMMPが標準で提供している自動でフォームのIDの管理をしてくれる機能を使うことができます。また、クロージャの型をチェックしてくれたり、各要素の値を簡単に取得したり、プレイヤーが正しいレスポンスを送らなかった際の処理も個別に書くことができるので簡潔に完成度の高いフォームを作成できます。各フォームクラスがPMMPが提供しているインターフェースを使っていることによりそのままメソッドに渡すことができるので、FormAPIとは違いEasyFormsのインスタンスを取得する必要がないところも大きなポイントです。

## 導入方法
[こちら](https://poggit.pmmp.io/ci/Frago9876543210/EasyForms/~)からプラグインをダウンロードし、`plugins`ディレクトリに格納してください。

## 入門編

EasyFormsを使う上で必要になる基礎と、ModalFrom、MenuForm、CustomFormの作り方をそれぞれ解説していきます。

### 基礎

#### クロージャ

``` PHP
$square = function(int $n) : int {
    return n * n;
}

print $square(10)    //100
```

PHPでは、関数を変数に格納したり、関数の引数・戻り値で用いることができる第一級オブジェクトとして扱えるよう、[クロージャ(無名関数)](https://www.php.net/manual/ja/functions.anonymous.php)と呼ばれる機能を提供しています。
上記では、int型を引数と戻り値に指定しており、その関数にint型のデータを渡すとそのデータの二乗を返すようなクロージャを作っています。変数に代入することで関数を変数で指定できるようになり、`$function(/*引数*/)`のようにクロージャを格納した変数の後ろに`()`と、必要であれば引数を指定することで呼び出せます。

クロージャのスコープの外部のスコープにある変数を使用する場合、

```PHP

$something1;
$something2;

$function = function($something3) use($something1, $something2) : void {
	var_dump($something1, $something2, $something3);
}

$function($something4);
```

のように、`use`句を使うことによって実現できます。`use`句には複数の変数を指定することができます。

第一級オブジェクトである関数は第一級関数と呼ばれ、第一級関数を引数または戻り値で扱う関数は`高階関数`と言います。公開関数を使うことで多彩な表現をすることが可能となり、EasyFormsでも必須となる機能となっています。
クロージャの型をしっかり合わせないと思わぬエラーの原因となるので注意してください。

#### フォームの送信

プレイヤーにフォームを送るには、

``` PHP
/** Player $player */
$player->sendForm($form);
```

を実行してください。

### ModalForm

``` php
use Frago9876543210\EasyForms\form\ModalForm;

/** Player $player */
$player;

$form = new ModalForm(
	"モーダル",    //フォームのタイトル
	"選んでください",    //フォームの説明文
	/**
	 * プレイヤーがボタンを押したときに実行されるクロージャ
	 *
	 * Player $player ボタンを押したプレイヤーオブジェクト
	 * bool $response プレイヤーが選択したボタンに合う真理値
	 */
	function(Player $player, bool $response) {
		if($bool) {
			$player->sendMessage("Yesを選びました");
		} else {
			$player->sendMessage("Noを選びました");
		}
		//$player->sendMessage($response ? "Yes" : "No" . "を選びました")
	}
);

$player->sendForm($form);
```

モーダルフォームでは、`Frago9876543210\EasyForms\form\ModalForm`クラスのインスタンスを生成します。

#### 第三引数(クロージャ)

ModalFormのコンストラクタの第四引数にはプレイヤーがフォームを返信した際に実行されるクロージャを渡します。クロージャの第一引数には返信したプレイヤー、第二引数にはプレイヤーがどちらを選んだかを理論値で渡されます。


### MenuForm

``` php
use Frago9876543210\EasyForms\forms\MenuForm;
use Frago9876543210\EasyForms\elements\Button;

/** Player $player */
$player;

$form = new MenuForm(
	"メニュー",    //フォームのタイトル
	"選んでください",    //フォームの説明文
	[
		new Button("Hoge"/* ボタンの名前 */),
		new Button("Fuga"),
		//...
	],    //選択可能なボタン
	/**
	 * プレイヤーがボタンを押したときに実行されるクロージャ
	 *
	 * Player $player ボタンを押したプレイヤーオブジェクト
	 * Button $button プレイヤーが押したボタンオブジェクト
	 */
	function(Player $player, Button $selected) : void {
		$player->sendMessage("ボタンの表示名: " . $selected->getText());
		$player->sendMessage("ボタンの順番: " . $selected->getValue());
	},
	/**
	 * プレイヤーがフォームをキャンセルしたときに実行されるクロージャ
	 *
	 * Player $player ボタンを押したプレイヤーオブジェクト
	 */
	function(Player $player) : void {
		$player->sendMessage("フォームをキャンセルしました");
	}
);

$player->sendForm($form);
```

メニューフォームでは、`Frago9876543210\EasyForms\forms\MenuForm`クラスと`Frago9876543210\EasyForms\elements\Button`を使います。

#### 第三引数(Buttonオブジェクトの配列)

MenuFormのコンストラクタの第三引数にはButtonクラスの配列を渡します。この配列はメニューフォームで表示されるボタンを表しています。

#### 第四引数(クロージャ)

MenuFormのコンストラクタの第四引数にはプレイヤーがフォームを返信した際に実行されるクロージャを渡します。クロージャの第一引数には返信したプレイヤー、第二引数にはプレイヤーが押した`Button`オブジェクトが渡されます。

#### 第五引数(クロージャ)

MenuFormのコンストラクタの第五引数にはプレイヤーがフォームの返信をキャンセルした際に実行されるクロージャを渡します。

### CustomForm

``` PHP

use Frago9876543210\EasyForms\forms\CustomForm;
use Frago9876543210\EasyForms\forms\CustomFormResponse;

use Frago9876543210\EasyForms\elements\Dropdown;
use Frago9876543210\EasyForms\elements\Input;
use Frago9876543210\EasyForms\elements\Label;
use Frago9876543210\EasyForms\elements\Slider;
use Frago9876543210\EasyForms\elements\StepSlider;
use Frago9876543210\EasyForms\elements\Toggle;


/** Player $player */
$player;

$form = new CustomForm(
	"カスタムフォーム",	//フォームのタイトル
	[
		new Dropdown("ドロップダウン", ["Hoge", "Fuga", "Piyo"]),
		new Input("インプット1", "プレイスホルダー", "デフォルト値"),
		new Input("インプット2"),
		new Label("ラベル"),
		new Slider("スライダー", 0.0/* 最小値 */, 100.0/* 最大値 */, 1.0/* 一目盛, 50.0/* デフォルト値 */),
		new StepSlider("ステップスライダー", ["Hoge", "Fuga", "Piyo"]),
		new Toggle("トグル", true/* デフォルト値 */)
	],	//要素の配列
	/**
	 * プレイヤーがフォームに返信したときいに実行されるクロージャ
	 *
	 * Player $player ボタンを押したプレイヤーオブジェクト
	 * CustomFormResponse $button 返信の内容
	 */
	function(Player $player, CustomFormResponse $response) : void{
		$dropdown = $response->getDropdown();
		$player->sendMessage("あなたが選択したもの: {$dropdown->getSelectedOption()}");

		$input = $response->getInput();
		$player->sendMessage("インプット1にあなたが入力した文字列: {$input->getValue()}");

		$input = $response->getInput();
		$player->sendMessage("インプット２にあなたが入力した文字列: {$input->getValue()}");

		$slider = $response->getSlider();
		$player->sendMessage("あなたが選択した数字: {$slider->getValue()}");

		$stepSlider = $response->getStepSlider();
		$player->sendMessage("あなたが選択したもの: {$stepSlider->getSelectedOption()}");

		$toggle = $response->getToggle();
		if($toggle->getValue()) {
			$player->sendMessage("あなたが選択したもの: True")
		} else {
			$player->sendMessage("あなたが選択したもの: False")
		}
	},
	/**
	 * プレイヤーがフォームをキャンセルしたときに実行されるクロージャ
	 *
	 * Player $player ボタンを押したプレイヤーオブジェクト
	 */
	function(Player $player) : void {
		$player->sendMessage("キャンセルしました");
	}
);

$player->sendForm($form);
```

カスタムフォームでは`Frago9876543210/EasyForms/forms/CustomForm`クラス、`Frago9876543210\EasyForms\forms\CustomFormResponse`クラス、`Frago9876543210/EasyForms/elements`名前空間以下の`Button`を除くクラスを使います。

#### 第二引数(Elementクラスを継承したオブジェクトの配列)

`CustomForm`のコンストラクタの第二引数には`Element`クラスを継承したオブジェクトの配列を渡します。この配列はカスタムフォームで表示される要素を表しています。

#### 第三引数(クロージャ)

`CustomForm`のコンストラクタの第三引数にはプレイヤーがフォームに返信した時に実行されるクロージャを渡します。クロージャの第一引数には返信した`Player`オブジェクト、第二引数には`CustomFormResponse`オブジェクトが渡されます。\<Element>に欲しい要素のクラス名を入れた`$response->get<Element>()`を実行することで`CustomForm`の生成時に配列で指定した要素が返されます。同じ種類の要素が複数ある場合は、`get<Element>()`を実行するたびにその種類の要素の順に要素が返されます。

##### 要素
- `Dropdown`
- `Input`
- `Label`
- `Slider`
- `StepSlider`
- `Toggle`

#### 第四引数(クロージャ)

CustomFormのコンストラクタの第四引数にはプレイヤーがフォームの返信をキャンセルした際に実行されるクロージャを渡します。

---

以上で３種類のフォームの基本的な使い方を紹介しました。

## 応用編

入門編だけでも十分フォームの作成を行うことができますが、EasyFormではオブジェクト指向の性質を利用することによって柔軟な書き方ができるようになります。また、プレイヤーにとって使いやすいフォームを作ることによって、ユーザーが得られる満足度が向上します。この応用編ではそれらの一部を解説した上で紹介します。

### 要素のカスタム

`MenuForm`と`CustomForm`で`Element`クラスを継承した要素を使っていましたが、それらの要素をさらに新しいクラスに継承させることでより使いやすくすることができます。

例えば`MenuForm`でサーバーに参加しているプレーヤーの一覧を表示させる際、EasyFormsで用意されている要素のみで作る場合は

``` PHP
$players = array_values(Server::getInstance()->getOnlinePlayers());
$buttons = [];

foreach($players as $index => $player) {
	$buttons[$index] = new Button($player->getName());
}

$form = new MenuForm(
	"プレイヤーを選んでください",
	"",
	$buttons,
	function(Player $player, Button $button) use($players) : void {
		$player->sendMessage("あなたが選択したプレイヤーは" . $players[$button->getValue()]->getName() . "です");
	}
);
```

のように、`getOnlinePlayers()`で返される配列をButtonオブジェクトの配列の順番と対応した配列に変換した上で`use`句を使ってクロージャに渡し、クロージャの中でもPlayerオブジェクトの配列の中からボタンの順番に合うプレイヤーオブジェクトを取得する必要があります。

次に、新しく`Button`クラスを継承した`Player`オブジェクトを保持するクラスを作成作成すると、

``` PHP

class PlayerButton extends Button() {
	/** Player $player */
	protected $player;

	function __constract(Player $player) {
		$this->player = $player;
		parent::__constract($player->getName());
	}

	function getPlayer() : Player {
		return $this->player;
	}
}

foreach(Server::getInstance()->getOnlinePlayers() as $player) {
	$buttons[] = new PlayerButton($player);
}

$form = new MenuForm(
	"プレイヤーを選んでください",
	"",
	$buttons,
	function(Player $player, PlayerButton $button) : void {
		$player->sendMessage("あなたが選択したプレイヤーは" . $button->getPlayer()->getName() . "です");
	}
);
```

のように、`PlayerButton`オブジェクト生成時にプレイヤーオブジェクトを渡すことで、簡潔に`Button`オブジェクトの配列を作り、クロージャ内でも新しく作成した`getPlayer()`メソッドで`Button`オブジェクトに対応した`Player`オブジェクトを得ることができます。

## 最後に

EasyFormsおすすめです。
若干書き足りないところがいくつかありますが、とりあえずひと段落したので投稿しました。気が向いたら追記していきます。

EasyFormsについての質問がある方はプラ集でお願いします。僕が気づけばその場で回答した後、この記事にまとめたいと思います。