Issei.M's Techlog

Web/iOS エンジニアの僕が技術関連のメモ等をつらつらと。主に Symfony について書いています。

[Symfony][Form] フィールドタイプはよく考えて決めよう

Symfony2を使い始めて2年くらい経ちましたが、未だ全容をつかみきれない機能の1つがForm。今回はそんなFormのフィールドタイプについて話します。

通常、Symfony2のFormではフィールドタイプを次のようにして定義します。

$builder->add('addressLine', 'text', ['label' => '住所']);
$builder->add('latitude', 'hidden');
$builder->add('longitude', 'hidden');

テンプレートはこんな感じ。

{{ form_start(form) }}
  {{ form_row(form.addressLine) }}
  <button type="submit">送信</button>
{{ form_end(form) }} {# hiddenフィールドはここで自動出力されます #}

この場合、画面上には「住所」を入力するテキストフィールドだけが表示されますが、サブミットの直前に住所の内容からJavaScriptでGoogleMap APIから取得し、hiddenタイプの緯度・経度に位置情報が入る仕様だとします。(実装例は割愛)

サブミットを行うと、データは次のようにマップされます。

$form->handleRequest($request);
var_dump($form->getData());

/*
 * ※DTOは使わないのでデータは配列です。
 * [
 *   'addressLine' => string '東京都渋谷区...',
 *   'longitude'   => string '35.658534',
 *   'latitude'    => string '139.701330',
 * ]
 */

緯度経度がfloatではなくstringとなっています。当然といえば当然なのですが...。
因みにはじめからnumberタイプを定義していれば、適切にfloat型としてマップされます。

$builder->add('latitude', 'number');
$builder->add('longitude', 'number');

※但しこの場合はinput[type=text]として画面上にUIが表示されます。

クライアントサイドの都合で安直にhiddenを使うと今回のような悲劇を生みます。(´・ω・`)
解決法は色々ありますが今回のケースではオーソドックスに DataTransformer を使うのが良さそうです。

$builder->add(
    $builder
        ->create('latitude', 'hidden')
        ->addViewTransformer(new NumberToLocalizedStringTransformer())
);
$builder->add(
    $builder
        ->create('longitude', 'hidden')
        ->addViewTransformer(new NumberToLocalizedStringTransformer())
);

この他にDataTransformerを使わずにnumberタイプを定義しておいて、テンプレート側でhiddenにすると言う方法もあります。

{{ form_start(form) }}
  {{ form_row(form.addressLine) }}
  {{ form_row(form.longitude, {type: 'hidden'}) }}
  {{ form_row(form.latitude, {type: 'hidden'}) }}
  <button type="submit">送信</button>
{{ form_end(form) }}

この場合form_endでのhiddenフィールド自動出力の恩恵が受けられませんが、最終的なUIがテキストボックスであればこちらの方が良いかもしれません。
Symfony2が予め用意しているフィールドタイプは、numberのようにデータを適切に変換してくれる物が多いからです。

何はともあれこれでデータは適切にfloatに変換されました。めでたしめでたし。

[Symfony] Doctrine の Repository をサービスコンテナに登録する

AcmeBlogBundle\Repository\BlogRepositoryをサービスコンテナに追加:

# @AcmeBlogBundle/Resources/config/services.yml
parameters:
    acme_blog.repository.blog.class:  Acme\BlogBundle\Repository\BlogRepository

services:
    acme_blog.repository.blog:
        class:           '%acme_blog.repository.blog.class%'
        factory_service: doctrine.orm.entity_manager
        factory_method:  getRepository
        arguments:       [ 'AcmeBlogBundle:Blog' ]

要するに $doctrine->getRepository('AcmeBlogBundle:Blog'); の処理をサービスとして定義しているだけです。

// returns true
$container->get('acme_blog.repository.blog') === $doctrine->getRepository('AcmeBlogBundle:Blog'); 

VagrantでNFSを使うときのsudoパスワード入力を省略する方法

Vagrantの共有ディレクトリ設定にNFSを使用している場合、vagrant up時に毎回sudoのパスワードを入力しないといけません。
なんとかならないものかとググっていたらありました。

Vagrant NFS Shares without a password

どうやら/etc/sudoersに数行追加するだけのようです。以下のシェルスクリプトを1度だけsudoで実行すれば追加が行えます。

試しにvagrant upしてみたら無事パスワード無しで起動することができました。

[Doctrine] MySQL の TEXT 型を使用する

※Doctrine 2.3 以下でのお話です。

MySQL のカラムにTEXT型を使おうとして普通に type を text にするとLONGTEXT型になってしまいます。

/**
 * @ORM\Column(type="text")
 */
private $title;

※コンソール結果

$ php app/console doctrine:schema:create --dump-sql
> CREATE TABLE ... title LONGTEXT NOT NULL, ...

どうやら length を 65532 にするとTEXTになるようです。

/**
 * @ORM\Column(type="text", length=65532)
 */
private $title;

※コンソール結果

$ php app/console doctrine:schema:create --dump-sql
> CREATE TABLE ... title TEXT NOT NULL, ...

因みに 255TINYTEXT, 16777215MEDIUMTEXT, それ以上はLONGTEXTになるみたいですね。

詳しくは Doctrine/DBAL/Platforms/MySqlPlatform を御覧ください。

追記

※バージョン2.4からはTEXTの長さが 65535 になるようです。
Doctrine/DBAL/Platforms/MySqlPlatform

[AWS] Root Device が ebs の EC2 から無停止で AMI を生成する

通常 Root Device が ebs の EC2 から AMI イメージを生成する際、インスタンスを一時停止する必要がありますが(自動で停止します)実は無停止で生成する方法も存在するので今回はその手順をご紹介します。

※今回はRightImage_CentOS_6.4_x64_v13.5.0.2_EBS (ami-0f2bbc0e)から作った EC2 をベースとします。

インスタンス情報の確認

まずは管理コンソールの Instances からインスタンスの情報を確認をします。

f:id:issei_m:20130826125148p:plain

Kernel IDRoot Device の値は後で使うので控えておいて下さい。

スナップショットの撮影

次に Volumes を開き、インスタンスのルートデバイスとなっているボリューム(例では sda に接続されている)を選択し、右クリックのメニューから「Create Snapshot」を実行します。

f:id:issei_m:20130826125805p:plain

イメージの作成

次に Snapshots を開き、先ほどのスナップショットを確認します。Progress が available (100%) になったら右クリックのメニューから「Create Image from Snapshot」を実行します。

f:id:issei_m:20130826130039p:plain

AMI 作成のダイアログが表示されます。
Name は適当に入力して下さい。Architecture は 64bit の CentOS を使用しているのでx86_64を、 Root Device NameKernel ID には先ほど控えた値を入力します。(※ Root Device がsdaの場合は/dev/sdaと入力します。)

続いて画面中部のタブから「Instance Store Volumes」を選択し、Instance Store, Device 共にデフォルト(今回は 0sdb) のまま「Add」 ボタンを押します。

f:id:issei_m:20130826130952p:plain

すべて完了したら設定をよく確認し、問題がなければ「Yes, Create」を押します。
(※ Ephemeral Disk を追加していないとインスタンスが正しく起動しないので注意。)

AMI を確認する

AMIs から AMI を確認します。Status が available になっていれば準備完了。

f:id:issei_m:20130826131134p:plain

後はここから EC2 を起動するだけです。
尚、今回の作業は全て API Tools からも行えるはずなので自動化も可能です。