概要
Symfony3でPOST値のルート配列の名前を変更したいときは FormFactory::createNamed()
を使う。
GitHub公式リポジトリ:https://github.com/symfony/form/blob/3.4/FormFactory.php
経緯
EC-CUBE4のプラグイン開発中に、リンク型の決済で50を超えるリクエストパラメータを決済代行会社側のサーバにPOSTするという場面に出会った。
SymfonyのControllerで使う、 createForm()
は、利用するFormTypeがフォームの名前になり、フォーム名の縦配列に実際にPOSTする値が横配列として入る二次元配列になる。
$form = $this->createForm(HogeType::class, $Hoge);
return ['form' => $form->createView()];
array:1 [▼
"hoge" => array:2 [▼ // ← このhogeっていう親の配列が邪魔!!
"key" => "value"
"_token" => "TOKEN"
]
]
これはデータの紐づきをマッピングするためだと思われるが、今回のように所定のフォーマットに従ってリクエストしなければいけないときに困る。
テンプレートファイルにHTMLを直接書いてしまうという方法もありだが、今回のケースではリクエストパラメータが50を超え、受注の状態によっては連番で動的に出力しなければならないパラメータもあったため、なんとかFormTypeで対応したい。
<input type="hidden" name="hoge" value={{ hoge }} /> // これでもいいのだが…
// 以下、最大で999項目(笑)
<input type="hidden" name="param1" value={{ value1 }} />
<input type="hidden" name="param2" value={{ value2 }} />
<input type="hidden" name="param3" value={{ value3 }} />
...
このため、name属性を上手くカスタマイズできないかといろいろ調べた結果、FormFactory::createNamed()
に行き着いた。
使い方
第一引数に任意のフォーム名渡して、createFormと同じように使ってやればよい。
$form = $formFactory->createNamed('form_name', HogeType::class, $Hoge, [
'action' => 'https://example.com' // 第四引数もcreateFormの第三引数と同様に使える。
]);
今回は親の配列が邪魔なので、第一引数に空文字を渡してやる。
$form = $this->formFactory->createNamed('', HogeType::class, $Hoge);
return ['form' => $form->createView()];
array:2 [▼
"key" => "value"
"_token" => "TOKEN"
]
実装を読む
せっかくなので実装を読んでみた。
継承関係を追ってみると、
いつも使っている createForm()
HogeController→ (継承) -> Controller → (トレイト) -> ControllerTrait::createForm()
↓
Symfony/Bundle/FrameworkBundle/Controller/Controller.php
Symfony/Bundle/FrameworkBundle/Controller/ControllerTrait.php
protected function createForm($type, $data = null, array $options = [])
{
return $this->container->get('form.factory')->create($type, $data, $options);
}
このまま追うと…
public function create($type = 'Symfony\Component\Form\Extension\Core\Type\FormType', $data = null, array $options = [])
{
return $this->createBuilder($type, $data, $options)->getForm();
}
public function createBuilder($type = 'Symfony\Component\Form\Extension\Core\Type\FormType', $data = null, array $options = [])
{
if (!\is_string($type)) {
throw new UnexpectedTypeException($type, 'string');
}
return $this->createNamedBuilder($this->registry->getType($type)->getBlockPrefix(), $type, $data, $options);
}
実は今回使ったcreateNamed()
がラップしている createNamedBuilder()
を更にラップしているだけだった!
一方、今回使った createNamed()
HogeController → (継承) → Controller → (DI?) → FormFactory::createNamed()
↓
Symfony/Bundle/FrameworkBundle/Controller/Controllerphp
Symfony/Component/Form/FormFactory.php
public function createNamed($name, $type = 'Symfony\Component\Form\Extension\Core\Type\FormType', $data = null, array $options = [])
{
return $this->createNamedBuilder($name, $type, $data, $options)->getForm();
}
public function createNamedBuilder($name, $type = 'Symfony\Component\Form\Extension\Core\Type\FormType', $data = null, array $options = [])
{
if (null !== $data && !\array_key_exists('data', $options)) {
$options['data'] = $data;
}
if (!\is_string($type)) {
throw new UnexpectedTypeException($type, 'string');
}
$type = $this->registry->getType($type);
$builder = $type->createBuilder($this, $name, $options);
// Explicitly call buildForm() in order to be able to override either
// createBuilder() or buildForm() in the resolved form type
$type->buildForm($builder, $builder->getOptions());
return $builder;
}
なるほど、スッキリした
あとがき
こうやって実装を読むのになれて、Core部分から柔軟に希望の実装を叶えるというテクニックも必要だなと思った。
SymfonyのDIがどういう仕組みで動いているかを知らないので、そのあたりの実装に迫ってみるのもありかもなと思っています。
指摘とかもっといい方法があったら指摘お願いします!!
宣伝
EC-CUBE、Laravelの開発お任せください!!