質問箱をOSSにしてみた


この記事では、私の作った質問箱のクローンサイトであるクローン質問箱の実装に関することを記事にします。
本家の質問箱はRuby on Railsで実装されているはずですが・・・(多分)
本記事で紹介するプログラムは完全なPHP製のプログラムです。
* また、本記事は本家質問箱とは一切の関係が無いので、ご了承ください。
* デザインやプログラムに関してもコピーはしておらず、全てオリジナルです。
* レイアウトは似せて作成しています。

はじめに

今回、質問箱のクローンサイトを構築した。

なぜ、プログラムを公開したのかというと、質問箱関連サービスが多々出てきているにも関わらず、どこの運営元もプログラムをオープンにしていないからである。
これは非常に勿体無いと思っている。
世の中はオープンイノベーションの時代になり、多くの資産を再利用する時代に入っている。

にも関わらず、基本機能がほとんど変わらないサービスを1から作るのはコスト的にあまりよく無い。
また、Railsの様なフルスタックフレームワークは拡張が柔軟に行えなかったり、学習コストの面で初心者が参加しにくい部分が多々ある。
なので、今回は私が愛用している軽量フレームワークであるSlim3で実装した。
*Silmに関する記事はこちら

また、本プログラムは作成期間が短く、テストコードもまだ十分にかけていないため、プルリクや問題報告のissueを大歓迎している。

本プログラムの対象

このプログラムはエンジニアの方をはじめ、特にプログラミング初心者やプログラミング未経験者が質問箱の様な匿名メッセージサイトに対する新たなイノベーションを起こせたら嬉しいと思い作成した。
基本的にはPHP7.1の動作環境があれば動くので、WordPressの様に簡単に誰もが導入できる。

実装について

ここからは実装のことを永遠に話していく。

環境構築・Docker

環境構築を行う際に以前は「Chef」などの自動化ツールを利用していた時代もあったが、近年では、Dockerでそれを実現して、ファストアーキテクチャ的な思想でボコボコ使い捨てのインスタンスを立てるのがちょっとしたトレンドになっている。
このサービスもホスト上に構築する必要はそれほどない。
今回は完全にDockerに負んぶに抱っこ状態だが、基本的には以下の3行で全てが完結する。

Setup.sh
docker-compose build
docker-compose pull
docker-compose up -d

データベースとテーブル構造など

データベース

今回はデータベースにMySQLを採用した。
今後はキャッシュサーバとしてKVSを使いたいと考えているが、どのように実装すれば良いのかを理解していない為、とりあえずはRDBのみでの実装になっている。

テーブル構造

チーブル構造は以下の図のようになっている。
Usersテーブルはユーザ一人にひとつづつ割り当てられる。
Messagesテーブルは質問一つに対してひとつ割り当てられる。
それぞれのカラム属性についての説明は省略するが、UsersテーブルのidとMessagesテーブルのuser_idが紐付いているので、リレーショナルになっている。
idはどちらのテーブルもオートインクリメントされる。

マイグレーション

マイグレーションはPhinxというライブラリを利用する。
Phinxを利用すれば自動的にテーブルと属性を追加してくれる。

ORマッパー

ORマッパーはDBからデータを取得したり、挿入したりする際にSQL文を書かなくてもオブジェクトを操作するだけで自動的に内部でSQL文を発行してくれる。
主要な脆弱性に対応しているので、無駄な神経を使わなくてもある程度の脆弱性対策になる。
今回はそんなORマッパーの中でも軽量なParisライブラリを利用する。
ParisはIdiormというORマッパーをオーバーラップして作られたActiveRecordなライブラリである。
最小限の機能しか提供されていないが、今回のサービスには十分すぎるレベルである。

画像生成

今回のサービス構築で一番手こずったのはツイッターに投稿する際の画像を生成する部分である。
画像に文字を挿入して生成するプログラムを書いたことがなかったので、色々調べていたが、本家の質問箱がImageMagickを利用して開発されているという話を小耳に挟んだので、ImageMagickで作ってみようと思って調べてみたが、ImageMagickの本来の機能ではなかなかカバーできない部分が多買った。
おそらく、今回実装した方法で本家の質問箱も実装しているのだろうという実装になっているが、もっと効率的な実装方法が存在するかもしれないので、もしあればご教授いただきたい。

実装方法

今回は以下の3つのImagickインスタンスを生成した。
headerとfooterはそれぞれ前もって縁のついた画像をresources/img ディレクトリに用意しておき、それをコンストラクタに渡して、インスタンスを生成する。
messageImageは特に画像をコンストラクタに渡さずにインスタンスを生成する。

Imagick.php
$header = new Imagick(__DIR__ . '/../resources/img/q-head.png');
$footer = new Imagick(__DIR__ . '/../resources/img/q-footer.png');
$messageImage = new Imagick();

文字数をカウントし、もし21行以上改行がなかった場合は改行コードを挿入する。
こうして、改行コードを21文字以内に必ず一つ含んだテキストデータにいくつ改行が含まれているのかを計算し、行数に応じてmessageImageの高さを決める。

message.php
$message = $this->getTextUtil()->checkMessageText($message);
$stringHeight = $this->getMessageImageHeight(substr_count($message, "\n"));

あとは、幅、高さ、背景色、画像フォーマットを指定し、ボーダーを左右に25pxずつ描画する。

messageImageSetting.php
$messageImage->newImage(550, $stringHeight, new ImagickPixel('white'));
$messageImage->setImageFormat('png');
$messageImage->borderImage('#FFB38A', 25, 0);

この後に、それぞれのImagickインスタンスを
header -> messageImage -> footer
の順番に結合していく。

基本的な実装方法は上記で説明した通りである。
この方法では、文字数を数える部分や、画像を合成する部分で多くのリソースを消費してしまうので、処理にある程度の時間がかかってしまうことに多くの問題がある。

テンプレートエンジン

テンプレートエンジンについては今回はLaravel採用のテンプレートエンジンであるBladeを採用した。
Slimは自前でDIコンテナをもっているので、Settings.phpにslim-blade-viewを追加する。

テストコード

テストについてはUnitテストを行なっている。
今回はPHPUnitとDBUnitを利用して、ControllerやUtil、Serviceクラスのテスト、RepositoryでDBを操作した時のテストを実施している。

テストコードを書くには、オブジェクトをモック化できるようにする必要がある。
プログラム自体をモック化できるように疎結合なコードにしなければならない。
疎結合なプログラムはできるだけインスタンスを生成することなく、コーディングを行う必要がある。

コードカバレッジ100%ではないが、主要なメソッドの多くはテストがかけている。
問題なのは、一つのサービスについてテストコードを書いた経験がなく、Controllerなどでどのようなテストコードを書いたらしっかり動作していると言えるのか計り知れない部分が多いことである。

もし、テストでもおかしな部分が存在した場合はissueなどをいただけると嬉しい。
また、Imagickを利用したImageUtilクラスに関してはテストを書いていない。
これは、Imagickクラスをモック化したとして、Imagickから生成される画像が正しい画像であるのかを単純にモック化しただけではテストできないからである。
Imagickクラスのテスト方法も調べてみたが、有力な情報が見つからなかった。
これについても、issueやプルリクを大歓迎している。

セキュリティ

セキュリティに関しては基本的にはリクエストの処理まではフレームワーク依存である。
XSS脆弱性は全て潰す為にBladeを利用し、ユーザ入力の表示には全てエスケープを行なった。
csrf対策は入力ホームがある部分には全てにcsrfトークンを追加した。
DB周りについてはORマッパーを利用したので、基本的なSQLインジェクション脆弱性はカバーできてるはずである。
まだまだ基本的な脆弱性しか対応できていないので、他の部分についても細かくみていきたいところである。

おわりに

今回はDockerでSlimフレームワークを利用してまだまだな段階ではあるが、質問箱のクローンを作る過程でImageMagickをはじめ色々なことを学ぶことができた。
まだまだ、わからない実装部分もあるが、それなりに質問箱をオープンソースにするという目標は達成できた。
ここからさらなる発展や、このプログラムを元にして新たなプロダクトが開発されると個人的には非常に嬉しく思う。

デモサイト

本記事で紹介したクローン質問箱はデモサイトとして以下のURLに公開している。
動作等を検証することができる。
Twitter連携をすると実際にTwitterに質問の解答をした場合にツイートされるので注意して利用して欲しい。
* 現在Twitter-APIの申請をしていないので、でもサイトでログインできないため、リンクを削除しました。

参考

・Peing(質問箱)
https://peing.net/ja/

・Silmに関する記事
https://qiita.com/nocturnality/items/b55e18a8361b3ff882b5