2009年7月22日 (水)

array_unique関数の海で海賊5.2.9船団と戦う

PHPのお話です。もしかしたら海賊のお話です。


昔、ある海域では海賊たちは決して堅気の船を襲いませんでした。世界の成り立ちは今よりいくぶんシンプルで、任侠にはまだ美学が残っていました。

そんなわけで漁師たちは安心して沖へ出てゆくことができました。


しかしある年に、破天荒でやんちゃな海賊(5.2.9船団)が出現、その海域を席巻し、漁師以外の船もたびたび襲われるという事態が起こりました。

漁船は襲わないが、それ以外はなんであろうと略奪するというのが、彼らのモットーです。

漁師にはまだ同じ海を生きる男としての多少なりのリスペクトのようなものが働いていたのかもしれませんが、そうでなければたとえ老人と一匹の犬しか乗っていないような無邪気な船でも、海へ出た理由が素っ頓狂なものであれば、それはもうもみくちゃのひどい目にあいました。

漁師としても、漁師の呈を成していなければ老人や犬のように扱われたので今までのようには安心して暮らせません。


不安で漁獲高が減りました。


海賊の男気にもやはり抵触するものがあったのでしょうか、翌年から幸いにも、やはり堅気の船は襲うまいと、かつての暗黙のルールが復活することになりました。

これで以前の穏やかな暮らしに戻れると、漁師たちは安堵の胸をなでおろしましたが、ことはそう簡単には収まりませんでした。

やんちゃな海賊(5.2.9船団)は、海賊であるがゆえに、一度あげた旗をおろすのと同じくらい、前言撤回を嫌いました。一度男が口にした以上、なんとしても漁師以外の船は略取強奪のターゲットなのです。
そして今でもこの海域で彼らは目を光らせているのです。

漁師に告ぐ。襲われたくなくば、漁師らしくせよ、などと彼らが言ったかどうかは定かではありませんが、それ以来、漁師たちは現在でもきちっと、他の船と判別できるように、漁船に高々と漁船旗を掲げているそうです。


僕たちの暮らす海は平和でよかったですね。

いや、PHPの話でした。


やんちゃな海賊: PHP ver5.2.9
海の名前: array_unique()
暗黙のルール: デフォルト引数
堅気の船は襲わない: SORT_STRING(文字列比較)
漁船は襲わない: SORT_REGULAR(通常の比較)


バージョン5.2.9までのPHPでは、array_unique関数に第2引数は存在しませんでした。
第1引数に配列を渡すだけで、暗黙のルールとして要素を「文字列として」比較、ソートしていました。

しかし5.2.9から、array_unique関数は第2引数を設けて、ソート方法を選べるようにしましたが、明示しなかった(旗を掲げなかった)場合、型を考慮しない「通常の比較で」ソートされることとなりました。
ここで、5.2.9は下位互換を失ったこととなります。

その後内部でいろいろあったのでしょうか、次のバージョン5.2.10からは明示しなかった場合の挙動は、再び「文字列として」比較、ソートするように変更されました。

使用されているPHPのバージョンがすでに5.2.9超である、もしくは第2引数を明示している、というのでなければ、バージョンアップをおこなった途端に運用中のサービスに支障をきたすかもしれません。
たとえばDBから商品の累積人気投票を配列に取得して、そこから投票理由や付与ポイントごとにソートして順位を発表とかしていると、5.2.9だけ結果が異なる、とか5.2.9以外では結果が異なる、ということになるかもしれません。

例をみてみます。


$array = array(0, '000', 'hello', 'world');

// ver5.2.9以外
var_dump(array_unique($array));
// array(4) { [0]=> int(0) [1]=> string(3) "000" [2]=> string(5) "hello" [3]=> string(5) "world" }

// ver5.2.9
var_dump(array_unique($array));
// array(1) { [0]=> int(0) }

5.2.9のデフォルト引数のSORT_REGULARでは、型が考慮されていないので、最初の数値0に比べられて、すべて等価であるとみなされてしまいました。


というわけで、5.2.9を使用されていて、第2引数を明示されていない場合は、互換性を確保するために

$array = array_unique($array, SORT_REGULAR);

としましょう。そうすることで5.2.9超でも同じ動作をしてくれます。


5.2.9以前で、第2引数が存在しない場合は

$array = version_compare(PHP_VERSION, '5.2.9', '>=') ? array_unique($array, SORT_STRING) : array_unique($array);

としましょう。そうすることで、5.2.9でも5.2.9超でも、5.2.9以前と同じSORT_STRINGで動いてくれます。

これは船に高々と掲げられた漁船旗のメタファーです。

# 間違いやよりいい方法があればどうぞご指摘ください。

参考:
array_unique関数がPHP5.2.9から後方互換性を失いました - hnwの日記 -

PHP:array_unique - Manual -

| | コメント (0) | トラックバック (0)

2008年1月 6日 (日)

経過年数を動的生成(Smarty版)

Smartylogoorange
blogやウェブサイトにSmartyというテンプレートエンジンを使ってる方も多いと思います。あれは便利ですね。
慣れると元には戻れません。

特に変化のあまり必要ない静的なコンテンツや最初に表示する内容が決まっているページは、毎回コンパイルしないでキャッシュした方のファイルを使うこともできるので、とても快適です。特に後者でデータべースから内容を拾って表示するような場合は、手にとるように速くなったのが分かります。

でもいくら静的だったりデフォルト表示でも、ここは変えたいなあと思う場所があります。

blogみたいに執筆日時が明確だと特に気にならないのですが、TIPSとかアーカイブとか、資料のようにして展開しているページの場合、ついうっかり○年前と書くと、年が変わったときに整合がとれなくなってしまいます。

そんなとき、Smartyは任意の場所のみ、キャッシュファイルを使わず常にリアルタイムに動的生成してくれるという機能を補完しています。
すごい!

なので、その機能を使って、常に整合のとれた経過年数を返してくれる簡単な関数をつくってみました(キャッシュを使わなくても有効です)。


■ロジックファイル側:
// 現在の年から当時の年をひいて、相対経過年数を動的に返す

function insert_relativeYear( $value ) {

  $relativeYear = (int)( date("Y") - $value['when'] );
  
  if ( ! $value['intFlg'] ) {
    if ( $relativeYear === 0 ) {
      $relativeYear = "今年";
    } elseif ( substr( $relativeYear, 0, 1 ) === "-" ) {
      switch( substr( $relativeYear, 1 ) ) {
        case "1":
        $relativeYear = "来年";
        break;
        case "2":
        $relativeYear = "再来年";
        break;
        default:
        $relativeYear = mb_convert_kana( substr( $relativeYear, 1 ), "N" ) . "年後";
      }
    } else {
      switch( $relativeYear ) {
        case "1":
        $relativeYear = "去年";
        break;
        case "2":
        $relativeYear = "一昨年";
        break;
        default:
        $relativeYear = mb_convert_kana( $relativeYear, "N" ) . "年前";
      }
    }
  }
  return $relativeYear;
}
?>


■テンプレートファイル側:
{insert name="relativeYear" when=半角で西暦年 [ intFlg=真偽 ]}


■使用例:
今年が2008年の場合――

{insert name="relativeYear" when=2006}
返り値: 一昨年 (全角string)

{insert name="relativeYear" when=2005}
返り値: 3年前 (全角string)

{insert name="relativeYear" when=2007 intFlg=true}
返り値: -1 (半角integer)

{insert name="relativeYear" when=2010 intFlg=true}
返り値: 2 (半角integer)


まーーー実に些細なことなんですが、書き換えなくて済むのはよいですね。以前は年を越えるたびに直してました。
もちろんはじめから西暦表示していればこんなの必要ないじゃんという話なのですが、ここらへんはそのサイトの性格とかになってきます。やっぱり口語的に「去年」とか書きたいひとにはよいのではないでしょうか。

経過時間を年ではなく、月とか日にち単位で表示する関数も、こんなノリでできちゃいますね。

| | コメント (2) | トラックバック (0)