3月16日に開催された、第29回北海道開発オフに参加しました。
ちょっと体調を崩してしまい、14時からの参加。
今回は先月の北海道アイディアソン&ハッカソンで作りはじめたプロダクトの実装の続きです。
QRコードを出力できるようにする
今回のアプリでは、特定のURLを作成し(URLを作るところはこれから)、それをQRコードで画面に表示する機能が必要になります。
今まで仮でQRコード画像を貼付けていたところを、動的にQRコード生成するよう実装をしました。
プロダクトの選定
これは開発オフが始まる前からちらちら見ていたのだけど、最終的に使えそうなのはこの2つのどちらかかな、となった。
- Image_QRCode
[良い]2012年にも更新してる
[不安]PEARライブラリ導入方法(vagrantの設定まで変えたくないなあ)
[不安]Release state:alpha(0.1.2) - PHP QR Code
[良い]サイトの印象が良かった
[良い]使い方簡単そう、オプションも充分
[良い]CakePHPへの導入も楽そう(Vendorの配下に入れれば大丈夫そう)
[不安]最終更新が2010年頃
今回はPHP QR Codeを使う事にしました。
Cake PHPでPHP QR Codeを使ってQRコードを生成する
- ライブラリのダウンロードと配置
まず、PHP QR Codeのページからソース一式をダウンロードしてきて、app/Vendor 配下にフォルダごと置いた。 - ライブラリのインポート
qrlib.phpをincludeすれば、全ての機能が使えるようです。今回は使う場面が1箇所だけなので、該当のControllerに次の記述を追加。App::import('Vendor', 'phpqrcode/qrlib');
- Viewでの実装
QRコードは動的にバイナリを生成して(画像は保存せずに)、表示が終わったら破棄される形にしたかった。
PHP QR Codeでは、バイナリを直接出力してくれるメソッドがあったのでviewの中でそれを使用しました。ob_start(); QRCode::png('https://www.google.co.jp/', null, 'H', 5, 2); $img_base64 = base64_encode( ob_get_contents() ); ob_end_clean(); echo $this->Html->div('qrcode', "<img src='" .sprintf('data:image/png;base64,%s', $img_base64). "'/>");
ハマったところとしては
- ob_start()-ob_end_clean()を使わなければ、バイナリが直接htmlに出力されてしまう
これに長い事はまりました。最後はstackoverflowで解決。
出力されたバイナリを内部バッファに保存しておき、ob_get_contents() を使って変数への代入をする必要があったのでした。
内部バッファという概念ははじめてで、ちょっと理解が足りない部分がまだある。 - オプションをつけたいけど第2引数がファイル名だ….
第3引数以降が、サイズやクォリティのオプションなのだけど、第2引数をどう無視すればいいのか…(サンプルコードがあまりない)
結局、第2引数にnullを入れたらあっさり解決してしまった。 - Helperが使えない
バイナリで出した画像はbase64形式にエンコードしてhtmlに表示させるのだけど(ここだけは最近業務でやった経験があったので、すんなり行けた)、HtmlHelperを使ってimageタグを作ると、”data:image/png;”の記述がエスケープされてしまった。
たぶんエスケープを無視する方法があるのだろうけれど、直接imageタグを記述する事で回避してしまっている。
- ob_start()-ob_end_clean()を使わなければ、バイナリが直接htmlに出力されてしまう
とりあえず、時間内にviewにQRコードを表示させる事ができたので満足です。
汚いソースコードをなんとかする
私は主にview側を担当して書いていたのですが、知識が足りないのもあり、ほぼ生のHTMLにphpのコードを埋め込んで書いていたのですね。
これが、見づらい。
他のソースを見る余裕もでてきて、Helperを使えばかなり綺麗になるだろうということで、@makiesさんに教えてもらいながらリファクタリングをしました。
例えば次のコード。これは「サイコロをユーザが振った後、出た目によってサイコロ画像と目的地を表示するjavascriptコードを出力している」ソースコードになります。
【before】
正直「動いたー!」の後、思考停止して何も修正していなかった状態。
echo("$('#dice').html('" . $this->Html->image("dice/choiced_dice_". $dice_num .".png", array('width' => '240px', 'height' => '240px')) . "');"); echo("$('#myModalLabel').html('目的地は<b style=\"font-size:20pt\">". $place ."</b>です。');");
【after】
文字列を決定する変数を別に定義する(jQueryのコードの可視性が上がる)、HtmlHelperを利用する(直接bタグ書かない、htmlタグの出力中に文字列連結しているのが汚い)をしたのが次のコード。
$str = sprintf(__('目的地は %s です。'), $this->Html->tag('b', $place, array('style' => 'font-size:20pt'))); $place_image = $this->Html->image(sprintf('dice/choiced_dice_%s.png', $dice_num), array('width' => '240px', 'height' => '240px')); echo("$('#dice').html('{$place_image}');"); echo("$('#myModalLabel').html('{$str}');");
Helperのマニュアルを見て、適用できるところを直すのと、sprintfを使う事で、後から読んでも混乱しないソースコードに近づいた。
あとはローカライゼイションに対応できるように、静的テキストを「__(‘テキスト’)」の形式で記述するよう書き換えた。これはCakePHPがもっているローカライゼイションの機能を使えるようにするための修正。
【before】
<h3 id="myModalLabel">なにがでるかな</h3>
【after】
<h3 id="myModalLabel"><?php echo __('なにがでるかな'); ?></h3>
@makiesさんが書いているソースコードが生きたお手本なので、時間を見てリファクタリングも進めていきたいなー。
今は、postgreSQLからMySQLにDBを変更したのだけど、自分の環境でうまく切り替えられなくてはまってます。
4月も1回くらい集まってプログラミングできたらいいな。
>たぶんエスケープを無視する方法があるのだろうけれど、直接imageタグを記述する事で回避してしまっている。
こんな感じでいけると思いますよ。
これはリンクですけど、imgも同じように出来ます。
ストロングタグなどタグを一緒にくくってしまいたい時は、画像リンクを作成する方法と同じようにする
Html->link(‘【再販】‘ . $item[‘Item’][‘name’], array(‘controller’=>’FrontItems’, ‘action’=>’detail’, $item[‘Item’][‘id’]), array(‘class’=>’link1’, ‘escape’=>false), null); ?>
エンドウさん
ありがとうございます!
‘escape’=>false
というプロパティがポイントっぽいですね。
試してみます!!
Html->image ではエスケープに対応していないみたいでした(TT)
Html->linkではエスケープできるのですね。
cake/lib/Cake/Helper/HtmlHelper.php も確認してみたのですが、そういうオプションがない(= base64のバイナリを直接表示する方法には対応していない)ようです。