phpDocumentor 2 で使える型名

@var @return @param に使える型の書き方です。
1.4. Definition of a ‘Type’ — phpDocumentor v0.13 documentation のまとめ。

Keyword 基本型など

PHP の型
  • null
  • string
  • integer (int)
  • boolean (bool)
  • float (double)
  • object
  • array
  • resource
PHP の疑似型
  • void
  • callback
  • mixed
phpDocumentor 2 で初めて見た型
  • true false
  • self

メソッドチェーンを多用する身としては、 self が非常に嬉しい。
というか PDT も self 対応してた
まじか……

class-name クラス名

そのスコープにおける namespace も考慮してくれる模様。

Multiple-types 複数の型を指定する

で区切って列挙する。

integer|float|null
など。

Arrays 配列

  • array
  • int[]
  • (int|string)[]
  • ((bool|null)|string)[]

配列のキーの型を指定する方法はない模様。
[] を使った記法は、もちろん PDT 非対応

PHP でシェルスクリプトを作成し、制御をシェルに戻したい場合

proc_open() で、 PHP が作る子プロセスの stdin/stdout/stderr と、 PHP プロセス自体のそれらを繋ぐ。

具体例

大量にある MySQL サーバに簡単にログインできるようにするためのスクリプトを書きたい。

実装例

mysql_connect.php (実行権限つき)

#!/usr/bin/php -f
<?php
// usage: mysql_connect.php database_name

$dbname = $argv[1];

// get_connection_info() は適当に自作する。
list($host, $user, $password) = get_connection_info($dbname);

$proc = proc_open(
    "mysql -h $host -u $user --password=$password -A --prompt=\"\u:\d>\_\" \"$dbname\"",
    array(
        0 => STDIN,
        1 => STDOUT,
        2 => STDERR,
    ),
    $pipes
);

if (!$proc) {
    fwrite(STDERR, "Failed to connect $dbname ($user@$host)");
    exit(1);
}

foreach ($pipes as $fp) {
    fclose($fp);
}

proc_close($proc);

【再訪】重みを持つ要素の配列から、ランダムに1つ選択する。

http://hirobanex.net/article/2011/12/1324792864
http://blog.livedoor.jp/dankogai/archives/51761113.html

似たようなのを 以前 書いたので、改めてまとめただけ。

使い方

/**
 * weight は 1 以上の整数。
 * 合計を 100 にしなければならない……なんてことはない。
 * weight が大きいほうが選択されやすい。
 */
var weighted = new Weighted([
    {value: "hoge", weight: 30},
    {value: "moge", weight: 30},
    {value: "foo",  weight: 20},
    {value: "bar",  weight: 10},
    {value: "jar",  weight: 10},
]);
/**
 * weight の合計が 100 なので、それぞれの出る確率は
 * hoge = 30 / 100
 * moge = 30 / 100
 * foo  = 20 / 100
 * bar  = 10 / 100
 * jar  = 10 / 100
 * となる。
 */

var result = {};
for (var i = 0; i < 100000; ++i) {
    // 重みを考慮して、ランダムに選択する。
    var value = weighted.select();
    if (value in result) {
        ++result[value];
    } else {
        result[value] = 1;
    }
}

var text = [];
for (var p in result) {
    text.push(p + ": " + result[p]);
}
alert(text.join("\n"));

/*
hoge: 29823
jar: 9943
foo: 20224
moge: 30099
bar: 9911

確率的には正しそうな結果になった。
*/

定義

function Weighted(input)
{
    var i = 0,
        length = input.length,
        pair = null,
        offset = null,
        sorted = new Array(length * 2),
        values = new Array(length),
        parameter = 0;

    for (; i < length; ++i) {
        pair = input[i];
        values[i] = pair.value;
        parameter += pair.weight;
        offset = i * 2;
        sorted[offset] = i;
        sorted[offset + 1] = parameter;
        sorted.push(i, parameter);
    }
    sorted.pop(); // 最後の要素は parameter と同じなのでいらない。

    this.sorted = sorted;
    this.parameter = parameter;
    this.values = values;
}

Weighted.prototype.select = function Weighted_select()
{
    var sorted = this.sorted,
        length = sorted.length,
        left = 1,
        right = length - 2,
        middle = null,
        ceil = Math.ceil,
        rand = Math.floor(Math.random() * this.parameter);

    if (length < 2) {
        return length == 1 ? this.values[0] : null;
    }
    do {
        middle = ceil((left + right) / 2);
        if (!(middle & 1)) {
            ++middle;
        }
        if (sorted[middle] > rand) {
            right = middle - 2;
            if (right < left) {
                return this.values[sorted[middle - 1]];
            }
        } else {
            left = middle + 2;
            if (left > right) {
                return this.values[sorted[middle + 1]];
            }
        }
    } while (true);
};

わざわざ二分探索を使った理由は、

  • 1000 種類のアイテムのうち、 500 種類は同じ確率で、 200 種類はより低い確率で、 100 種類はより低い確率で……

なんていう状況をターゲットにしていた為。
データ量が少なければ、重い順に舐めていったほうがいいだろうけど、量が多い場合はこうしないときつかった。

MySQL によるランキング管理

CREATE TABLE ranking (
    user_id INT UNSIGNED NOT NULL COMMENT "ユーザID",
    score INT NOT NULL COMMENT "順位付けに使う値",
    rank INT UNSIGNED NOT NULL COMMENT "順位",

    PRIMARY KEY (user_id),
    INDEX USING BTREE (score)
);
user_id score rank
1 100 0
2 70 0
3 50 0
4 50 0
5 20 0

というテーブルがあったとき、

SET @rank = 1, @rownum = 0, @prev = null;
UPDATE ranking SET rank = (@rank := IF((@rownum := @rownum + 1) AND (@prev <=> score OR (@prev := score) <=> NULL), @rank, @rownum)) ORDER BY score DESC;

することで

user_id score rank
1 100 1
2 70 2
3 50 3
4 50 3
5 20 5

となります。


ranking テーブル自体をメモリテーブルにして、 1 分ごとに上クエリを発行していれば、中々最新のデータになるのではないでしょうか。
※もちろん score の元データは別のテーブルでちゃんと記録されてるとして。

携帯用ページ上における各種SNS連携方法まとめ

mixi (mixiチェック)

<form method="POST" action="http://m.mixi.jp/share.pl?guid=ON">
    <input type="hidden" name="charset" value="shift_jis もしくは utf-8 。デフォルトは shift_jis" />
    <input type="hidden" name="check_key" value="識別キー" />
    <input type="hidden" name="title" value="リンク先のタイトル (charset の文字エンコーディング)" />
    <input type="hidden" name="description" value="リンク先の説明文 (charset の文字エンコーディング)" />
    <input type="hidden" name="content_rating" value="19歳未満非対応の場合は 1" />
    <input type="hidden" name="image" value="サムネイル画像のURL" />
    <input type="hidden" name="primary_url" value="リンク先のURL (*1)" />
    <input type="hidden" name="pc_url" value="リンク先のURL(PC用)" />
    <input type="hidden" name="smartphone_url" value="リンク先のURL(スマートフォン用)" />
    <input type="hidden" name="mobile_url" value="リンク先のURL(携帯用) 携帯電話向け URL のいずれかは必須" />
    <input type="hidden" name="mobile_docomo_url" value="リンク先のURL(DoCoMo携帯用)" />
    <input type="hidden" name="mobile_au_url" value="リンク先のURL(au携帯用)" />
    <input type="hidden" name="mobile_softbank_url" value="リンク先のURL(SoftBank携帯用)" />
    <input type="submit" value="mixiチェック" />
</form>
<!-- (*1) primary_url は「チェックされた対象」を識別するために使われます。個別のチェックのうち、primary_url が同じものは同じ対象をチェックしたものとして扱われます。-->

mixi パートナーに登録して、mixi チェック用の識別キーを取得する必要がある。
詳細は http://developer.mixi.co.jp/connect/mixi_plugin/mixi_check/spec_mixi_check/
どのパラメータが必須でどのパラメータが省略可能かは↑を参照。

GREE (Social Feedback)

<?php
// PHP
$url = urlencode('リンク先のURL');
$button_type = 0; // 0-4 の値。
$button_size 16; // 16, 20, 22, 23, 32 のいずれか。
$button_src = 'リンクに使う画像のURL';
// 以上、全て必須パラメータ。
?>
<a href="http://m.gree.jp/?mode=share&act=write&url=<?=$url?>&button_type=<?=$button_type?>&button_size=<?=$button_size?>"><img src="<?=$button_src?>" /></a>

詳細は https://developer.gree.co.jp/connect/plugins/sf
リンクに使う画像は http://i.share.gree.jp/img/share/button/ 以下に用意されているが、全て PNG なので、携帯 (というか古い DoCoMo) で表示するには、 GIF に変換して自分のところにアップロードする必要がある。
さらに (なぜだか知らないが) GREE のページから $url へ遷移してくるとき、 & が &amp; に変換される。
http://example.com/foo?a=1&b=2 という URL を指定した場合は、 http://example.com/foo?a=1&b=2 という URL でアクセスされる。
PHP の arg_separator が &amp; でないと正常に $_GET に格納されないので注意。
私の場合、 arg_separator は & のままで、 $_SERVER['REQUEST_URI'] 中で &amp; があったら & に変換しつつ、自分で $_GET に代入して対処している。
button_type は 0-4 があるが、多分どれでもいい。 GREE 側でどれが一番使われてるか計測してるんだろう、きっと。

twitter (tweet)

<?php
// PHP
$url = urlencode('リンク先の URL');
$text = urlencode('リンク先の説明文 (UTF-8)');
$via = 'フォローさせたいアカウントその1'; // 省略可。
$related = 'フォローさせたいアカウントその2'; // 省略可。
?>
<a href="http://twtr.jp/share?guid=ON&url=<?=$url?>&text=<?=$text?>&via=<?=$via?>&related=<?=$related?>">つぶやく</a>

詳細は https://dev.twitter.com/docs/tweet-button
via と related は、つぶやいた後におすすめユーザとして表示される。 via を未だフォローしていなければ via が、そうでなければ related が、どちらもフォロー済であれば省略される。
なお、 twtr.jp を使っているユーザが、ポストされた URL へ遷移すると、 Google Wireless Transcoder で中継されてしまう。
これを解消するには、リンク先のページの 以下に

<link rel="alternate" media="handheld" href="携帯用URL" />

を入れておく。
参考: http://d.hatena.ne.jp/w6500/20110511

Facebook (Share)

<?php
// PHP
$url = urlencode('リンク先の URL');
$text = urlencode('リンク先の説明文 (UTF-8)');
?>
<a href="http://m.facebook.com/sharer.php?guid=ON&u=<?=$url?>&t=<?=$text?>">シェア</a>

どうやら古いインターフェースのようで、公式の詳細が見つからなかった。
ウォールに投稿される。

gcc4.4環境でswfmill-0.2.12をコンパイルする

パッチを書いた。
KLabさんとこのエンコーディングパッチ充ててからやってください。

コンパイル

wget http://swfmill.org/releases/swfmill-0.2.12.tar.gz
tar xf swfmill-0.2.12.tar.gz
cd swfmill-0.2.12
wget http://lab.klab.org/files/flash/encoding.patch
patch -p1 < encoding.patch
wget http://xif.jp/src/swfmill/0.2.12/gcc44.patch
patch -p1 < gcc44.patch
./configure
make
make install

http://xif.jp/src/swfmill/0.2.12/gcc44.patchの中身

diff -ur swfmill-0.2.12/src/swft/swft_css.cpp swfmill-0.2.12.gcc44/src/swft/swft_css.cpp
--- swfmill-0.2.12/src/swft/swft_css.cpp	2006-07-20 22:57:17.000000000 +0900
+++ swfmill-0.2.12.gcc44/src/swft/swft_css.cpp	2011-04-30 18:16:35.640273197 +0900
@@ -6,6 +6,7 @@
 #include <libxslt/xsltutils.h>
 #include <libxml/xpathInternals.h>
 #include "swft.h"
+#include <string.h>
 
 using namespace std;
 #define TMP_STRLEN 0xff
@@ -236,4 +237,4 @@
 	//fprintf(stderr,"looking up style %s: %s\n", needle, r.c_str() );
 	
 	valuePush( ctx, xmlXPathNewString( (const xmlChar *)r.c_str() ) );
-}
\ No newline at end of file
+}
diff -ur swfmill-0.2.12/src/swft/swft_import.cpp swfmill-0.2.12.gcc44/src/swft/swft_import.cpp
--- swfmill-0.2.12/src/swft/swft_import.cpp	2006-07-20 22:57:17.000000000 +0900
+++ swfmill-0.2.12.gcc44/src/swft/swft_import.cpp	2011-04-30 18:16:53.984310124 +0900
@@ -10,7 +10,7 @@
 	int l;
 	
 	// figure basename (filename without path)
-	b = strrchr( filename, '/' );
+	b = strrchr( const_cast<char*>(filename), '/' );
 	basename = b ? b+1 : filename;
 	
 	l = strlen(basename);
diff -ur swfmill-0.2.12/src/swft/swft_import_mp3.cpp swfmill-0.2.12.gcc44/src/swft/swft_import_mp3.cpp
--- swfmill-0.2.12/src/swft/swft_import_mp3.cpp	2006-08-17 19:38:49.000000000 +0900
+++ swfmill-0.2.12.gcc44/src/swft/swft_import_mp3.cpp	2011-04-30 18:17:06.328336831 +0900
@@ -5,6 +5,7 @@
 #include "swft.h"
 #include <sys/types.h>
 #include <sys/stat.h>
+#include <string.h>
 
 #define TMP_STRLEN 0xff
 

重みを持つ要素の配列から、ランダムに1つ選択する。

地道に for だの使って処理してもいいんだけど、配列の要素の数が巨大になったときのパフォーマンスが心配だったので、二分探索にしてみた。
例えば、
『数十種類あるメッセージのうち1つをランダムに表示したいけど、うち数種類はレアにしたい』
みたいな時に使えるはず。
こういう処理は割と頻繁に使うので、汎用化しておくと便利。

サンプルコードは javascript

/**
 * weight が大きいほど選択される可能性が高い。
 * weight は 1 以上でなければならない。
 */
var messages = [
    {message: "Hello1", weight: 100}, // 100/188 で選択される。
    {message: "Hello2", weight: 50},  // 50/188 で選択される。
    {message: "Hello3", weight: 25},  // 以下略。
    {message: "Hello4", weight: 12},
    {message: "Hello5", weight: 1},
];

var input = [];
for (var i = 0, length = messages.length; i < length; ++i) {
    input.push(messages[i].weight);
}

var map = makeMap(input);
// {tree: [0, 100, 1, 150, 2, 175, 3, 187, 4], parameter: 188}
// input が大規模でも、 map をキャッシュしておけば速度は稼げる。
var key = getKey(map.tree, randomBetween(0, map.parameter - 1));
alert(messages[key].message);

function makeMap(input)
{
    var length = input.length;
    var parameter = 0;
    var tree = [];
    for (var i = 0; i < length; ++i) {
        parameter += input[i];
        tree.push(i, parameter);
    }
    tree.pop(); // 最後の parameter は tree には要らないので取り除く。
    return {tree: tree, parameter: parameter};
}

function getKey(tree, value)
{
    var length = tree.length;
    if (length < 1) {
        return null;
    }
    if (length === 1) {
        return tree[0];
    }
    var left = 1;
    var right = length - 2;
    var ceil = Math.ceil;
    do {
        var middle = ceil((left + right) / 2);
        if (!(middle & 1)) {
            ++middle;
        }
        if (tree[middle] > value) {
            right = middle - 2;
            if (right < left) {
                return tree[middle - 1];
            }
        } else {
            left = middle + 2;
            if (left > right) {
                return tree[middle + 1];
            }
        }
    } while (true);
}

function randomBetween(min, max)
{
    return Math.floor(Math.random() * (max - min + 1)) + min;
}