PHP、traitsってどう使うのさ?

traitsとは、PHP5.4で実装された複数のクラスに実装する共通メソッドを定義するための仕組み。Programming PHPを読んでもtraitsの使いどころがわからない。スーパークラスじゃダメなの?

web検索すると、疑問にズバリ答えてくれるブログが見つかった!

要約すると、traitsは「多重継承を実現するための仕組み」で、「デザインパターンなど、必ず共通で使う枠組みでソースのコピペをなくしたい時に使用」するものだそうだ。ここではSingletonを例にとって説明してくれている。

 

<?php
trait SingletonTrait
{
    private static $instance;
 
    private function __construct() { }
 
    public static function getInstance()
    {
        if (!isset(self::$instance)) {
            self::$instance = new self();
        }
        return self::$instance;
    }
}

class SomeManager
{
    // シングルトンパターンを利用する
    use SingletonTrait;
 
    public function processSomething()
    {
        // ...
    }
}
 
// クラスの利用
$mngr = SomeManager::getInstance();
$mngr->processSomething();

 なるほどね!ありがとうございます!


詰碁印刷2

詰碁印刷のためのプログラム – diary 六帖
の続き。今日はSGFのパーサーを作るところまでで時間切れで終了。来週はcanvasに問題図と正解図を描いて並べて表示したい。
kickzone/SGFPrint · GitHub

SGFファイル仕様参考
SGFƒtƒ@ƒCƒ‹Œ`Ž®

次のサイトを参考にして、とてもスタイリッシュなswitch文の変形が書けました。ありがとうございます。qiita.com
コードの先頭で使ってます。

var SGFParser = (function(){
var parseItemSub = {};
//黒置き石
parseItemSub['AB'] = function(str, item){
item.okiishi.push(new SGFStone(BLACK, str));
}
//白置き石
parseItemSub['AW'] = function(str, item){
item.okiishi.push(new SGFStone(WHITE, str));
}
//コメント
parseItemSub['C'] = function(str, item){
item.comment = str;
}
//黒着手
parseItemSub['B'] = function(str, item){
item.stone = new SGFStone(BLACK, str);
}
//白着手
parseItemSub['W'] = function(str, item){
item.stone = new SGFStone(WHITE, str);
}
return {
//SGFファイル全体のパース
parse: function(content, element){
//改行は邪魔なので全部取っ払う
content = content.replace(/[\n\r]/g,"");
var nodeStack = []; //スタック
var prevNode = null;
var startIdx = -1;
//ノードに分割
for(var i=0; i<content.length; i++){
if(content[i] == "("){
//ノード開始
//いま見ているノードをpushして次のノードを作成
if(startIdx != -1){
var node = new SGFNode(content.slice(startIdx, i));
if(nodeStack.length == 0){
//ルートノードを設定
element.root = node;
} else {
//最後のノードの子ノードとしてpush
nodeStack[nodeStack.length-1].childNodes.push(node);
}
nodeStack.push(node);
}
//ノードの文字列開始
startIdx = i + 1;
}else if(content[i] == ")"){
//ノード終了
//いま見ているノードがあれば新規作成
if(startIdx != -1){
var node = new SGFNode(content.slice(startIdx, i));
if(nodeStack.length == 0){
//ルートノードを設定
element.root = node;
} else {
//最後のノードの子ノードとしてpush
nodeStack[nodeStack.length-1].childNodes.push(node);
}
} else {
//見ているノードがない、すなわち終了カッコが連続した場合は、スタックを1つpop
nodeStack.pop();
}
startIdx = -1;
}
}
},
//ノードのパース
parseNode: function(text, node){
//アイテムに分割
var startIdx = -1;
for(var i=0; i<text.length; i++){
if(text[i] == ";"){
if(startIdx != -1){
var item = new SGFItem(text.slice(startIdx+1, i));
node.items.push(item);
}
startIdx = i;
}
}
if(startIdx != -1){
var item = new SGFItem(text.slice(startIdx+1));
node.items.push(item);
}
},
//アイテムのパース
parseItem: function(text, item){
var id = "";
var str = "";
var startKakko = -1;
var startId = -1;
for(var i=0; i<text.length; i++){
if(text[i] == "["){
startKakko = i;
if(startId != -1){
id = text.slice(startId, i);
}
startId = -1;
}else if(text[i] == "]"){
str = text.slice(startKakko+1, i);
startKakko = -1;
//parseItemSubに処理があればそれを実行
if(id in parseItemSub){
parseItemSub[id](str, item);
}
}
else{
if(startKakko == -1 && startId == -1){
startId = i;
}
}
}
}
}
})();

PHPイミフ仕様3 __get(), __set()

PHPはクラスに存在しないプロパティをセットすることができる。これだけでもかなりフリーダムすぎる仕様だと思う。
その上、クラスに存在しないプロパティが呼び出されたとき専用のメソッドを定義することができる。それが、__get()と__set()である。

<?php
class Thing
{
public $a = 0;
}
$buttai = new Thing;
$buttai->b = 1; //問題なし
?>

__get()と__set()は、例えばDBからでっかいデータをゲットする処理なんかは重いから、アクセスされたときはじめてDBアクセスするような場合、なんかに使うのだそうだ。

<?php
class Person
{
public function __get($property)
{
if ($property === 'biography') {
$biography = "なんだか長い説明文123456789"; // DBからデータをゲットするなど
return $biography;
}
}
public function __set($property, $value)
{
if ($property === 'biography') {
// DBにデータをしまい込むなど
}
}
}
?>

jQuery, getterとsetterのヘンテコ動作

Programming PHPと並行してjQueryのオンラインマニュアルを読んでいる。

今日とても変わっている実装だと思ったのはこちら。getterとsetterについての解説だ。

$( "h1" ).html( "hello world" );

これはH1タグ全部を”hello world”に書き換える。

ところが、getterはセレクタで指定される先頭の要素の値だけしか返ってこない。

$( "h1" ).html();

これは先頭のH1タグの中身だけが返ってくる。setterのことを考えると全要素の中身が返ってきそうだが、全部ではない。1つだけ。

さらに、setterはjQueryオブジェクトを必ず返すが、getterはjQueryオブジェクトを返すとは限らない

上の例なら、setterはメソッドチェーンを使ってさらにH1タグ全体に対して何か処理を加えることができるが、getterでは”hello world”などの文字列が返ってくるので追加の処理は行えない。

C言語からプログラミングを始めた身としては、まずgetterとsetterの書き方がまるでオーバーロード関数のようである、というのは直感的ではない。さらに、返ってくるオブジェクトが異なるというのも直感的ではない。

jQueryを使いこなしている人から見れば当たり前のことだろうが、見た目がほとんど同じなのに動作が全然違うというのは躓きやすそうなのでメモしておく。

これはなんでもありのJavaScriptだから許される仕様だ。ライブラリは何を返そうがどこでどんな変数の置き換えをしても許される。グローバル空間の汚染すら許される。そのため、ユーザーはライブラリについて一々マニュアルを読みまくる必要性が必ず生じる。仕様を知らなければ何もできない。しかも仕様は変わる。技術はすぐに移り変わる。1つ覚えたからといって未来永劫その知識を活用できる保証が全くない。大変な世の中になったものだ。

 

なお、メソッドチェーンならこんなこともできる。

$( "#content" )
    .find( "h3" )
    .eq( 2 )
        .html( "new text for the third h3!" )
        .end() // Restores the selection to all h3s in #content
    .eq( 0 )
        .html( "new text for the first h3!" );

ちょっと気持ち悪い。私ならend()は使わないで2行に分割して書くだろうな。


PHPイミフ仕様2 連想配列に順序がある

<?php
$kencho = array(
'神奈川県' => "横浜市",
'埼玉県' => "さいたま市",
'千葉県' => "千葉市",
'茨城県' => "水戸市",
'静岡県' => "静岡市"
'山梨県' => "甲府市"
);
$removed = array_splice($kencho, 2, 2); // 千葉、茨城が消える
$tokyo = array('東京都' => "新宿区");
array_splice($kencho, 1, 0, $tokyo); // 神奈川と埼玉の間に東京が入る
?>

連想配列の順序が保障されている。こんなことはPHPだけで許される。例えばpythonの連想配列に順序はない。printで配列の中身を見ると、参照するたびに順番が変わる。javascirptでもそう。for-in構文を使うと順番が環境や状況によって変わる。

PHPは通常の配列も数値インデックスを持つ連想配列と同様に扱われる。例えば数値と連想配列の混合が可能なことからもわかる。

<?php
$kencho = array(
'神奈川県' => "横浜市",
'埼玉県' => "さいたま市",
'千葉県' => "千葉市",
);
var_dump($kencho);
$suuji = array(14, 11, 12);
var_dump($suuji);
$merge = array_merge($kencho, $suuji);
var_dump($merge);
?>

実行結果

array (size=6)
‘神奈川県’ => string ‘横浜市’ (length=9)
‘埼玉県’ => string ‘さいたま市’ (length=15)
‘千葉県’ => string ‘千葉市’ (length=9)

array (size=3)
0 => int 14
1 => int 11
2 => int 12

array (size=9)
‘神奈川県’ => string ‘横浜市’ (length=9)
‘埼玉県’ => string ‘さいたま市’ (length=15)
‘千葉県’ => string ‘千葉市’ (length=9)
0 => int 14
1 => int 11
2 => int 12

PHP: 配列 – Manual
マニュアルによると、「PHP の配列は、実際には順番付けられたマップです。」と書いてある。順番があるということは、PHPの配列は内部では次のように処理されているということらしい。

インデックス:0 キー:神奈川県 値:横浜市
インデックス:1 キー:埼玉県 値:さいたま市
インデックス:2 キー:千葉県 値:千葉市
インデックス:3 キー:0 値:14
インデックス:4 キー:1 値:11
インデックス:5 キー:2 値:12

順序があることでやりやすくなる処理もあるかもしれない。が、基本的に連想配列は順序が無いものとして扱った方が無難だろう。大多数の言語ではそうではないのだから。


PHPイミフ仕様1 strpos

次のコードは動作しない。

<?php
$str = "ゆっくりしていってね";
$pos = strpos($str, "ゆっくり");
if($pos != false)
{
echo "ゆっくりが見つかった";
}
?>

なぜなら、上のコードならstrposは0を返すので、0とfalseは==や!=のような演算子なら等しいものとして扱われてしまう。さらに厳格な===や!==を使わなければならない。

<?php
$str = "ゆっくりしていってね";
$pos = strpos($str, "ゆっくり");
if($pos !== false)
{
echo "ゆっくりが見つかった";
}
?>

これなら動作する。なぜ見つからなかったら-1を返す仕様にしなかったんだろう。。


VBA:それは全角と半角、大文字小文字を区別しないアバウトな言語

仕事でAccessVBAを使っていて1点ハマったことがあった。CSVファイルのカンマ区切りの指定したカラムの文字列を取り出す、という私ではない誰かが作った関数に欠陥があるので、その調査をしていた。

'※記述は変えてあります
Public Function mid_between_comma(strin As String, strout As String, J As Integer) As Integer
Dim i As Integer, AAA As Integer, cnt As Integer
Dim cnt_X(2) As Integer, cnt_A(2) As Integer
Dim strtemp As String
cnt = 0
For i = 1 To Len(strin)
strtemp = Mid(strin, i, 1)
If strtemp = "," Then
cnt = cnt + 1
If cnt = J Then
cnt_X(1) = cnt
cnt_A(1) = i
End If
If cnt = J + 1 Then
cnt_X(2) = cnt
cnt_A(2) = i
End If
End If
Next
'以下略

これだけ見ると動作しそうだ。しかし、次のような行を読ませるとまともに動作しない(内容は変えてあります)。

5,55,5555,55,5555,5555,5555555555,5555,555555,”ジョセフ・ジョースター,きさま!,見ているなッ!”,55,555,5,5555,5,5,5555,5,5,5,5,5,5,5,”5,55,55,5555

『,”ジョセフ・ジョースター,きさま!,見ているなッ!”』このカラムを丸ごと取り出したいのに、全角カンマが何故かセパレーターと認識され、例えば『”ジョセフ・ジョースター』だけが取り出されてしまう。理由がわからないので上記のコードをデバッグしていた。すると

If strtemp = "," Then

なんとここ、strtempに全角カンマが入っていても、Trueとなった!!あほか!!!!
カンマだけなのか?とデバッガ上で次のように試してみた。

"S" = "S" 'True
"0" = "0" 'True
"a" = "A" 'True
"0B1" = "0b1" 'True

おいマジかよ!アンビリーバボー!ありがた迷惑!
というわけで、VBAを扱う方は注意!彼のイコール演算子は、全角半角、大文字小文字を区別してくれない。丹精込めて作成したコードが思わぬ誤動作をしているならば、このクソみたいな仕様が原因かもしれない。。なお、VB.NETでは、上記の比較は全てFalseになる。それがまともな判断だと思う。
それでもExcelやAccessで前時代的なVBAを使わなければいけない貴方は次のメソッドを使いましょう。

StrComp(",", ",", vbBinaryCompare) 'False

PHPフリーダムその4、可変変数による関数コール

$変数名()
で変数名と同じ名前の関数をコールできる。

<?php
switch ($which) {
case 'first':
first();
break;
case 'second':
second();
break;
case 'third':
third();
break;
?>

これは次のコードと等しい。

<?php
$which();
?>

何でもありだな。$whichの名前をもつ関数がなければ当然ランタイムエラーなので、次のようにしてエラーを予防できる。

<?php
if (function_exists($which)) {
$which();
}
?>

PHPのゆるーい型付け、タイプヒンティング

PHP5で導入されたタイプヒンティング。関数の引数に型情報を書くことができる。

<? php
class Entertainment {}
class Clown extends Entertainment {}
class Job {}
function handleEntertainment(Entertainment $a, callable $callback = NULL)
{
echo "Handling " . get_class($a) . " fun\n";
if ($callback !== NULL) {
$callback();
}
}
$callback = function()
{
// なんかする
};
handleEntertainment(new Clown); // 動作する
handleEntertainment(new Job, $callback); // ランタイムエラー
?>

関数に型を付けることで、引数として与えるオブジェクトの種類を強制することができる。ただしスクリプト言語のサガで、実行時にしかエラーを検出できないのが欠点だ。TypeScriptみたいにコンパイルしないからしゃーないよねー。
更にもう一つ欠点がある。なんとスカラー型は使用できない。つまりint, stringを型情報として書くことはできない、、ってどこまで片手落ちなんじゃ!
もちろん型情報を省略することもできるわけで、PHPの型付け力は非常にゆるいものなのでした。


詰碁印刷のためのプログラム

詰碁を印刷できるプログラムが必要になった。

日本中で売られている詰碁集には限りがあり、買い占めていけばいずれ全部解き終わってしまう日がやってくる。

インターネット上、特に中国には大量のsgfファイルがあり、これを扱えるようになれば10年は詰碁に不自由しないだろう。

都合上webやPC上ではなく、印刷して使用したい。しかしsgfを扱うソフトは印刷機能が無かったり、あっても期待するような機能ではない。MultiGoの印刷機能はそれなりではあるものの、碁盤全体が必ず表示されてしまい紙が勿体ない。

またも、自分で作るしかないようだ。

HTML5+canvas+javascriptを使うのが一番手っ取り早いだろう。以前File APIの使い方を覚えた所なので、zipで圧縮したSGFファイルを一気に読み込んで問題集を作る、という形式にするのがベターか。これは完成したら公開できそうだ。