2011年12月21日水曜日

FuelPHPってなんじゃ?(インストール編)

PHPのフレームワークは数々ありますが、どれも巨大すぎたり機能が足りなすぎたり、丁度よいモダンなフレームワークはなかなかありませんでした。
そこに登場したCodeIgniterは機能が豊富ですっきりとしている良質なモダンフレームワークとして人気なようです。

今回のFuelPHPはCodeIgniterの開発者なども参加しており、CodeIgniterや他のプロダクトのいいとこどりの期待のフレームワークだそうです。


主な特徴は
・MVC
・HMVC(画面要素の複数のMVCモジュールをひとつに纏めて1画面をつくれる)
・モジュール拡張可能
・セキュリティ対応(出力エンコーディング、入力フィルタ、XSS、CSRF、SQLインジェクション)
・豊富なコマンドライン
・ORMとSQLどちらもOK
・認証機構つき
・複数のビューテンプレート(smartyなど)に対応
・scaffoldできる
・migrateできる
・PHPUnitできる

こう見ると分かる通り、Railsから多くの影響を受けているようです。

では、さっそく触ってみます。

ここでは、
ドキュメントルートを/var/www/html
アプリベースを/opt/cloudpack/
として、blogアプリをつくってみたいと思います。


インストール
$ curl get.fuelphp.com/oil | sh
$ cd /opt/cloudpack/
$ oil create blog
これだけでひと通りのセットが揃います。

次にセキュリティのためにpublicフォルダだけをDocumentRootに移動します。
# mv blog/public /var/www/html/blog

そして、index.phpに書いてある、アプリパスを変更します。
# vi /var/www/html/blog/index.php
~
/**
 * Path to the application directory.
 */
define('APPPATH', realpath('/opt/cloudpack/blog/fuel/app/').DIRECTORY_SEPARATOR);

/**
 * Path to the default packages directory.
 */
define('PKGPATH', realpath('/opt/cloudpack/blog/fuel/packages/').DIRECTORY_SEPARATOR);

/**
 * The path to the framework core.
 */
define('COREPATH', realpath('/opt/cloudpack/blog/fuel/core/').DIRECTORY_SEPARATOR);


それでは画面をみてみます。



おー、Welcome画面が表示されてました。
とりあえず問題ないようです。



Scaffolding

次にデータベースの設定をします。(mysqlサーバーがインストールされ起動していることを前提とします)

vi /opt/cloudpack/blog/fuel/app/config/db.php
'default' => array(
                'type'        => 'mysql',
                'connection'  => array(
                        'hostname' => 'localhost',
                        'port' => '8889',
                        'database' => 'blog',
                        'username' => 'memorycraft',
                        'password' => '*********',
                        'persistent' => false,
                ),
                'identifier'   => '`',
                'table_prefix' => '',
                'charset'      => 'utf8',
                'caching'      => false,
                'profiling'    => false,
        ),

vi /opt/cloudpack/blog/fuel/app/config/development/db.php
'default' => array(
                'connection'  => array(
                        'dsn'        => 'mysql:host=localhost;dbname=blog',
                        'username'   => 'memorycraft',
                        'password'   => '***********',
                ),
        ),


mysqlでデータベースとユーザーを作成します。
mysql> create database blog;
mysql> GRANT ALL PRIVILEGES ON blog.* TO 'memorycraft@localhost'  IDENTIFIED BY '*******';


次に、scaffoldをつくってみます。
$ cd /opt/cloudpack/blog/
$ oil g scaffold blog title:string body:string tags:string created_at:date

そしてmigrate
$ oil refine migrate

これで、データベースblogにblogテーブルが作成されました。
mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| blog               |
| mysql              |
| performance_schema |
| test               |
+--------------------+
5 rows in set (0.00 sec)


それでは画面をみてみます。



ブログの管理画面ができています。
入力して

登録すると、、、、



おおー!一覧に保存内容がひょうじされました。
railsと同じノリですね。

あくまでひな形ですが、このくらいのスピードでDB、画面ができあがります。
PHPはもうこれでいいんじゃないかなと思うくらいの可能性を感じます。

これから使い込んでみたいと思います。
以上です。

2011年12月14日水曜日

SESってなんじゃ?(SMTPがサポートされました)

SESにSMTP機能がサポートされました。
メーラーでもSendmailやPostfixでも利用できるのは素晴らしいですね。
今回は試しにmiura@cloudpack.jpのメールアドレスで、GmailからSESを経由してHotmailにメール送信してみたいと思います。


SESの設定


AWSコンソールのSESタブを開くと、「SMTP Settings」というメニューが増えています。


これを開くと、SMTPサーバーの基本情報が表示されます。
Server Name: email-smtp.us-east-1.amazonaws.com
Port: 465
TLS: Yes
Authentication: Your SMTP credentials

Authenticationの欄にある通り、SMTPで送信するには、SMTP認証用のIAMユーザーを作る必要があり、画面中央の「Create My SMTP Credential」を押すと手順に従って設定できます。


ボタンを押すと、自動的にIAMタブに遷移し、「Create User for SMTP」というダイアログが開き、SMTP用のIAMユーザーを登録します。ここにSMTP用のIAMユーザー名を入力して、「Create」ボタンを押します。



すると、作成完了のダイアログが表示されます。
Credential情報は「Show User SMTP Security Credential」で表示されます。


Credential情報はファイルとして「Donwload Credential」でダウンロードし保管しておきます。
"Iam User Name","Smtp Username","Smtp Password"
"smtp-memorycraft","ACCESS_KEY******","SECRET_KEY******"


ダウンロードが終わり、ダイアログを閉じると再びSESタブに自動遷移します。
SESのダッシュボードを表示し、メール送信者を追加するために、「Verify a New Sender」をクリックします。



ここで、今回SMTPで送信したい送信者のメールアドレスを入力し、「Submit」をクリックます。



すると、そのメールアドレス宛に認証用のメールが届くので、認証リンクをクリックします。



AWSの画面が表示され、認証が完了した旨のメッセージが表示されています。これで送信者の認証が完了しました。


SESは無償では、認証したメールアドレスに対してしか送信できないため、テスト受信用のメールアドレスもこれと同様にして認証しておきます。(手順は同様なので、割愛します。)



メールクライアント( Gmail ) の設定


次にGmailのSMTP設定を行います。 「設定<アカウントとインポート」 タブの「名前」欄で送信者のメールアドレスを追加します。今回は既にGmail経由送信で設定済みだったので、「編集」で設定を変えてみます。



「次のステップ」を押すと、「Gmail経由で送信する」が選択されているので、「SMTPサーバー経由で送信する」のほうをクリックして選択します。すると、SMTPサーバー、ポート、ユーザー名、パスワード、接続プロトコルの入力欄が表示されます。
SESのSMTP Settingsに記載のあったように、SMTPサーバー名とポート、プロトコルを入力します。

Server Name: email-smtp.us-east-1.amazonaws.com
Port: 465
TLS: Yes
Authentication: Your SMTP credentials

また、ダウンロードしたcredential.csvに記載されている、Smtp UsernameとSmtp Passwordをユーザー名、パスワードに入力します。



ここで、「変更を保存」をクリックすると、有効性のチェックが始まるのですが、下記のようなエラーがでました。



どうやらTLS接続だとうまく設定できないようです。接続プロトコルをSSLにしたら、設定が成功したのでこのまま進みます。
登録が完了すると、「名前」欄が以下のような表示に変わり、AWSを使ったSMTP接続が設定されていることが確認できます。




それでは実際にメールを送信してみます。以下の内容のメールを「Verify User」で登録しておいたhotmailアドレス宛に送信します。



hotmail側を見てみると、、、


おおー、受け取れています!
アプリケーションなどでもSDKではなく、従来のSMTP送信でメール配信することができますね。

以上です。

2011年12月9日金曜日

S3ってなんじゃ?(SDKで複数S3オブジェクトを一括削除:SDKバグの修正パッチつき)

S3で複数のS3オブジェクトを指定して一括で削除できるようになりました。
早速PHPのSDKで試してみたいと思います。

delete_objectsというメソッドが追加されていますので、これを使ってみます。
対象のバケットには以下のようにファイルがあります。


ここで、以下のようなプログラムでvp_sample.csvとwelcome.txtを削除するコードを書きます。
$ vi bulk_delete.php
-------------------------------------------
#!/usr/bin/php -q
<?php
require_once '/opt/aws/php/default/sdk.class.php';

$bucket = 'hoge-bucket';

$s3 = new AmazonS3('AccessKey','SecretKey');
$s3->set_region(AmazonS3::REGION_APAC_NE1);

$response = $s3->delete_objects ($bucket, array(
        'objects' => array(
                array('key' => 'vp_sample.csv'),
                array('key' => 'welcome.txt')
        )
));
echo "done."
?>


これを実行します。

$ php bulk_delete.php
PHP Notice:  Undefined property: AmazonS3::$multi_object_delete_xml in /opt/aws/php/sdk-1.4.8/services/s3.class.php on line 1406
PHP Fatal error:  Uncaught exception 'Exception' with message 'String could not be parsed as XML' in /opt/aws/php/sdk-1.4.8/services/s3.class.php:1406
Stack trace:
#0 /opt/aws/php/sdk-1.4.8/services/s3.class.php(1406): SimpleXMLElement->__construct('')
#1 /opt/cloudpack/test/bulk_delete.php(21): AmazonS3->delete_objects('hoge-bucket', Array)
#2 {main}
  thrown in /opt/aws/php/sdk-1.4.8/services/s3.class.php on line 1406

。。。。。
エラーになりました。

なんでしょう?
/sdk-1.4.8/services/s3.class.php を調べてみると、delete_objectsメソッド内でSimpleXMLObjectの初期化に失敗しているようです。

1406行目の
$xml = new SimpleXMLElement($this->multi_object_delete_xml);

ここで、初期化の引数に渡している
$this->multi_object_delete_xml
がどこにも存在していないことが原因のようです。SDKのバグですね。
これを直してみます。

ここで、まず238行目あたりに、
public $multi_object_delete_xml;

とクラスのプロパティを追加し、

さらに238行目あたり、__construct内で、他のXMLのタグ文字列の初期化と同じように、以下のように初期化します。
$this->multi_object_delete_xml = '<?xml version="1.0" encoding="utf-8"?><MultiObjectDelete></MultiObjectDelete>';


これで、再度実行してみます。
$ php bulk_delete.php
done.

おお、成功しました。

実際削除されたのか確認します。

いけてますね。

この修正パッチをAWSのフォーラムにアップしましたので、必要な方は自己責任でご利用ください。
またパッチ内容に問題があればご一報いただけると助かります。

Bug: PHP SDK 1.4.8 Multi Object Delete (patch)
https://forums.aws.amazon.com/thread.jspa?threadID=81967&tstart=0

以上です。

追記:2011/12/21
このバグは1.4.8.1で修正されたようです。
https://forums.aws.amazon.com/thread.jspa?threadID=81967&tstart=0&messageID=299617#299617

2011年11月30日水曜日

シェルってなんじゃ?(mysqlに簡単ランダムデータ生成)

DBにランダムな大量データを入れ込みたいけれども、データの内容やテーブル同士の結合などあまり気にしなくて良い場合は、ちゃんとしたジェネレーターなどをインストールするよりもシェルでさくっと書いてしまいたい時があります。

ここではmysqlにシェルからINSERTし続けるシェルを書いてみたいと思います。

例として対象となるテーブルは以下のとおり

CREATE TABLE user (
 id int(11) NOT NULL AUTO_INCREMENT,
 name varchar(50) DEFAULT NULL,
 age int(11) DEFAULT NULL,
 description text,
 created_at datetime DEFAULT NULL,
 PRIMARY KEY (`id`)
) ENGINE=InnoDB

 ここで、idはオートインクリメント、created_atはNOW()を使用するのでいいとして、name, age, descriptionにランダムなデータを入れ込むためにランダム生成をします。


文字列のランダム

mkpasswdというパスワード生成用のランダム文字列の生成ツールで、expectパッケージに含まれています。
たとえば3文字以上の数字と5文字以上の大文字が必ず含まれる50文字のランダム文字列が生成するには以下のようにします
$ mkpasswd -l 50 -d 3 -C 5
kDVoDyxqwd5csu0bfuk8dpoygyhwwersqoazoycvehsovsMjqS


mkpasswdは厳密にランダムさを求めたり使用文字のルールを決めることができるので便利ですが、とても重いので大量に生成するには向かない場合があります。 ほかにもuuidgenというUUIDを作成するコマンドがあります。
これは全世界的に一意なIDを発行するためのツールです。
$ uuidgen
a62f9c8d-9d97-4b5d-a572-d83222837fc2


数値のランダム

数値をランダムに出力するにはexprが便利です。
たとえば1〜8までのランダム数値は以下のようにします。
$ expr 1 + $RANDOM % 8
3

これらをつかってランダムなデータをテーブルに入れるスクリプトを書いてみます。
mysqlへの投入はmysql -e "SQL文"で、コマンドから直にSQL実行できるので、それを利用します。


ランダムデータ生成スクリプト

insert_random.sh
#!/bin/sh

while ((1))
do
name=$(uuidgen | tr -d '-' | cut -c 1-10)
desc=$(mkpasswd -l 50 -s 0)
age=`expr 1 + $RANDOM % 1000`
mysql -u hoge_user cloudpack -phoge_pass -e "INSERT INTO user(name, age, description, created_at) VALUES('${name}', ${age}, '${desc}', NOW());"
echo -n "."
done;

exit 1


これを実行します。
$ sh ./insert_random.sh 
...............................................................................................................................................................................................................................................................................................................................................................................................

進捗がすすんでいるようです。
ちょっと別のターミナルセッションからデータを覗いてみます。

mysql> select * from user;
+----+------------+------+----------------------------------------------------+---------------------+
| id | name       | age  | description                                        | created_at          |
+----+------------+------+----------------------------------------------------+---------------------+
|  1 | 598d056309 |  896 | rkggeRoxaqvmhlcwhon2ordjhafdLoqdrs5ecaplouaqkccdfk | 2011-11-30 03:00:14 |
|  2 | 6ab7bf0029 |  487 | 8iOrolcltandneoxtsmwizx6shtreovoodulnmpjePdqgyrxxa | 2011-11-30 03:00:14 |
|  3 | c62df8dab5 |  464 | oxncixqdamctmeppmohmwkdayxzgw0rxRvwx3vbDhlyndqnlcv | 2011-11-30 03:00:14 |
|  4 | 63a9d01e41 |  609 | ilovfkcskrvdui2nHnumcfarlkn5iVaopabyymiafjiystbyvx | 2011-11-30 03:00:14 |
|  5 | e5abb35d2d |  677 | epkdylnwhlx9kophdOxcvcofqqheuljknsiivgon8cyovyAvaf | 2011-11-30 03:00:15 |
|  6 | 2888a4f6fd |  230 | ywqbCazysbakzweqfohli6oqhjuncohtyxplxp2Frcuhfacyqa | 2011-11-30 03:00:15 |

〜

| 2452 | 2b1e84b98d |  388 | kEheuvccvwusygulkfhig6qvnffxmnveahazmnaclcIbllcz5j | 2011-11-30 03:00:52 |
| 2453 | ea1ef37248 |   44 | feoeumwokgrvtypatzxfyfZg3ltvrnxwdEhzfveveykavitzl9 | 2011-11-30 03:00:52 |
| 2454 | 05a5341597 |  309 | chlpvjttq2axlvkrubjgubfdfhwzoatolbfddo6nqpgWgbkfPh | 2011-11-30 03:00:52 |
| 2455 | 8403fbafe9 |  655 | tdwi2egdtAucxroylarikvqdfhpxnwxeopqxtcufvqsix0Wekz | 2011-11-30 03:00:52 |
| 2456 | 471be293bc |  660 | lnKqpbueti3izbg1orceykiomjqocexezpkulqgkdhzFczyfze | 2011-11-30 03:00:52 |
| 2457 | 05628b4a7c |  180 | ueebrCqwn5brvjUqrkwcpg6qpjpkokkrtxvfczxjtcyqmrfpnf | 2011-11-30 03:00:52 |
| 2458 | 3688ea54b3 |   92 | wrpamv9QmycbccpuwqPnlsjdyiwcl5avyfgytdqllgtiljcwsc | 2011-11-30 03:00:52 |
+------+------------+------+----------------------------------------------------+---------------------+
2458 rows in set (0.01 sec)

おお、順調に投入されています。
このように、気軽に大量のデータをつくれ、また常にデータ投入が行われている状態にすることで開発の最初から負荷を気にすることもできます。
以上です。

統計ってなんじゃ?(mysqlで度数分布)

プロジェクトを運用していると、ユーザーのサービス利用傾向を知るためにデータの統計を依頼されることがあります。

たとえば、ゲームを例にすると、ユーザーがどのアイテムを何個持っているか、と聞かれた場合、一人ひとりのユーザーの持ち物をしらべるとなると、結局DBのダンプになってしまいます。
そういった場合、「何個もっているユーザーが何人いる」ということを調べることで、ユーザーの全体的な動向を知ることができます。

ここでは、0〜9個の人は何人、10〜19個の人は何人、、、というように段階的に範囲を絞って、アイテムごとにMySQLから統計を採ってみたいと思います。このような統計の採り方を度数分布といいます。

対象となるテーブルは

●ユーザー
mysql> select id, name, age, created_at from user limit 5;
+----+------------+------+---------------------+
| id | name       | age  | created_at          |
+----+------------+------+---------------------+
|  1 | 598d056309 |  896 | 2011-11-30 03:00:14 |
|  2 | 6ab7bf0029 |  487 | 2011-11-30 03:00:14 |
|  3 | c62df8dab5 |  464 | 2011-11-30 03:00:14 |
|  4 | 63a9d01e41 |  609 | 2011-11-30 03:00:14 |
|  5 | e5abb35d2d |  677 | 2011-11-30 03:00:15 |
+----+------------+------+---------------------+
5 rows in set (0.00 sec)

●アイテム
mysql> select id,name from item;
+----+--------------------------+
| id | name                     |
+----+--------------------------+
|  1 | やくそう                 |
|  2 | どくけしそう             |
|  3 | ばんそうこう             |
|  4 | 100円ライター         |
|  5 | ビー玉                   |
|  6 | りんご                   |
|  7 | けしごむ                 |
|  8 | わゴム                   |
+----+--------------------------+
8 rows in set (0.00 sec)


●ユーザーの持ち物
mysql> select * from my_item limit 5;
+------------+---------+---------+---------------------+
| my_item_id | item_id | user_id | created_at          |
+------------+---------+---------+---------------------+
|          1 |       7 |     330 | 2011-11-30 01:33:17 |
|          2 |       6 |      68 | 2011-11-30 01:33:17 |
|          3 |       7 |     477 | 2011-11-30 01:33:17 |
|          4 |       5 |     554 | 2011-11-30 01:33:17 |
|          5 |       4 |     153 | 2011-11-30 01:33:17 |
+------------+---------+---------+---------------------+
5 rows in set (0.00 sec)

だとします。

アイテムごとに各ユーザーが持っている個数を取得するには以下のようにとれます。
select item_id, user_id, count(my_item_id) from my_item group by item_id, user_id;

ここで、例えば0〜9個までを持っているユーザーは、更にhaving句を使って以下のように絞られます。
select item_id, user_id, count(my_item_id) from my_item group by item_id, user_id having count(my_item_id) >= 0 AND count(my_item_id) < 10

オンラインゲームなどは保持数の上限などを設けている場合がほとんどです、例えばアイテムは最大100個しか
持てないという制限があるとします。
ここでは、アイテムの所持数を0〜9、10〜19、、、90〜100のように区切って、段階ごとに範囲を決めて抽出するとすると、以下のSQLになります。

mysql< select 
z.item_id,
z.name,
IFNULL(t1.cnt, 0) as "0=<10",
IFNULL(t2.cnt, 0) as "10=<20",
IFNULL(t3.cnt, 0) as "20=<30",
IFNULL(t4.cnt, 0) as "30=<40",
IFNULL(t5.cnt, 0) as "40=<50"
from
(select 
 id as item_id,
 name
 from 
 item
) z
LEFT OUTER JOIN
(
select a.item_id,count(a.user_id) as cnt from (select item_id, user_id, count(my_item_id) from my_item group by item_id, user_id having count(my_item_id) >= 0 AND count(my_item_id) < 10) a group by a.item_id
) t1 ON t1.item_id = z.item_id
LEFT OUTER JOIN(
select a.item_id,count(a.user_id) as cnt from (select item_id, user_id, count(my_item_id) from my_item group by item_id, user_id having count(item_id) >= 10 AND count(item_id) < 20) a group by a.item_id
) t2 ON t2.item_id = z.item_id
LEFT OUTER JOIN(
select a.item_id,count(a.user_id) as cnt from (select item_id, user_id, count(my_item_id) from my_item group by item_id, user_id having count(item_id) >= 20 AND count(item_id) < 30) a group by a.item_id
) t3 ON t3.item_id = z.item_id
LEFT OUTER JOIN(
select a.item_id,count(a.user_id) as cnt from (select item_id, user_id, count(my_item_id) from my_item group by item_id, user_id having count(item_id) >= 30 AND count(item_id) < 40) a group by a.item_id
) t4 ON t4.item_id = z.item_id
LEFT OUTER JOIN(
select a.item_id, count(a.user_id) as cnt from (select item_id, user_id, count(my_item_id) from my_item group by item_id, user_id having count(item_id) >= 40 AND count(item_id) < 50) a group by a.item_id
) t5 ON t5.item_id = z.item_id;
+---------+--------------------------+-------+--------+--------+--------+--------+
| item_id | name                     | 0=<10 | 10=<20 | 20=<30 | 30=<40 | 40=<50 |
+---------+--------------------------+-------+--------+--------+--------+--------+
|       1 | やくそう                 |  1336 |      0 |     16 |     52 |     21 |
|       2 | どくけしそう             |  1346 |      0 |     13 |     60 |     16 |
|       3 | ばんそうこう             |  1327 |      1 |     14 |     47 |     27 |
|       4 | 100円ライター         |  1355 |      0 |     18 |     47 |     24 |
|       5 | ビー玉                   |  1368 |      0 |     14 |     52 |     24 |
|       6 | りんご                   |  1334 |      1 |     21 |     49 |     19 |
|       7 | けしごむ                 |  1354 |      1 |     18 |     54 |     17 |
|       8 | わゴム                   |  1329 |      1 |     23 |     42 |     23 |
+---------+--------------------------+-------+--------+--------+--------+--------+


少し冗長になってしまいましたが、もっと他にスマートなやり方があるのかも知れません。 他にいい方法も浮かばなかったのですが、とりあえず抽出することができました。

本日は以上です。