Do You PHP はてな このページをアンテナに追加 RSSフィード Twitter

2014-02-17

[]svn: E155036: Please see the 'svn upgrade' command

今頃引っかかりました。。。

svnの操作で

$ svn update
svn: E155036: Please see the 'svn upgrade' command
svn: E155036: Working copy '/path/to/webapp' is too old (format 10, created by Subversion 1.6)
$

となる場合、ワーキングコピーをアップグレードする必要があるようです。

実際のアップグレード手順は以下のような感じ。

$ svn update
svn: E155036: Please see the 'svn upgrade' command
svn: E155036: Working copy '/path/to/webapp' is too old (format 10, created by Subversion 1.6)
$
$ svn upgrade
svn: E155019: Can't upgrade '/path/to/webapp' as it is not a pre-1.7 working copy root, the root is '/path/to'
$
$ cd /path/to
$ svn upgrade
   :
$

Subversionリポジトリを作成したsvnバージョンとsvnクライアントのバージョンが不一致の場合に起こるエラーのようです。今回は

という組み合わせでエラーになりました。

2014-02-12

[]Google HangoutsでSoftBankMMSを送受信する

何かと面倒くさいAndroidSoftBank MMSですが、Google HangoutsでもMMSを送受信できたのでメモ代わりに書いておきます。

あくまで検証なので動作保証はありません。また、何をやっているか分からない方はやめといた方が良いです。ご自分で試される際は自己責任で:-p

検証環境

概要

  • SoftBankMMS網は、クライアントアプリのUser-Agentでアクセス制限をしている(らしい)
  • Google HangoutsのUser-Agentは当然上記にマッチしないので接続自体不可
  • 調べてみると、Google HangoutsのUser-Agentは、Android API Level 19から導入されたandroid.telephony.TelephonyManager#getMmsUserAgentを使っているっぽい
  • android.telephony.TelephonyManager#getMmsUserAgent自体はリソースからUser-Agent文字列を取得しているだけ
  • リソースはframework-res.apkに含まれているので、apktoolを使って一度バラしてUser-Agentなどを書き直し、再度framework-res.apkをbuildしてオリジナルと差し替える

手順

端末をリカバリモードで再起動し、USB接続後cygwinから以下の様な感じで。

$ export PATH=/path/to/jre7/bin:/path/to/android-sdk-windows/build-tools/19.0.0/:$PATH
$ 
$ adb shell mount -a
$ 
$ # fetch framework-res.apk
$ adb pull /system/framework/framework-res.apk
$ 
$ # backup
$ \cp -pf framework-res.apk framework-res.apk_
$ java -jar apktool_2.0.0b9.jar d -advance framework-res.apk 
$ 
$ # modify user-agent, uaprofile in resources
$ cp -p framework-res/res/values/strings.xml strings.org.xml
$ perl -i -p -s -e 's#http://www.google.com/oha/rdf/ua-profile-kila.xml#http://www.apple.com/mms/uaprof.rdf#g' framework-res/res/values/strings.xml
$ perl -i -p -s -e 's#<string name="config_mms_user_agent">CyanogenMod</string>#<string name="config_mms_user_agent">iPhoneOS/3.0 (7A341)</string>#g' framework-res/res/values/strings.xml
$ find . -iname \*.bak -delete
$ 
$ # install as 127.apk
$ java -jar apktool_2.0.0b9.jar if framework-res.apk_
$ 
$ # build framework-res.apk
$ java -jar apktool_2.0.0b9.jar b -advance framework-res 
$ 
$ # update resources.arsc only
$ cp -p framework-res/build/apk/resources.arsc .
$ 7z u -tzip -mx0 framework-res.apk resources.arsc
$ 
$ # push framework-res.apk
$ adb push framework-res.apk /system/framework/framework-res.apk
$ adb shell chmod 644 /system/framework/framework-res.apk
$ 

あとは端末を再起動し、HangoutsからMMSを送受信してみる。logcatで以下のような感じになっていれば多分大丈夫。

$ adb logcat | grep MmsConfig
V/MmsConfig( 3219): mnc/mcc: 44020
V/MmsConfig( 3219): tag: bool value: enabledMMS - true
V/MmsConfig( 3219): tag: int value: maxMessageSize - 307200
V/MmsConfig( 3219): tag: int value: maxImageHeight - 1944
V/MmsConfig( 3219): tag: int value: maxImageWidth - 2592
V/MmsConfig( 3219): tag: int value: defaultSMSMessagesPerThread - 10000
V/MmsConfig( 3219): tag: int value: defaultMMSMessagesPerThread - 1000
V/MmsConfig( 3219): tag: int value: minMessageCountPerThread - 10
V/MmsConfig( 3219): tag: int value: maxMessageCountPerThread - 5000
V/MmsConfig( 3219): tag: string value: uaProfUrl - http://www.apple.com/mms/uaprof.rdf
V/MmsConfig( 3219): tag: int value: recipientLimit - -1
V/MmsConfig( 3219): tag: bool value: enableMultipartSMS - true
V/MmsConfig( 3219): tag: bool value: enableSplitSMS - false
V/MmsConfig( 3219): tag: int value: smsToMmsTextThreshold - -1
V/MmsConfig( 3219): tag: bool value: enableSlideDuration - true
V/MmsConfig( 3219): tag: int value: maxMessageTextSize - -1
V/MmsConfig( 3219): tag: string value: userAgent - iPhoneOS/3.0 (7A341)||<
            :
$ 

その他

  • TelephonyManagerが返す値そのものを変更しているので、どこに影響が出るかは未検証;-)
  • 仕組み上、Hangoutsのstrings.xmlを書き直しても良いと思うんだけど、OS起動時のapkチェックで弾かれます。

2013-10-31

[][]書評 - PHP逆引きレシピ第2版

初版から4年ですか。改訂版おめでとうございます&献本いただきました。ありがとうございます:-)

初級者〜中級者向けの定評のあるレシピ本です。

初版から変わったところ

カバーの裏から一部引用。。。

改定にあたっては、すべてのサンプルコードを再検討し、、現在の開発現場の潮流に合わせて必要と思われる更新を加え、また数多くのレシピを追加しました。具体的には、サンプルをHTML5で書き直し、オブジェクト指向のレシピを大幅に拡充しました。またデータベースのサンプルについてもPDOで書き直し、さらにはComposerやPHPUnitのレシピも追加しました。初版で定評のあったセキュリティについても、より充実したものとなっています。

あと、ますます鈍器になりましたね。。。900ページ弱の大作です。

f:id:shimooka:20131029111723j:image

目次と概要

翔泳社さんの書籍紹介のページにもありますが、ざっと以下の様な構成です。

第2版で強化されたオブジェクト指向(第5章)は一気に60ページ強になりました。初版でもがっつり書かれていたセキュリティ(第10章)も80ページ強も割かれています。XSSCSRFSQL/OSコマンド/メールヘッダの各インジェクション攻撃、セッションハイジャックなど、幅広い範囲にわたって具体的な攻撃方法と対策方法を解説しています。

また、ユニットテストやComposerについても説明があり、PHPUnitPEARではなくComposerを使ったインストールになっています。

総評

プログラムちょっとずつ書いている」といったプログラミング初級者なら、第1章から順にサンプルを写経・実行してみれば、スムーズに「次のステップ」に移行できるんじゃないでしょうかね。また、手元に置いておいて、「あれ?○○○ってどうやるんだっけ?」という場合に開いてみるのもいいかもしれません。

税込税抜2,800円と初版から70円200円ほど高くなりましたが、この内容とボリュームならかなりお買い得だと思います:-)

2013-09-20

[]Jaybird BlueBuds Xを買った

f:id:shimooka:20130904222527j:image

レビューを見て気になってたJaybird BlueBuds Xを購入し、使用開始から約2週間経ったので、簡単にレビューを書きます。

キッカケ

フィーチャーフォンの頃(913SHとか)からカナル型のBluetoothイヤホンを使ってたんですが、初めて買ったSONYのDR-BT20NXが断線し、DR-BT100CXPを購入。

SONY Bluetoothワイヤレスステレオヘッドセット マイク付き ホワイト DR-BT100CXP/W

SONY Bluetoothワイヤレスステレオヘッドセット マイク付き ホワイト DR-BT100CXP/W

3年近く頑張ったんですが、それも断線して次にXBA-BT75。

SONY ワイヤレスステレオヘッドセット XBA-BT75 XBA-BT75

SONY ワイヤレスステレオヘッドセット XBA-BT75 XBA-BT75

XBA-BT75は、

  • 安定してそれなりに音もいい。音飛びは通常使用でほぼ経験してない
  • 受信機やバッテリーがボックスになっていない(ボックスレス)
  • ケーブルを耳の下にぶら下げるアンダーイヤー型ではなく、耳の上にケーブルを通すオーバーイヤー型

といったこともあり結構気に入っていたんですが、

  • ケーブルが中途半端に長い割りに長さ調節できないし、ケーブル自身も形状記憶でないので、夏場に歩いていると首筋にまとわりついて気持ち悪い
  • 専用の充電機能付きキャリングケースに入れないと充電できない(microUSBから直接充電できない)ため、充電時に毎回面倒くさいし、キャリングケースを持ち歩く必要がある

という欠点が気になり始めてしまいました。で、別のイヤホンの購入を検討していたところ、探してたのはこれだった!スポーツのための Bluetooth ヘッドセット “BlueBuds X” を買ってみた - 頭ん中のレビューでかなり良さそうだったので、秋葉原のヨドバシで実物を見てAmazonで購入。

良い点

  • とにかく小さい!軽い!XBA-BT75はボックスレスと言えど、イヤホン部分は結構大きかった。。。
  • きしめんケーブル、かつ、長さ調節用のチップが付いているので、ケーブルがプラプラしたり首にまとわりついたりすることはありません。
  • Bluetoothデバイスへの接続がめちゃくちゃ早い(電源オンから1秒以内)!XBA-BT75だとヘタすると10秒ほどかかってました。。。
  • microUSBから直接充電可能!家でも会社でも車の中でも充電できる!充電は本体のキャップを外すとコネクタがあります。バッテリーの持ちは、通勤時利用なら2日くらい楽勝。

f:id:shimooka:20130905112444j:image

  • アンダーイヤー型とオーバーイヤー型どちらも可能。現在はXBA-BT75の時と同様、オーバーイヤー型で使ってます。

f:id:shimooka:20130905112530j:image

気になる点

  • 歩いたり小走りするとイヤホンがポロッと取れそうになるのがイヤなので、個人的にはSONY製の"耳道にズボッと入るカナル型"が好みだったんですが、BlueBudsはそこまで入りません。まあ、イヤークッションを付けてるので取れることはありませんが。。。
  • イヤーキャップとイヤークッションをちゃんと調整するのに時間が掛かります。結構手間ですが、ここで手を抜くとフィット感がなくなって音漏れしたり、耳が痛くなります。
  • 電源を操作する際にリモコンの真ん中のボタンを長押しするんですが、電源オフ時は結構長めに押す必要があります。でないと、通話(終話?)になってしまいます。ここ、結構操作間違えるんですよね。。。
  • 高音はしっかり出ます。低音はちょっと控えめなので、物足りない方はイヤーキャップを変えたりしましょう。今のところ、COMPLYのTx-500(Mサイズ)に変更してますが、低音もしっかり感じられるようになりました。

まとめ

別にランニング時に使うというわけではないんですが、ケーブルをしっかりまとめる事ができるので通勤時利用程度でも「ケーブルが擦れて雑音が気になる」とかは全くなくなりました。一方で、イヤーキャップとイヤークッションの調整次第でフィットする/しないが変わるので、面倒でも気長にやる必要があると思います。まあ、毎回使う度に耳が痛くなるのでは、せっかく買ったのに使う気も失せますし。。。

ちと値段が高いのが難点ですが、良い買い物したと思います:-)

2013-09-12

[][]PHPからService AccountsとしてGoogleカレンダーAPIにアクセスしてみる

今更感漂いますが気にしない:-)

ちょっとGoogleカレンダーにアクセスするバッチアプリを書いてたんですが、いろいろと苦労したのでまとめてみます。

前置き

PHPからGoogleAPI群にアクセスする記事はいろいろありますが、ユーザーがブラウザを介してアクセスするモノ(Web Server Applicationsと呼ばれる)がほとんどです。一方で、バッチアプリなどブラウザを介さずにGoogleAPIにアクセス(Service Accountsと呼ばれる)するモノはあまり見つかりません。

上のリンクにある図を見比べてもらえれば分かりますが、これら2つの大きな違いは"User Login & Consent"の部分、つまり、ログインアプリのアクセス許可があるかどうか?なんですが、バッチアプリの場合はこれをGoogle側の設定であったり、PHPコードで書く必要があります。

今回の環境

  • CentOS5+PHP5.3.3(rpm)
  • CentOS6+PHP5.5.3(src)

Google APIs Consoleでの作業

1. Google APIs Consoleにアクセスする

まずはGoogleアカウントログインした後、Google APIs Consoleにアクセスします。

2. プロジェクトを作成する

デカデカと表示されている"Create project..."ボタンをクリックして、プロジェクトを作成します。

f:id:shimooka:20130912180451p:image

3. 利用するGoogleのサービスを設定する

プロジェクト作成後、Googleのサービス一覧が表示されますので、このプロジェクトで利用するサービスを"ON"にします。今回は"Calendar API"のトグルスイッチをクリックします。

f:id:shimooka:20130912170856p:image

4. OAuth 2.0用のクライアントIDを作成する

Google APIs Consoleの左メニューにある"API Access"をクリックし、表示される青い大きなボタンをクリックします。

f:id:shimooka:20130912170851p:image

ダイアログが表示されるので、Product nameに入力して"Next"ボタンをクリック。その他の項目は適宜入力してください。

f:id:shimooka:20130912170852p:image

"Client ID Setting"ダイアログが表示されますので、真ん中の"Service account"を選択して"Create client ID"ボタンをクリックします。クリックするとキーペアが生成されます。

f:id:shimooka:20130912170853p:image

10秒ほど時間がかかりますが、以下の様なダイアログが表示されたら、"Download private key"ボタンをクリックして秘密鍵ダウンロードします。

f:id:shimooka:20130912170854p:image

ダウンロードされる秘密鍵のファイル名は、

[0-9a-f]{40}-privatekey.p12

のようなパターンです。このp12ファイルはバッチアプリに必要です。また、p12ファイルはSSHなどの秘密鍵と同様、他のユーザーからの読み書きできないパスに保存し、p12ファイル自身の権限も変更しておきます。

3. Client IDとメールアドレスを確認する

クライアントIDを生成したダイアログを閉じると、Google APIs Console画面に以下のような項目が追加表示されているはずです。ここに表示されている

がバッチアプリに必要になりますので、メモしておきます。

f:id:shimooka:20130912170855p:image

Googleカレンダーの設定

これ、ハマりどころだと思うんですが、バッチアプリからカレンダーを参照/更新する場合のアカウントは、先ほど作成したクライアントになります。ですので、カレンダーを参照/更新する権限を、クライアントに追加しておく必要があります

Googleカレンダーにアクセスし、バッチからアクセスするカレンダーの設定画面にある"このカレンダーを共有"タブから権限を与えておきます。今回は

  • 予定の変更権限

を指定しています。

f:id:shimooka:20130912171515p:image

PHPコードを書く

事前準備が終わったので、ようやくPHPコードの方です。

1. Google APIs Client Library for PHPダウンロードする

Google APIs Client Library for PHPダウンロードします。今回ダウンロードしたのは、ver.0.6.6です。

$ wget https://google-api-php-client.googlecode.com/files/google-api-php-client-0.6.6.tar.gz
$ tar zxf google-api-php-client-0.6.6.tar.gz
$ 

2. カレンダー一覧を取得してみる

以下の様なコードで動作を確認してみます。カレンダーの一覧が取得できればOKです。

<?php
require_once '/path/to/src/Google_Client.php';
require_once '/path/to/src/contrib/Google_CalendarService.php';

const CLIENT_ID = 'xxxxxxxxxxxx.apps.googleusercontent.com';
const SERVICE_ACCOUNT_NAME = 'xxxxxxxxxxxx@developer.gserviceaccount.com';
const KEY_FILE = '/path/to/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx-privatekey.p12';

$client = new Google_Client();
$client->setApplicationName("Google Prediction Sample");
$client->setAssertionCredentials(new Google_AssertionCredentials(
    SERVICE_ACCOUNT_NAME,
    array('https://www.googleapis.com/auth/calendar'),
    file_get_contents(KEY_FILE)
));

$client->setClientId(CLIENT_ID);
$service = new Google_CalendarService($client);
$calList = $service->calendarList->listCalendarList();
var_dump($calList);

3. すべてのカレンダーに登録されたすべての予定を取得してみる

前半は先ほどのコードと同じです。

<?php$service = new Google_CalendarService($client);
$calList = $service->calendarList->listCalendarList();
foreach ($calList['items'] as $calendar) {
    $events = $service->events->listEvents($calendar['id']);
    foreach ($events['items'] as $event) {
        printf('%s - %s %s%s',
            (isset($event['start']['dateTime']) ? $event['start']['dateTime'] : $event['start']['date']),
            (isset($event['end']['dateTime']) ? $event['end']['dateTime'] : $event['end']['date']),
            $event['summary'],
            PHP_EOL
        );
    }
}

実行してみると分かりますが、開始日時と終了日時はISO-8601形式です。

4. カレンダーに予定を登録してみる

前半はこれまでのコードと同じです。登録する場合、イベント自体を表すGoogle_Eventクラスを使用します。開始・終了日時はGoogle_EventDateTimeオブジェクトでラップします。

<?php$service = new Google_CalendarService($client);

// Google_CalendarService.calendarList#listCalendarList()を使って
// 登録対象のカレンダーのIDを取得しておく
//       :
// $calendarId = ... ;

/**
 * 予定(イベント)の作成
 */
$event = new Google_Event();
$start_time = new Google_EventDateTime();
$start_time->setDateTime('2013-10-25T10:00:00+09:00');
$event->setStart($start_time);
$end_time = new Google_EventDateTime();
$end_time->setDateTime('2013-10-25T11:00:00+09:00');
$event->setEnd($end_time);
$event->setLocation('ヨドバシAkiba');
$event->setSummary('QX100を触りに行く');
$event->setDescription('QX100が展示されてたらいろいろと弄ってみる。Wi-Fi接続とかNFCとか。');
$service->events->insert($calendarId, $event);

カレンダーサービスのリファレンスは以下を参照してください。

まとめ

Google APIをService Accountsとして利用する場合のいろいろをまとめてみました。ここまでできれば、他のカレンダーサービスと同期するとか、何らかのメールが届いたらカレンダーにイベントを登録するとか、いろいろできそうです:-)

また、基本的には「認証部分をどう書けばいいのか?」が確認できたので、他のサービスでも似たような感じで行けるんじゃないかと思います。

2013-08-28

[][]PHPThrift+HBaseを試してみた

使ってる人にとっては何周目かの今さら感漂いますが、ひょんなことから調べる必要が出てきたのでざっくりまとめてみました。

環境

  • CentOS6.3
  • PHP5.5.3
  • JDK1.6.0-45
  • HBase0.94.11
  • Thrift0.9.1

HBaseとは

HBaseはKVS(Key-Value Store)の1つで、ASF(Apache Software Foundation)のHadoopプロジェクトの一環として作られたオープンソースソフトウェアです。本家はHBase - Apache HBase? Homeです。

とりあえず、以下の記事をざっと読むと良いかと。

HBaseの論理データモデルは「多次元ソートマップ」で、テーブルやカラムといった概念がありますので、RDBMSを触ったことがある人は馴染みやすいかも。また、カラムをグルーピングした"カラムファミリー"という概念があります。カラムファミリーはテーブルに1つ以上存在し、カラムはいずれかのカラムファミリーに属します。

HBaseのインストール

"インストール"と言っても、ダウンロードして展開するだけです。

$ wget http://ftp.riken.jp/net/apache/hbase/stable/hbase-0.94.11.tar.gz
$ tar zxf hbase-0.94.11.tar.gz
$ cd hbase-0.94.11/
$ 

HBaseを起動・停止してみる

起動と停止は、直下のbinディレクトリにあるstart-hbase.shとstop-hbase.shを使います。環境変数JAVA_HOMEの設定を忘れずに。

$ export JAVA_HOME=/path/to/java_home
$ bin/start-hbase.sh
starting master, logging to /path/to/hbase-0.94.11/bin/../logs/hbase-hoge-master-mobylog64.out
$ 
$ bin/stop-hbase.sh
stopping hbase..................
$

HBase shell

HBaseにはJRuby製のshellが用意されていて、テーブルの作成やデータの登録などが可能です。helpを表示させると、大体どんなことができそうか分かると思います。

$ bin/hbase shell
HBase Shell; enter 'help<RETURN>' for list of supported commands.
Type "exit<RETURN>" to leave the HBase Shell
Version 0.94.11, r1513697, Wed Aug 14 04:54:46 UTC 2013

hbase(main):001:0> help
HBase Shell, version 0.94.11, r1513697, Wed Aug 14 04:54:46 UTC 2013
Type 'help "COMMAND"', (e.g. 'help "get"' -- the quotes are necessary) for help on a specific command.
Commands are grouped. Type 'help "COMMAND_GROUP"', (e.g. 'help "general"') for help on a command group.

COMMAND GROUPS:
  Group name: general
  Commands: status, version, whoami

  Group name: ddl
  Commands: alter, alter_async, alter_status, create, describe, disable, disable_all, drop, drop_all, enable, enable_all, exists, is_disabled, is_enabled, list, show_filters

  Group name: dml
  Commands: count, delete, deleteall, get, get_counter, incr, put, scan, truncate

  Group name: tools
  Commands: assign, balance_switch, balancer, close_region, compact, flush, hlog_roll, major_compact, move, split, unassign, zk_dump

  Group name: replication
  Commands: add_peer, disable_peer, enable_peer, list_peers, list_replicated_tables, remove_peer, start_replication, stop_replication

  Group name: snapshot
  Commands: clone_snapshot, delete_snapshot, list_snapshots, restore_snapshot, snapshot

  Group name: security
  Commands: grant, revoke, user_permission

SHELL USAGE:
Quote all names in HBase Shell such as table and column names.  Commas delimit
command parameters.  Type <RETURN> after entering a command to run it.
Dictionaries of configuration used in the creation and alteration of tables are
Ruby Hashes. They look like this:

  {'key1' => 'value1', 'key2' => 'value2', ...}

and are opened and closed with curley-braces.  Key/values are delimited by the
'=>' character combination.  Usually keys are predefined constants such as
NAME, VERSIONS, COMPRESSION, etc.  Constants do not need to be quoted.  Type
'Object.constants' to see a (messy) list of all constants in the environment.

If you are using binary keys or values and need to enter them in the shell, use
double-quote'd hexadecimal representation. For example:

  hbase> get 't1', "key\x03\x3f\xcd"
  hbase> get 't1', "key\003\023\011"
  hbase> put 't1', "test\xef\xff", 'f1:', "\x01\x33\x40"

The HBase shell is the (J)Ruby IRB with the above HBase-specific commands added.
For more on the HBase Shell, see http://hbase.apache.org/docs/current/book.html
hbase(main):002:0> exit
$ 

ここで、接続テストに使うためのテーブル"tbl"を作ってデータを突っ込んでおきます。定義は唯一のカラムファミリー"family"にカラム"column1"〜"column3"を所属させる感じ。

以下のコマンドで、putはSQLで言うところのINSERT文で、カラム単位にデータを登録します。scanはSELECT文に相当します。

$ bin/hbase shell
HBase Shell; enter 'help<RETURN>' for list of supported commands.
Type "exit<RETURN>" to leave the HBase Shell
Version 0.94.11, r1513697, Wed Aug 14 04:54:46 UTC 2013

hbase(main):001:0> create "tbl", "family", {NAME => "column1"}, {NAME=>"column2"}, {NAME=>"column3"}
0 row(s) in 1.2370 seconds

hbase(main):002:0> put 'tbl', 'test1', 'family:column1', 'value1'
0 row(s) in 3.1850 seconds

hbase(main):003:0> put 'tbl', 'test1', 'family:column2', 'value2'
0 row(s) in 0.0200 seconds

hbase(main):004:0> put 'tbl', 'test1', 'family:column3', 'value3'
0 row(s) in 0.0370 seconds

hbase(main):011:0> put 'tbl', 'test2', 'family:column1', 'value1'
0 row(s) in 0.0500 seconds

hbase(main):012:0> put 'tbl', 'test3', 'family:column1', 'value1'
0 row(s) in 0.0100 seconds

hbase(main):005:0> scan 'tbl'
ROW                                       COLUMN+CELL
 test1                                    column=family:column1, timestamp=1377683232461, value=value1
 test1                                    column=family:column2, timestamp=1377683293228, value=value2
 test1                                    column=family:column3, timestamp=1377683298505, value=value3
 test2                                    column=family:column1, timestamp=1377683807442, value=value1
 test3                                    column=family:column1, timestamp=1377683810364, value=value1
3 row(s) in 0.0930 seconds

hbase(main):006:0> exit
$ 

RDBMSとは異なり、カラムごとに1行ずつ出力されます。

Thriftとは

ThriftFacebookにて開発されたRPCフレームワークで、本家はApache Thrift - Homeです。また、PHPとHBaseを仲介するProxyサーバーとして起動することで、PHPとHBase間でデータをやりとりすることができます。

この時、.thriftファイルと呼ばれるファイルをThriftが持っているコード生成エンジンに食わせることで、PHPを含む様々な言語でのソースコード(クライアントサーバー)を出力することができます。SOAPで言うwsdlファイルと生成されるProxyコードみたいな感じですかね。

Thriftインストール

Thriftインストールの前に事前準備。必要となるパッケージ郡インストールします。

$ sudo yum install -y automake libtool flex bison pkgconfig gcc-c++ boost-devel libevent-devel zlib-devel python-devel ruby-devel
$

Thriftアーカイブダウンロードし、configure・make・make installすればOK。。。と言いたいところですが、Thriftにはthrift_protocolというPHP拡張モジュールが含まれていますが、buildに必要なファイルが含まれていないっぽいです。。。このため、1つ前のバージョンのThrift0.9.0から拝借してきます。

$ wget http://ftp.riken.jp/net/apache/thrift/0.9.0/thrift-0.9.0.tar.gz
$ tar zxf thrift-0.9.0.tar.gz
$ wget http://ftp.riken.jp/net/apache/thrift/0.9.1/thrift-0.9.1.tar.gz
$ tar zxf thrift-0.9.1.tar.gz
$ cp -rp thrift-0.9.0/lib/php/src/ext/thrift_protocol thrift-0.9.1/lib/php/src/ext/
$ tar zxf thrift-0.9.1.tar.gz  # 再度展開
$ ./configure
$ make
$ sudo make install
$ 

インストールすると、PHPからの接続に必要となるライブラリPEARディレクトリにコピーされます。

なお、PHP拡張のインストール先などはRPMPHPのものを基準としているようなので、ソースからインストールした場合でconfigure時に--prefixを指定したりデフォルトのままの場合、そのインストールディレクトリ環境変数PATHに含めておく必要があります。また、必要に応じてPHP_CONFIG_PREFIX()を指定してください。さらに、make install時にもPATHが通っている必要があります。

$ ./configure PHP_CONFIG_PREFIX=/usr/local/lib/php/lib/
$ make
$ su
# make install
# exit
$ 

Thriftを起動・停止してみる

Thriftサーバーとして起動するためには、HBaseのbinディレクトリにあるhbaseコマンド、もしくは、hbase-daemon.shを使います。いずれも環境変数JAVA_HOMEの設定を忘れずに。

まずは、hbaseコマンドの場合の例。

$ export JAVA_HOME=/path/to/java_home
$ cd ../hbase-0.94.11/
$ bin/hbase thrift start

なお、ログが標準エラー出力にそのまま出力されますので、適宜リダイレクトteeコマンドを使いましょう。停止はCTRL-Cで。

次にhbase-daemon.shコマンドの場合の例。

$ export JAVA_HOME=/path/to/java_home
$ cd ../hbase-0.94.11/
$ bin/hbase-daemon.sh start thrift
starting thrift, logging to /path/to/hbase-0.94.11/bin/../logs/hbase-hoge-thrift-mobylog64.out
$ 
$ bin/hbase-daemon.sh stop thrift
stopping thrift.
$ 

PHPThrift+HBase

まずは、HBaseの.thriftファイルからPHP用のコードを作成します。

$ cd ../
$ thrift --gen php hbase-0.94.11/src/main/resources/org/apache/hadoop/hbase/thrift/Hbase.thrift
$ 

直下にgen-phpディレクトリと、その中に

が作成されていることを確認します。ただし、namespaceがイケてないので、ここではコメントアウトしておきます。

$ perl -i -p -s -e "s#^namespace ;#//namespace ;#g" gen-php/*.php
$ 

次に、gen-phpディレクトリにテスト用スクリプト(test.php)を作成します。

<?php
function autoload($className)
{
    $className = ltrim($className, '\\');
    $fileName  = '';
    $namespace = '';
    if($lastNsPos = strrpos($className, '\\')) {
        $namespace = substr($className, 0, $lastNsPos);
        $className = substr($className, $lastNsPos + 1);
        $fileName  = str_replace('\\', DIRECTORY_SEPARATOR, $namespace) . DIRECTORY_SEPARATOR;
    }
    $fileName .= str_replace('_', DIRECTORY_SEPARATOR, $className) . '.php';

    require $fileName;
}
spl_autoload_register('autoload');

include 'Hbase.php';
include 'Types.php';

use \Thrift\Transport\TSocket;
use \Thrift\Transport\TBufferedTransport;
use \Thrift\Protocol\TBinaryProtocol;

$server = 'localhost';
$port = 9090;

try {
    $socket = new TSocket($server, $port);
    $socket->setRecvTimeout(5000);
    $transport = new TBufferedTransport($socket);
    $protocol = new TBinaryProtocol($transport);
    $client = new HbaseClient($protocol);

    $transport->open();

    /**
     * 利用可能なテーブルの一覧を取得
     */
    var_dump($client->getTableNames());

    /**
     * SQLで言うと"SELECT * FROM tbl LIMIT 2"に相当
     */
    $scan = new TScan();
    $scan = $client->scannerOpenWithScan('tbl', $scan, null);
    var_dump($client->scannerGetList($scan, 2));

} catch (TException $e) {
    error_log('TException');
    error_log($e);
} catch (Exception $e) {
    error_log('Exception');
    error_log($e);
}

で、実行。データが無いカラムは出力されないことに注意です。

$ export JAVA_HOME=/path/to/java_home
$ cd ../hbase-0.94.11/
$ bin/start-hbase.sh
$ bin/hbase-daemon.sh start thrift
$ php -v
PHP 5.5.3 (cli) (built: Aug 23 2013 19:04:46)
Copyright (c) 1997-2013 The PHP Group
Zend Engine v2.5.0, Copyright (c) 1998-2013 Zend Technologies
$ php test.php
$ php test.php
array(1) {
  [0]=>
  string(3) "tbl"
}
array(2) {
  [0]=>
  object(TRowResult)#7 (3) {
    ["row"]=>
    string(5) "test1"
    ["columns"]=>
    array(3) {
      ["family:column1"]=>
      object(TCell)#9 (2) {
        ["value"]=>
        string(6) "value1"
        ["timestamp"]=>
        int(1377683232461)
      }
      ["family:column2"]=>
      object(TCell)#10 (2) {
        ["value"]=>
        string(6) "value2"
        ["timestamp"]=>
        int(1377683293228)
      }
      ["family:column3"]=>
      object(TCell)#11 (2) {
        ["value"]=>
        string(6) "value3"
        ["timestamp"]=>
        int(1377683298505)
      }
    }
    ["sortedColumns"]=>
    NULL
  }
  [1]=>
  object(TRowResult)#8 (3) {
    ["row"]=>
    string(5) "test2"
    ["columns"]=>
    array(1) {
      ["family:column1"]=>
      object(TCell)#13 (2) {
        ["value"]=>
        string(6) "value1"
        ["timestamp"]=>
        int(1377683807442)
      }
    }
    ["sortedColumns"]=>
    NULL
  }
}
$ 

SQLで言うところのWHERE句

"フィルター"と呼ばれるものがそれに相当します。色々なフィルターが用意されていますので、詳細は以下のURLを読んでみてください。

フィルターはPHP側でも利用可能で、以下はcolumn1の値を正規表現マッチしたカラムを取得する例です。PostgreSQLだと

SELECT column1 FROM tbl WHERE column1 ~ 'e[13]$'

な感じですかね?

<?php$scan = new TScan();
    $scan->columns = array('family:column1');
    $scan->filterString = "ValueFilter(=,'regexstring:e[13]$')";
    $scan = $client->scannerOpenWithScan($tablename, $scan, null);
    :

その他メソッドについて

thriftコマンドで生成されたHbase.phpの先頭で定義されているHbaseIfインターフェースを眺めてもいいんですが、大元のHbase.thriftファイルを見たほうが型定義がハッキリするので、こちらの方がオススメです。

名前空間をサポートしないPHP5.2系はどうすれば?

Thrift0.9.0以降で生成されるPHPコードや同梱されているPHPライブラリでは名前空間を使用しています。このため、PHP5.2系などではエラーになります。1つ前のThrift0.8.0であれば名前空間を使用していないので、PHP5.2系でも動作します。試したところ、HBase+ThriftサーバはThrift0.9系、PHP側のみThrift0.8.0でも問題なく動作しました。

まとめ

またもや「ちゃんと繋げられましたよー」で終わっちゃうわけですが。。。

個人的には(RDBでなはいけど)DB系の新ネタだったので、「SQLで○○○なのはどうやるんだろ?」とかいろいろ試してました。

ちなみに、scannerOpenWithScanメソッドの第4引数にnullを指定していますが、ここって何を指定するんですかね。。。?もし、ご存じの方がいらっしゃいましたら教えてもらえるとありがたいです:-)

2013-06-26

[]Temperature LayerというAndroidアプリ作った

自分が欲しいと思ってたアプリを作ってGooglePlayで公開してみました。

アプリ名は?

Temperature Layerといいます。

Temperature Layerとは?

Temperature Layerはバッテリー温度を表示するだけのシンプルなAndroidアプリケーションです。

ステータスバー上のアイコンウィジェットとして温度を表示するではなく、画面上に透明なレイヤをかぶせて表示しますので、HOME画面や他のアプリの邪魔になりません。

Temperature LayerはGoogle Playから入手できます。

特徴

  • Android2.0以降に対応
  • バッテリー温度を画面上に透明なレイヤをかぶせて表示
  • 端末起動時の自動起動を設定可能
  • 単位は摂氏(°C)、華氏(°F)から選択
  • 表示位置はスクリーンの四隅から選択(ステータスバーの上にも表示可能)
  • テキストサイズは6〜100sp(scaled-pixel)、テキスト色は透明度も含めて指定可能
  • 英語と日本語に対応
  • 無料です!

画面キャプチャ

f:id:shimooka:20130626133724p:image (大きいサイズ)

ソースコード

ソースコード(eclipseプロジェクト一式)をGitHubにて公開しています。ライセンスApache License, Version 2.0です。

謝辞

このアプリケーションAndroid Color Pickerを使用しています。作者のyukuku氏に感謝!

ライセンス

Copyright © 2013 Hideyuki SHIMOOKA <shimooka@doyouphp.jp>

Licensed under the Apache License, Version 2.0

きっかけ

初めてのAndroid端末(X06HT無印/HTC Desire)を買った時、充電中にバッテリーが結構熱くなるのが気になってTemp+CPUとか入れてたんですが、ウィジェットで表示するタイプなんですよね。最初はこれでも良かったんですが、徐々にHOME画面にウィジェットショートカットの類を配置しない方が好みになってきて、「ジャマにならずに温度を表示するアプリがほしい」という流れに。

そう思ったのは随分前ですが、仕事でAndroidアプリ用のライブラリとか作ったりして何となく全体像がつかめてきたので、重かった腰を上げて作ってみました:-)

作ってみてどうだったか?

  • AndroidアプリUI・サービス・テスト込みで作り、Google Playで公開するところまで一通り経験できたことが一番大きかったかな、と。Webアプリ開発と違う部分が多いので、かなり新鮮でした。
  • メインの表示部分はそんなに難しいことをしてるわけじゃないんですが、設定画面が意外と大変でした(「技術的に」という意味ではなく、「色々と考慮することが多い」という意味で)。
  • Google Play Developer Consoleで公開設定を行なっても、実際にGoogle Playに公開されるまで数時間待たされるのはどうにかなら。。。ないか。。。あ、あと、アプリの説明などを考えるのが結構大変ですorz

まとめ

自分が欲しいと思ってたアプリなので最低限自分が使えれば全然問題ないんですが、折角なのでソースコード込みで公開しました。何かの役に立てば幸いです:-)