あーさーの備忘録

ゆっくり自由に生きてます

「ぶらつき学生ポータル」を作った

ぶらつき学生ポータル完成

前クール覇権アニメDYNAMIC CHORDの原作ゲーム(闇が深い)のプレイで鬱加速中のあーさーです。この世界の高校生闇が深すぎる。

某サークルを引退して暇になったので、先月下旬から「ぶらつき学生ポータル」の開発をしていました。これは、アグリコラというボードゲームのプレイ記録をまとめるためのサイトです。あまりに暇すぎて、作り始めてから1ヶ月経たずに完成してしまいました。とはいえ、フロントエンドからバックエンドまで1人で書いて、さらにサーバーも1人で立ててという感じだったので、チーム制作に慣れた身体にはしんどいものがありました。フロント書ける人ってすごい。僕はフロントの知識が乏しいので、CSSフレームワークに頼りました。

完成したサイトがこちら。

トップページ - ぶらつき学生ポータル

一応開発環境というか言語は下の通り。

  • Language: PHP 7.1
  • PHP Framework: FuelPHP 1.8.0
  • CSS Framework: Materialize 0.100.2

ぶらつき学生ポータルの機能

このサイトの機能は以下の通りです。

ユーザ登録・認証

誰でも編集できるようにするのもアレなので認証を導入しました。僕がユーザに登録用のURLと合言葉を教え、合言葉を入力することでユーザ登録ができるようにしました。認証はFuelPHPのAuthパッケージを使用しています。ログインするとマイページからプロフィールの編集やスコアの入力などができるようになっています。

スコア入力・表示

ゲームをプレイする前に、代表者がプレイヤーのIDやレギュレーションなどを入力します。すると、各プレイヤーのマイページ画面からスコア入力画面に飛ぶことができるようになります。アグリコラのスコア計算アプリとこのサイトに2回スコアを入力するのは面倒なので、各カテゴリーの点数を入力してボタンを押すと合計点を計算するようにしました。スコア入力だけでなく、盤面の画像をアップロードしたり、コメントを入力したりできます。入力したスコアは記事として公開されます。

使用したカードの効果を表示

スコア入力時に使用したカードの番号を入力することで、スコア表示ページでそのカードの番号だけでなく名前や効果まで見ることができるようにしました。アグリコラには様々な拡張が用意されていて、我々は基本的にそれらをすべて混ぜて使用しています。拡張のほとんどは日本語版が出ていないので、日本語訳する必要があるのですが、界隈によって表記ゆれや飜訳の違い、エラッタなどが存在します。そのため、私たちで使用している訳を載せつつ番号を載せることで界隈外の人にも伝わるようにしています。

ぶらつき学生ポータルの今後

とりあえずすぐに運用したかったので、最低限の機能だけ実装してさっさとdeployしてしまいました。でも、まだまだ追加できる機能はあります。各カードの詳細ページを作って、カード評価やエラッタ、活用法について議論できる場にしたら面白いなぁと思っています。ページを作ること自体は、URLからカード番号をパラメータに持ってきてそのカードのデータをDBから取得して…という感じなのですぐできそうです。評価欄やコメント欄を作るともう一手間二手間かかりますが。

また、プレイ回数が増えてきたら、スコアや順位、使われているカードなどで統計を取ることができます。拡張入りでもやっぱり4・5番手は弱いのかとか、この人は安定して良い点が取れているとかが分かります。有意な結果を得るためにはたくさんプレイしないとダメですが。そんなことよりダイナーやりたい

アグリコラには旧版と新版(リバイズドエディション)があって、リバイズドの方のデータはまだ入れていないのでまだ使えません。カード番号がバッティングすると嫌だなぁと思ったので保留しました。実際はCデッキ以外は被らなさそうですね。そもそもCも一般的にはCzデッキと呼ばれているそうですし、置き換えるだけです。

作った感想

やっぱりMaterializeは楽にモダンなサイト作れるのでいいですね。今回はなぜかドキュメント通りにマークアップしてもメニューがちゃんと動かなかったり(z-indexの数値がおかしい)、フォントを変えたかったりしたので一部書き換えましたが、CSS書かなくて済むのはかなり大きいです。

パフォーマンスのことはほとんど考えてません。PageSpeed Insightsで99点取ったって記事書いたのが嘘のようになにもしてないです。一応70点台だったけど。faviconすらFuelPHPのデフォルトの奴使ってるのはさすがにアレなので、気が向いたらスピードアップも含めてちょっとずつアプデしようかなと思います。PagenationもOFFSET使ってるのでデータ量が増えれば増えるほど遅くなります。頭が悪くてOFFSET使わないSQLの書き方が理解できませんでした。理解できないことを鵜呑みしてコピペするのは信条に反するので、理解できるようになることを祈るばかりです。アルゴリズムとか苦手なんですよね。完全に文系プログラマーやってます。

とりあえず、完成してよかった!企画倒れしなくてよかった!そんな自分への拍手を込めて新しい乙女ゲーを今日もポチるぞ。

FuelPHPのValidationにクロージャを複数使う

FuelPHPのValidationにクロージャを使う

FuelPHPでは、クラスを作りメソッドを定義することでオリジナルのValidationルールを作ることができます。ルールに対するメッセージ文も、APPPATH/langのファイルを編集することで定義できます。でも、あるページだけでしか使わないルールをいちいち作るのは面倒。

ということで、Validationインスタンスごとルールを追加できる方法の登場です。

qiita.com

普通は上に挙げた記事通りやれば上手くいくのですが……

クロージャを複数使った場合の挙動

上の記事では、Validation::add_rule()にクロージャを渡すことによって独自ルールを定義しています。これを複数回行ったときに、ある不具合が生じます。

たとえば、アカウント作成フォームを作るとしましょう。

アカウント作成フォームに求められる要件は以下の通りです。

  • IDがユニークであること
  • メールアドレスがユニークであること

同じIDで複数回登録できたら区別がつかなくなってしまいますからね。

ということで、前述の記事を参考に、以下のようにルールを作ったとします。(本当はIDにvalid_stringがあったりするのでしょうが必要最低限ということで)

<?php
$val = Validation::forge();
$val->add('id', 'ID')
    ->add_rule('required')
    ->add_rule(function($id) {
        $record = DB::select()
            ->from('users')
            ->where('id', '=', $id)
            ->execute()
            ->as_array();
        if ($record !== []) {
            Validation::active()->set_message('closure', 'すでに同IDのユーザーが存在します。');
            return false;
        }
        return true;
    });
$val->add('email', 'メールアドレス')
    ->add_rule('required')
    ->add_rule(function($email) {
        $record = DB::select()
            ->from('users')
            ->where('email', '=', $email)
            ->execute()
            ->as_array();
        if ($record !== []) {
            Validation::active()->set_message('closure', 'このメールアドレスはすでに登録されています。');
            return false;
        }
        return true;
    });

ちなみに、Validation::active()を使っているのはスコープの関係です。useで$valを渡せば動くんじゃないかな(未検証)。

このようにすると上手くいくように思えますが、両方のValidationに引っかかった場合、後のメッセージが前のメッセージを上書きしてしまい、同じメッセージが2回表示されてしまいます。この方法ではset_message()の際にクロージャを区別できないというわけです。

解消方法

困ったのでドキュメントを読んだところ、下記ページにこんな記述がありました。

Validation Errors - Classes - FuelPHP Documentation

If you want to give them custom names instead you can do that like

<?php
// Add a rule which checks if the input is odd
// It can either use ->set_message('odd', ':label is not odd.') or use a lang key 'validation.odd'
$field->add_rule(array('odd' => function($val) { return (bool) ($val % 2); }));

先ほどクロージャを渡していたところに、array((ルール名) => (クロージャ))を渡すことでルールに名前をつけることができるということですね。

これを踏まえて先ほどのコードを書き換えます。

<?php
$val = Validation::forge();
$val->add('id', 'ID')
    ->add_rule('required')
    ->add_rule(['unique_id' => function($id) {
        $record = DB::select()
            ->from('users')
            ->where('id', '=', $id)
            ->execute()
            ->as_array();
        if ($record !== []) {
            Validation::active()->set_message('unique_id', 'すでに同IDのユーザーが存在します。');
            return false;
        }
        return true;
    }]);
$val->add('email', 'メールアドレス')
    ->add_rule('required')
    ->add_rule(['unique_email' =>function($email) {
        $record = DB::select()
            ->from('users')
            ->where('email', '=', $email)
            ->execute()
            ->as_array();
        if ($record !== []) {
            Validation::active()->set_message('unique_email', 'このメールアドレスはすでに登録されています。');
            return false;
        }
        return true;
    }]);

こうすると、クロージャ同士がメッセージを上書きせずに済みます。

ちなみに、この方法ならメッセージをクロージャ内で定義する必要がなく、クロージャの外側で$val->set_message()としてあげるともう少し綺麗に書けるのかなと思います。というか元の方法でも、クロージャが1つしかないのなら外側で定義できる……はず。

こういう技術系記事ってQiitaでやったほうがいいのかもしれないね。

東日本大震災

急に思い出した話を書きます。

7年弱前に起きた東日本大震災。 (もうこんなに昔なんですね)

当時僕は中学2年生で、学校の体育館で3年生を送る会を行っていました。 突然の長い揺れ。ガラスの割れる音。

急いで教室に戻りテレビをつけると、大津波警報の文字が。 震源から数百km離れた沼津でも、保護者引き渡しとなりました。 家に帰るための国道は津波警報のため通行止めになりました。

中学の体育館は、新しいものができるまで立ち入り禁止になりました。 仙台の親戚の家は水たまりを残して跡形もなくなりました。 電池などの物資が不足し、大阪の友人が送ってくれました。

震災から1週間経ったある日、 国語の授業のはじめに先生がこんなことを言いました。

「大震災の報道を見て、 非日常に興奮してしまっている自分がいるんじゃないか。 みんなも自問自答してみてほしい。」

この発言は非常に衝撃的でした。 さして当事者でもない僕の気持ちを表現するに どれほど的を射た発言だったでしょうか。

多くの人が亡くなって、 生き残った人も過酷な避難生活を過ごして、 大規模な原発事故も起きて、

そんな状況に自分は興奮しているとするならば、 どれだけ残酷なことだろう、 と子どもながらショックを受けました。

こんなことをメディアで言ったら それこそ津波のような勢いで叩かれるでしょう。 しかしながら、それを的確な言葉で伝えた先生。 この立場だからこそできることだと思います。

不謹慎という言葉だけで片付けられるでしょうか。

ニュースや週刊誌に載っているのは他人の不幸ばかり。

これは人間の真理なのかもしれません。

「シン・ゴジラ」と舞台芸術に対する僕のスタンス

シン・ゴジラ

ご無沙汰しております、あーさーです。今日はこのブログ初めての技術系じゃないお話をします。

いきなりですが、「シン・ゴジラ」という映画をご覧になったことがありますか。この前の日曜に地上波初放映していたので、そのときに見たという人も多いと思います。まだ見ていない人はすぐにこのタブを閉じて、Amazonで円盤をポチってください。(以後、ネタバレの要素を含みます)

(アフィリエイトでは)ないです

演劇は問題提起の場

話は変わりますが、今回は僕の演劇に対する基本姿勢について書きます。僕は高校2年間を演劇同好会(今はもうなくなってしまったようですが)に所属して過ごしました。それまで、ラジオやテレビなどのメディアに出演したことはありますが、役者、舞台の経験は皆無でした。その当時、リーフレットに載せるためか、「演劇とは何か。」という質問をされたことがあります。それに対しての僕は、「演劇とは問いである」と答えました。

みなさんは舞台芸術を見て何を思いますか。ストーリーにただただ感動しますか。メッセージ性を感じますか。そもそも、舞台芸術というものはステージ上で完結しているものなのでしょうか。

演劇を見て、「やっぱり友情は大切だなぁ」とか「戦争はよくない」とか思うことは多いでしょう。でも、僕には、そんな単純なメッセージを伝えるためだけにその舞台があるとは思えないのです。物事にはそう簡単に白黒がつけられない、だからこれを見て観客も考えてみてほしい。そして、その観客の思考を含めた総合芸術が演劇であると考えます。

今、演劇という言葉を使っていますが、これは文学にも映画にも、あらゆる物語にも当てはまると思います。

シン・ゴジラ」に何を思うか

具体的に、「シン・ゴジラ」で考えてみましょう。第3形態(通称:品川くん)の際に自衛隊が攻撃しようとするも、逃げ遅れた民間人が近くにいたため、首相の判断で攻撃を取りやめたというストーリーがあります。この時、あなたは何を思いましたか。

  • このときに躊躇わず攻撃していれば、後の災難は起こらなかった
  • 人命第一のこの決断は正しい
  • これは首相が判断することではない

いろいろな意見があると思います。しかし、これらのどれが正義なのかということは、作品中では触れられていません。確かに事実はあり結果はあれど、何が正しいのか、そう簡単に決めることができる問題ではないのです。

他にも、役人は無能だとか、日本社会は形式に囚われすぎているとか、核開発や遺伝子研究はよくないとか、アメリカの言いなりになってはいけないとか、この作品を見ていろんなことを思うでしょう。でも、それは「シン・ゴジラ」という作品が観客に届けたかったメッセージではないと思うのです。これはあくまで結果でしかないのです。

でも、「考える」ことって、とても楽しくないですか?

そして、自分が考えたことを他の人と議論できたら、より意見が深まって、楽しくないですか?

そう、これが「シン・ゴジラ」が提供する副次的なエンターテインメントです。ストーリーを見て楽しい、面白いことを言っているから楽しい、それは映画を見ているその時に感じる面白さ。もちろんそれがメインなのですが、提起された問題について考えてみる、それは映画館の外でもできます。映画の上映時間以上に楽しめるというわけです。

ハッピーエンドも悪くないけど

これは個人的な価値観なのですが、僕は物語におけるハッピーエンドはあまり好きではありません。かといって、バッドエンドが好きなのかと言われるとそうでもありませんが。終演後にどれだけ観客の脳裏に余韻を残せるか、ということがポイントだと思います。一般的なハッピーエンドだと、「2人は幸せなキスをして終了」といったように、はいはいよかったね、ということで終わってしまうんですよね。見ててスッキリはしますが。

シン・ゴジラ」の最後はどうだったでしょうか。僕は初見では気づかなかったのですが、ゴジラの尻尾から何か子供のようなものが生まれるというシーンを最後に映画が終わります。この後の日本に何が起こるのか、わくわくしますよね。

こういう遊びが用意されている演劇を見に行くと、僕は劇場から家に帰るときにずっと考えてしまいます。同じチケット代を払うのであれば他人より楽しみたいものですね。

舞台を作る人間として

少し話が逸れてしまいましたが、裏を返せば、演劇をやっている私はこういったことも考えて本を書いたり、演出したりしないといけないわけですね。偉そうに書いてますけど僕にはまだ無理でした。ちゃんと勉強すべきなんでしょうけど、する場もないのでひたすら小説読んでます。文系の授業でこういう感じのあればいいですけどね。

ここまで長々と語ってきましたが、僕が考えていることが真理だとはこれっぽっちも思っていません。僕がこう考えているというだけです。これもまた、そう簡単に白黒つけられる問題でもないでしょうから。

世界はそんなに単純にできていない

なにか伝えたい事があって、それを芸術に昇華させる、という人も多いと思います。でも、僕には伝えたい事が思いつきません。一般的にものを言うことって、凡人にはとてもむずかしいことです。周りを見回すと、様々な問題があります。あるものが部分的に悪いから、それをすべて否定してしまおうと考える人が世の中には多すぎる気がします。その逆も然りです。もしそんなにすぐ答えが見つかるなら、世界はとっくに不満のない素晴らしい世界になっていることでしょう。考えることも大事ですが、自分とは違う視点の意見を大事にすることも忘れてはいけませんね。

ファイルアップロードの際に文字化けする問題を解消する

あるサーバー

私の環境ではないが、 CORESERVER あるサーバーで、「ファイルアップロードの際にファイル名からフィールド名まで文字化けしてしまう」という問題が発生したので、解消するためのメモ。

文字コードがおかしい?

最初はmb_internal_encodingが違う値なのかなぁとか適当に考えて文字コードを変換して見たが、上手くいかない。まず、文字化けの仕方がいつも見慣れるようなタイプではないので、何から何にエンコードされているのかが分からない。メジャーな文字コードの組み合わせで試してみても、同じような文字化けを再現できませんでした。

困ったときのphpinfo()

ということで、phpinfo()の結果を、自分の環境と比べてみた。狙いをmbstring系に絞って差分を取ってみると、いかにも怪しそうな名前の設定が見つかった。

mbstring.http_input = auto;

mbstring.encoding_translationとは

mbstring.encoding_translationというものがあるらしく、$_POST$_GET文字コードを、スクリプト上で変換しなくても自動でやってくれるらしい。正直自分でやるから勝手にやらないでほしい。とりあえずinputの場合だけ書き換えてみる。

mbstring.http_input = "pass";

デフォルト値に戻してみた。すると、文字化けも直った。やったぜ。

ちなみにこの設定は古いものらしく、PHP5.6~は非推奨になっている。とりあえず空にしておけばいいらしい。

PHP: 実行時設定 - Manual

FuelPHPで日本語URLを扱う

FuelPHPって日本語URL使えるの?

FuelPHPは自動でURLからリクエストを処理するController、actionを指定します。その関係で、クラス名・メソッド名に日本語や%が使えない以上、日本語URLは扱えない、と思っていたのですが、routes.phpというConfigファイルを思い出しました。

結論を言うと、日本語URLは、できます!!

日本語URLを使う方法

APPPATH/config/routes.php

<?php
return array(
    '_root_'  => 'index',  // The default route
    '_404_'   => '404/index',    // The main 404 route
    'hello(/:name)?' => array('welcome/hello', 'name' => 'hello'),
    'テスト' => 'test/index',  // ←ここ追記
);

こんな感じで日本語と、それに対応するController名/action名を配列に追加すると、アクセスできちゃいます。

元のURLでアクセスしてきた人を弾く

URLの正規化のために、test/indexとアルファベットのURLでアクセスしてきた人を弾く方法は、Uriクラスを使うことで簡単にできます。詳しくはあまり興味が無いので書きません。

自分でさえ興味のない誰得な記事を書いてしまった……。

FuelPHPのEmailパッケージでSMTP-AUTHを使用して送信する

Emailパッケージ

久々にFuelPHPの話です。FuelPHPには便利なEmailパッケージが同梱されていて、メールをphp標準のmail()関数を使わなくてもメールが送れます。使用方法は以下の通り。

<?php
$email = Email::forge();
$email->subject(mb_convert_encoding('テストメール', 'ISO-2022-JP'))
      ->body(mb_convert_encoding(View::forge('mail', [], false), 'ISO-2022-JP'))
      ->to('hoge@hoge.com')
      ->from('huga@huga.com', mb_convert_encoding('ぽよぽよ', 'ISO-2022-JP'));
try
{
    $email->send();
}
catch (\EmailValidationFailedException $e)
{
    // メールアドレスが正しい形式じゃないとき
}
catch (\EmailSendingFailedException $e)
{
    // ドライバがメールの送信に失敗したとき
}

同様の方法でbccやccを設定したり、添付ファイルをつけたりなんてこともできます。View::forge()の第2引数に配列を渡したり、予めViewオブジェクトを生成し$view->hoge = 'hoge';とプロパティを指定してあげることで、変数を使ってメール本文を生成することもできます。詳しくは、FuelPHPのドキュメントを読んでください。

Usage - Email Package - FuelPHP Documentation

Emailパッケージのデフォルト設定の問題

さて、このEmailパッケージを導入する手順で、APPPATH/configにemail.phpというEmailパッケージの設定ファイルを設置します。こちらの設定ファイルは、文字コードISO-2022-JPエンコードを7bitにする以外に弄ることがなかったのですが、ある時急に思い出しました。

あれ、SMTP認証してなくね?

すぐさまconfigファイルを覗くとこんな設定がありました。

APPPATH/config/email.php

<?php
return array(
    //117行目付近
    /**
     * SMTP settings
     */
    'smtp' => array(
        'host'     => '',
        'port'     => 25,
        'username' => '',
        'password' => '',
        'timeout'  => 5,
        'starttls' => false,
    ),
);

今更ですけどPHPってプログラム部分が<?php ?>で囲われてるせいで、ブログでsyntax highlightするのにいちいち<?phpってつけなきゃいけないんですよね。見た目が煩雑になるので嫌ですね。

そんなことはさておき、SMTP settingsという設定を見つけ、その中には25の文字が!あ~、25番ポートで送ってたのか~。じゃあこの設定をちゃんと埋めればいいんですね。

Configを弄る

だいたいの環境ではメールアドレスごとにusernameが異なるはずなのですが、デフォルト設定を書き換えるだけだと1種類のアドレスからしか送れない…。もちろんこちらのConfigファイルに新しいプロファイルを作成(setupのところに配列を追加)すればいいのですが、この設定そんなにたくさん使わない!ということもあるでしょうし、今回はEmail::forge()の第2引数に設定を追記していく形で設定していきます。

<?php
$email = Email::forge('default', [
    'smtp' => [
        'host'     => 'poyopoyo.com',
        'port'     => 587, //サブミッションポート
        'username' => 'poyo@poyopoyo.com',
        'password' => '********',
        'timeout'  => 5,
        'starttls' => false,
    ],
]);

メールソフトを設定するような気持ちで入力すればOKです。これで送ってみたのですが、ポートが変わらない……どころかパスワードが違っても送れてしまう始末。たしかにこのEmailインスタンスには設定情報が入っているのですが……。

driver設定を追加

FuelPHPのEmailパッケージのドキュメントはお世辞にもしっかりしているとは言えない(情報量が少ない)ので、仕方なくコードをひたすら読んでいきました。すると、こんな設定があることがわかりました。

APPPATH/config/email.php

<?php
return array(
    //48行目付近
    /**
     * Mail driver (mail, smtp, sendmail, noop)
     */
    'driver' => 'mail',
);

この値をsmtpにしないとsmtpの設定が反映されないようですね。

先程のconfig配列に追記します。

<?php
$email = Email::forge('default', [
    'smtp' => [
        'host'     => 'poyopoyo.com',
        'port'     => 587,
        'username' => 'poyo@poyopoyo.com',
        'password' => '********',
        'timeout'  => 5,
        'starttls' => false,
    ],
]);

これでメールを送ってみると、見事にSMTP-AUTHを使用して587ポートからの送信に成功しました。

おまけ①SSLを使う

<?php
$email = Email::forge('default', [
    'smtp' => [
        'host'     => 'poyopoyo.com',
        'port'     => 465,
        'username' => 'poyo@poyopoyo.com',
        'password' => '********',
        'timeout'  => 5,
        'starttls' => true,
    ],
]);

これでSTARTTLSで接続できるかなぁと思ったのですが、残念ながら、私の環境では以下のエラーが発生し、認証ができませんでした。

ERROR - ***time*** –> Notice - fputs(): send of 6 bytes failed with errno=32 Broken pipe in ***path*** on line ***line***

私の設定が間違っている可能性のほうが高いので、またいつか試してみます。誰か原因が分かる人がいたら教えてください。

2017/09/26 16:37追記(できました)

hostsの値の前にssl://をつけたらふつうにできました。おかしいな、朝はこれでダメだったのにな。

<?php
$email = Email::forge('default', [
    'smtp' => [
        'host'     => 'ssl://poyopoyo.com',
        'port'     => 465,
        'username' => 'poyo@poyopoyo.com',
        'password' => '********',
        'timeout'  => 5,
        'starttls' => true,
    ],
]);

おまけ②mailドライバーは何だったのか

デフォルトで指定されていたdriver => 'mail'はどんな処理をしているのかな、と思ってソースを覗いてみました。そこには@mailの文字が…!!。結局mail()使ってるんか~い。まぁ確かに認証情報ないなら使わないと送れないんですけどね。初めてお目にかかった関数前の@ですが、エラーを出力しないようにするエラー制御演算子というものらしい。なるほど。あまり使用は推奨されていないようだけど。