PHP関西勉強会でYii2-alphaを試しました
「えー、会場の時間の関係でこの後の人は発表時間2分でお願いします」
という消化不良だったので、Yii2を試した感想を書きます。
http://www.yiiframework.com/news/76/yii-2-0-alpha-is-released/
12月のアタマで、Yii2がようやくアルファ版になりました。パブリックプレビューからずいぶん経ちましたね。あと残るはNoSQLのActiveRecordを作っていろいろ仕上げに入るということで、待ち遠しいきょうこの頃、PHP勉強会で「やり残したことをもくもくしよう」というわけで、どこまで進んだのかをじっさいに見てみました。
まず、プロジェクト作成が Composer で簡単にできるようになっていました。
$ php composer.phar create-project --stability=dev yiisoft/yii2-app-basic testapp
単純な構成のアプリケーションはこれ一発で初期化できます。testapp
というプロジェクトフォルダができて、要るものが全部入っています。設定などの基本構成もほとんど済んでいます。--stability=dev
は安定版がリリース されればなくなるでしょう。
で、いきなりPHPのビルトインサーバで動かすと...
$ php -S localhost:8081 -t web
いきなりBootstrapじゃん! しかも3がベース。
こんなかっこいい画面が出来上がった状態で始まります。最初から問い合わせフォームとログインフォームが付いているのも健在です。
Yii-Bootstrap チュートリアル - tips - ドキュメント - yiijan.org | Yii日本ユーザグループ
ここもう要らないですね。せっせとBootstrapを入れるためにがんばっていたアレをしなくて済むようになるのは大助かりです。
デフォルトのプロジェクト構造も変わりました。
├── assets ├── commands ├── config ├── controllers ├── models ├── runtime ├── tests ├── vendor ├── views └── web
protected
の中身が出てきた感じです。通常の公開フォルダ web
がコードの中にいます。もちろんこれは基本形というだけで、yii2-app-basic
以外にも advanced
みたいなマルチサイト向けの構成もあります。で、このプロジェクトのルートが名前空間の \app
になります。ビューも静的ファイルも上に名前空間のルートがある感じなので、アプリケーションが綺麗にパッケージになってる感じがありますね。
注目すべきは画面の下の方にあるデバッグバーです。Symfonyにあるアレです。リクエストごとの状態やログを見られる機能。面白いのはPHPのバージョン番号が書いてあるところ。
クリックするとアレがそのまんま!!
phpinfo(); exit;
だけやるアクションをいちいち書かなくてもいいんですよ。わかってらっしゃる。
Giiも乗りました。GiiはWebブラウザでコード生成できるツールです。
モデルとCRUDを作ってみます。モデルはこれまでと全く同じ感じで作れました。CRUDは名前空間をフルに指定する必要がありました。リリースまでにもうちょっと補完が効くようになるんだろうな。
レイアウトは main.php
一択になり、2カラムレイアウト用のアクションのメニューはない感じになっています。Yiiの1のときの2カラムレイアウトとアクションメニューって、レスポンシブでモバイルファーストにするとき標準とするにはちょっと邪魔で、自分は外して使っていたんですが、それが基本になったということで、個人的には嬉しい仕様です。
そうそうレスポンシブといえば、Bootstrap3ベースなので何もしなくてもこのぐらいできてます。本当に自動で作ってまだ何もしてないサイトですよ。
ログインの認証のカスタマイズもわかりやすくなっていました。Yii 1 ではこういうコンポーネントを実装しました。
<?php class UserIdentity extends CUserIdentity { public function authenticate() { // ここを書き換えてDBアクセスによる認証など自由に書く } }
Yii 2 はこうです。
<?php namespace app\models; use yii\web\IdentityInterface; class User extends Model implements IdentityInterface { // IdentityInterface を実装する }
こういう形の User という名前の モデル が models
に置いてあります。それをこう。
<?php namespace app\models; use yii\db\ActiveRecord; use yii\web\IdentityInterface; class User extends ActiveRecord implements IdentityInterface { public static function tableName() { return 'user'; } public static function findIdentity($id) { return self::find($id); } public static function findByUsername($username) { return self::find()->where( 'username=:name', [ ':name'=>$username ] )->one(); } // あとは初期実装のまま }
Model を ActiveRecord にして、必須であるテーブル名とその検索方法を書いたぐらい。
これまでは DB 上でアカウント情報を表すARなモデルを作って、それを CUserIdentity が使うという形で、まあそれでも何でもできたんですが、Yii 2 ではもっと簡単に、モデルとして見えるところにユーザが最初から定義されていて、それをARにするならどうぞ、というわかりやすい感じになっています。
で、ここでもうひとつ驚きがありますね。->model()
がなくてクラス自体がリポジトリになってますね。Rails感が増しました。また、findByAttributes()
と find()
+ CDbCriteria
のどっち使うかがなくなり、引数なしの find()
の戻り値がクエリビルダになるからみんな使え、という仕様になりました。
対比してわかりやすいよう Yii1 のコード例を3つ:
User::model()->findAllByAttributes(array( 'username'=>$username, ));
User::model()->find('username=:name', array( ':name' => $username, ));
$criteria = new CDbCriteria(); $criteria->addCondition('username=:name'); $criteria->params = array( ':name' => $username, ); User::model()->find($criteria);
どれ使えと... というのがひとつの様式に統合された感じです。
で、どの形でフェッチするのか、つまり、ARとしてone
か、ARの配列としてall
か、単一の値をscalar
か、というのを普通に書くというのも、例には出してないですが、CDbCommandの方法との一貫性につながっています。
まああと、PHP5.4以上なので普通にショートアレイが使われていますね。コンパクトでいい感じです。
コントローラまわりの改善。不自然さはなくなっていて、依存関係的に正しい感じになっていると感じました。Yii1は、アクションのアクセス制限の仕組みの根本は付け替え可能なフィルタ機能なのに、なぜかコントローラの抽象にaccessRulesなんてメソッドが最初からある、なんていう、現実的だけどちょっと変な進化をしてきた痕跡がありましたが、そこらへんたぶんだいぶ整理されてます。
ビューも、ビューファイルでいう $this
が実はコントローラのことだった、なんてことはなく、yii\web\View
のインスタンスになっています。これまで可能だった、ビューヘルパーとしてコントローラにメソッドを追加、なんていう変な作り方はできなくなっています。
あと、これ大きいなと思うのが、エンティティとフォームを兼用しているあのARモデルですが、フォームっぽさがCRUD用途のみという感じになるように、Giiが生成するようになったことです。検索フォームなどDBの列とは別のプロパティが必要な種類のフォームは、エンティティのCRUDをやるためのモデルから外れて、検索だけやるフォームとしてARではない単独のフォームモデルに分離されるようになりました。
これ教育上とてもいいことだと思います。追加機能のためのフィールドをどんどんひとつのモデルに付け足して混在させていく作りって、モデル駆動の人にしてみたら、中心とするモデル実装の汚染なんですよね。でもプログラマーは、お手本コードに search()
なんていう特殊なものを同じところに書いてあるなら、それ真似するでしょ。結果、どんどんひとつのモデルにフィールドとメソッドが増え、太っていきます。ファットモデル。Yii2の初期コードは、それをしないお手本になっている気がしました。(それでいてフォームバリデーションを最低限必要なぶんだけ同じ所に持っているモデルというのは、素早いプロトタイピングをやるのにすごく便利です)
Yii経験者じゃないと何のことかわからないと思いますが、とにかくYii2はよくできています。速さと便利さのためにいくらか犠牲になっていた、正しい方法への誘導というフレームワークの意義を、可能な限り取り戻そうという意図が感じられました。数年後、PHP界の正しいRailsとして選択の余地なしという評価をもらえるものになっていると思います。たぶんね。
PHPでは配列ではなくオブジェクトに状態を持たせよ
アドベントカレンダーを書いたらコメントに面白い課題もらいました。
「Python - すごく簡単なアルゴリズムがphpで書けなくてつらい」のやつ。
php の array と参照の関係がクソで無いなら、 http://qiita.com/methane/items/41e1376c41d8c15e8894 これを普通に書いてみてください。
面白そう。やりましょう。
最近ずいぶんPHP成分多めですが、実はPythonも好物なのでホクホクです。
といっても、あのエントリーは「php の array と参照の関係がクソで無い」とは言ってなくて、むしろ逆にそこは腐ってるから避けろ、オブジェクトで囲んでやれ、という話だったので...(^^ そのままやってもPythonの性能にはならないとわかっているので、配列を直接使うのはイヤです。なので、オブジェクトが状態を持つように書けという主張がどういうことなのか、実装で表してみたいと思います。
あ、5.5 の yield 使ってます。せっかく PHP にも Python のあれができたので。 yield なんてズルいと思う人はそういうイテレータークラスを実装したらいいと思います。イテレーターの実装が面倒な人は Ginq とか検討してもいいかもです。
<?php /** * 末尾から消化できるシーケンス */ class TailConsumableSequence { protected $sequence; function __construct($source) { $this->sequence = $source; } /** * Pythonのpopと同じようなもの * 末尾からゆらぎぶんさかのぼって抜き出す */ function consume($fluctuation=0) { $size = count($this->sequence); $targetPos = $size - 1 - $fluctuation; $consumedValue = $this->sequence[$targetPos]; for ($i = $targetPos; $i < $size - 1; ++$i) { $this->sequence[$i] = $this->sequence[$i + 1]; } array_pop($this->sequence); return $consumedValue; } /** * n以上残っているか */ function remainsEqualsOrMoreThan($n) { return count($this->sequence) >= $n; } /** * 残り数かlimitかどちらか小さい方 */ function sizeOr($limit) { $size = count($this->sequence); return min($limit, $size); } } /** * 数列の後ろのほうからごにょごにょ拾ってきてペアを作る * ごにょごにょ = 最末尾とそこから limit までの範囲さかのぼった位置のペア */ function allRandomPeairsFrom($sequence, $limit=100) { $tcs = new TailConsumableSequence($sequence); while ($tcs->remainsEqualsOrMoreThan(2)) { $a = $tcs->consume(); // 最末尾から取る $b = $tcs->consume( mt_rand(0, $tcs->sizeOr($limit) - 1) ); // 最末尾からある程度さかのぼって取る yield [$a, $b]; } } function test($n, $r) { foreach (allRandomPeairsFrom(range(0, $n-1), $r) as list($a, $b)) { //printf("%d %d\n", $a, $b); assert($a - $b < $r * 2); } } test($argv[1], 10);
これでいかがでしょうか。追記3のベタ書きと同じような特性なのに、わりと読めるコードでもあります。
$ time php matching2.php 10000; time php matching2.php 20000 php matching2.php 10000 0.23s user 0.01s system 99% cpu 0.245 total php matching2.php 20000 0.40s user 0.02s system 98% cpu 0.421 total
追記2 で list_pop() にしていた処理をベタ書きしたら、やっと予想通りの性能が出るようになった。 php で配列の参照に対して操作してはいけない。つまり、配列を操作するアルゴリズムを関数にしてはいけない。
すごく同意。関数にしてはいけない。配列の参照に対して操作してはいけない。生で配列をごりごり書き換えるとポインタが使えないためにリファクタできなくなってハマる。じゃあどうするか...
PHPで状態を持つバッファは、オブジェクトで囲むといいです。参照渡しとか配列のコピーとかから来るややこしさから解放され、ベタ書きしたのと同じような動きになります。オブジェクトはポインタなので、好きなだけ関数に渡してもよくて、もっといいのは、オブジェクトのフィールドはスコープを行き来するローカル変数より、もっと安定したメモリに置いてあるってことです。
あの「状態の変化は、それを意図したメソッドを持つオブジェクトでのみ起こるべきです。」というのは、お作法の話であるという側面もあるけど、他に、そう書いたほうが参照渡しよりも問題が簡単になって、がんばらなくてもパフォーマンスが安定する、という意味でもあるんです。
まわりくどくて重厚に見える方法のほうが実は素直、というのが最近のPHPの面白いところです。OOPのお作法がよくなるのと、物理的に悪いものから遠ざかるのが反比例じゃない感じ。
PHPが糞言語なのはどう考えても参照をポインタだと思っているお前らが悪い
この投稿はPHP Advent Calendar 2013の12日目の記事です。
PHP恒例行事の参照と三項演算子のdisりですが、そろそろあさってな議論はやめませんかという話です。
今年のPHP-dis大賞といえばこちら。
※ 追記: これ書かれたのは2012年でしたすんません。
なんで君たちそんなコードが必要なのかね、と。結論から先言うと、きみたちがPHPが使えないって思うのは、そんな挙動に左右されるようなコードを書くからでしょ、だからCとかRubyとかそういう簡単な言語でわかった気になっている初心者はまったくもう...というわけでPHPの言語文法の基礎んとこ、いきますね。
まず、PHPのarrayは「値」です。もちろん文字列も「値」です。値は値なんだけど、それはミュータブルです。PHPのarrayもしくは文字列の代入は、一見すると、ポインタを使わない大きなC構造体を代入するような感じになります。
function x2first($arr) { $arr[0] *= 2; return $arr; } $input = array(1, 2, 3, 4, 5); $output = x2first($input); // PHPの配列渡しはポインタではない assert($input[0] != $output[0]);
PHPでは、コール先で中身を変更しても、コール元のスコープでは変数の値が維持されています。
で、ここでポインタわかって調子乗っちゃってるプログラマーは誤解するのですよ。大きな配列や文字列を別の変数に代入したり、関数に渡したりすると、その回数ぶん メモリの確保とバイナリのコピーが起こっている と。
その勘違いに捕らわれた人は、C++の参照演算子(ポインタを逆に表現した感じのアレ)を思い出して、「そうだPHPでも参照を使えばポインタと同じだ」と思い込んで、まあこんなコードを試すわけです。
function x2first_ref(&$arr)
{
$arr[0] *= 2;
}
$input = array(1, 2, 3, 4, 5);
x2first_ref($input);
// 引数がコピーだということは、ポインタ相当のパフォーマンスを得るには参照渡しかな
assert($input[0] == 2);
やったーこれでメモリのコピー量がスタックに8バイト積むだけで済むぜ。
んなわけねーよ。
まあ、それほど極端でなかったとしても、みなさん計測せずに感情的な判断をしていませんか? パフォーマンスのために書いたつもりが、計測してないなんて、そんなの一度も実行してないのにバグってないロジックができたと言い張ってるようなものですよ。
ちゃんと計測します。
function profile($funcname) { $bigarray = range(1, 1000000); echo $funcname . "\n"; if ($funcname) { $start = microtime(true); $ret = $funcname($bigarray, 500000); $end = microtime(true); } else { $start = microtime(true); $end = microtime(true); } echo " caller memory: " . number_format(memory_get_usage()) . "\n"; echo " time: " . (($end - $start) * 1000) . "(ms)\n"; unset($bigarray); unset($ret); } ob_start(); profile(null); // ウォームアップ ob_end_clean();
誰でも試せる素朴な計測ツールです。zval_とかわからなくても大丈夫な感じのやつ。100万要素の配列を準備して、処理にかかった時間と、呼び出し元スコープでの使用メモリを報告します。
まずは、本当に何もしない場合の数値を基準として取っておきましょうか。
profile(null); // 関数を呼ばない場合 // caller memory: 144,639,024 // time: 0.0030994415283203(ms)
実行するときは、たぶん php.ini のメモリ上限にひっかかるので、上限外してやりましょう。
$ php -d memory_limit=-1 test.php
さてじゃあ、引数渡しと戻り値の返却を、参照ありなしいろいろ試してみましょう。
// 参照なし function nop_noref($arr) { echo " callee memory: " . number_format(memory_get_usage()) . "\n"; return $arr; } // 参照渡しのみ function nop_ref_arg(&$arr) { echo " callee memory: " . number_format(memory_get_usage()) . "\n"; return $arr; } // 参照渡し+参照返し function &nop_ref_both(&$arr) { echo " callee memory: " . number_format(memory_get_usage()) . "\n"; return $arr; } profile('nop_noref'); profile('nop_ref_arg'); profile('nop_ref_both'); // nop_noref // callee memory: 144,639,072 // caller memory: 144,639,072 // time: 0.028133392333984(ms) // nop_ref_arg // callee memory: 144,639,128 // caller memory: 241,027,888 ← コール後にメモリ増大 // time: 106.05406761169(ms) ← なんだこれは // nop_ref_both // callee memory: 144,639,112 // caller memory: 144,639,112 // time: 0.034093856811523(ms)
おい参照渡し、参照しない場合に対して処理時間が3700倍とかどないなっとんじゃい。
参照渡しのみした場合、見事にメモリが倍になっています。つまり巨大配列のコピーが発生した証拠です。実はこれ、参照返しをしなければ、戻り値の受け取りのとき、配列のコピーが生まれているのです。参照渡しだけでなく参照返しも意識しないと、こんなふうに内容が同じ100万要素の配列が2つできちゃう。こいつは厄介...
いや、厄介じゃないですね。驚くべきことに、いっさい参照を意識していないコールが最も優秀になっていますね。安全だし速いし。パフォーマンスのために内容破壊のリスクを冒して参照渡しするべき、とか考えてた人は残念でした。その努力には、まったく意味がありません。
いちど構造が作られてしまったら、多くの場合は読み取りアクセスだけで事足ります。読み取りに関しては、参照渡しだろうとそうでなかろうと、その負荷はまったく同じです。(7マイクロ秒の差は計測誤差です)
function getat_noref($arr, $index)
{
echo " callee memory: " . number_format(memory_get_usage()) . "\n";
return $arr[$index];
}
function getat_ref(&$arr, $index)
{
echo " callee memory: " . number_format(memory_get_usage()) . "\n";
return $arr[$index];
}
profile('getat_noref');
profile('getat_ref');
// getat_noref
// callee memory: 144,639,168
// caller memory: 144,639,168
// time: 0.03814697265625(ms)
// getat_ref
// callee memory: 144,639,232
// caller memory: 144,639,232
// time: 0.030994415283203(ms)
参照にパフォーマンス上の意味はない、つまり、参照記号の &
は、コール元に魔の手を延ばして値を書き換えてやるぞ〜、と待ち構えている怖いヤツの目印だというだけなんですよ。
もういちどポインタを思い出して。そう、PHPの変数は、最初からすべてポインタなのです。だから特別な記号を使わなくても、いくらでも変数を関数に引き渡せるのです。いやそうとしか説明できないでしょ、この結果見たら。
PHPで、すごく層の厚いフレームワークが案外実用的な速度で動く理由は、実はこのへんが効いているんですね。リクエストごとにやり直しでありながらも、言語ランタイムで代入が自動的にすべてポインタなので、層の間で無駄なコピーが発生していない。だからこそ、大きなarrayをコンフィグとして取り回したり、設計をあれだけ高級化しても案外ダメージが少ない。
でもまって、中で変更しても外は保護されてたアレはどう考えてもコピーでしょ。----そうですね、アレを試さなきゃ。
function x2at_noref($arr, $index) { echo " callee memory1: " . number_format(memory_get_usage()) . "\n"; $arr[$index] *= 2; echo " callee memory2: " . number_format(memory_get_usage()) . "\n"; return $arr; } function &x2at_ref(&$arr, $index) { echo " callee memory1: " . number_format(memory_get_usage()) . "\n"; $arr[$index] *= 2; echo " callee memory2: " . number_format(memory_get_usage()) . "\n"; return $arr; } profile('x2at_noref'); profile('x2at_ref'); // x2at_noref // callee memory1: 144,639,360 // callee memory2: 241,028,168 ← ここでメモリを大幅に確保 // caller memory: 241,028,168 // time: 111.29093170166(ms) ← コピーコスト // x2at_ref // callee memory1: 144,639,376 // callee memory2: 144,639,376 // caller memory: 144,639,376 // time: 0.036954879760742(ms)
最初の nop_noref()
に対して、この x2at_noref()
がちょうど、中身を操作するかしないかの違いになっています。中身を操作しなければポインタのままだったものが、操作したことによって実体を共有できなくなると、こんどは裏で勝手にクローンが作られる、これがPHPの「普通の変数」の正体です。なんという高級言語。わざわざそういう最適化を書かなくても、言語ランタイムが暗黙的にやってくれてるんですよ。
こう見ると、参照のほうがむしろ普通に見えてきます。ただまあ、PHPの参照はいちど変数が参照になってしまうと、二度ともとに戻ることができないので、扱いにくくてやっぱりダメです。我々アプリケーションプログラマーにとっては、より平易で少ないコード量で、より安全で最適化された処理を得るのが正義なんですから。
zval の is_ref がどうとかあたりのちゃんとした説明は、2013-03-07 - bravewood の日記 で読めます。中身にこだわる方はどうぞ。参照代入演算子は、右辺の変数の特性をこっそり変化させてるあたりで、「うわなにこれ演算子の見た目に騙されてた」感を堪能できますよ。
まあ、そもそも話で、LLな言語の変数がミュータブルなのはしょうがないですが、であるからこそ、別のスコープではできるだけイミュータブルな値であるように意識して扱うのが、うまいプログラムのお作法ですよね。どこで中身が書き換えられるかわからない、副作用を期待した作りではなく、生成は生成に関わる箇所だけで、読み取りは参照透過で途中で言ってること変わらない、となっているのが、言語を問わず良いとされる方法です。
状態の変化は、それを意図したメソッドを持つオブジェクトでのみ起こるべきです。コードの中にできるだけ状態を作らない。わかりやすいインターフェースのオブジェクト設計で、これは状態だ、他は状態じゃないと静的に判別できるように考えましょう。今のPHPのオブジェクトインスタンスは、Javaのそれと同じく、明示的なクローンをするまで実体はひとつです。arrayを直接ではなく、可変な変数を持つオブジェクトを用意して、オブジェクトを普通に関数に渡し(オブジェクトは素直にポインタです)、そのオブジェクトのメソッドを使って変化を管理しましょう。
というわけで、参照渡しをカジュアルにやるのが間違いなのです。関数の戻り値の型の整合性がとれず、やむなく出力引数で表さなければならない場合などを除いて、基本的には使わない。使う意味がない。参照の仕様から来る複雑さに関しては、PHPが悪いというより、基礎を押さえずに用途を勘違いして使うほうが悪いと思います。PHPの変数の基礎を知っていれば、ほとんどの場合使わなくていいということが、おのずとわかると思います。
PHPが変な言語に見えるのは、そういう特殊な高級言語だと知らずに、素朴なメモリモデルを持つCのようなもので例えようとするのが良くないのです。もちろん、本当のビギナーが誤解して使っている場合も多いですが、よりややこしいのは、少々わかったふうな若葉マーク取れたぐらいの人が起こす勘違いです。そこに門外漢が弱いものいじめのように集まってきてしまう。本当のPHPプログラマーは、これがどういう言語なのかをよく知って、つつましく適切に、でも都合のいいところを活かして便利に使うのです。
まったく役に立たないかに見える参照の機能ですが、僕は、ここだけは使えるというホットスポットがあると思ってて、あとはそれを紹介しておきますね。
$str = '123-456'; preg_match('/^(.*?)-(.*?)/', $str, $match);
preg_match()
の第三引数は参照渡しです。このコールで、$match
は宣言されている必要がありません。このように、コール元のスコープの変数の代入式と同じように働く参照渡しは、時として役に立ちます。
あと、クロージャが束縛する変数。
$removed = array(); $data = array_filter($data, function($element) use(&$removed) { if ($element->ckeck()) { return true; } else { $removed[] = $element; return false; } });
$removed
の参照を束縛しています。ここがもし参照でなかったら、まさに引数に配列を渡したときのように、クローンに対して操作され、$removed=array();
が維持されてしまいます。本来はオブジェクトを設けるべきなのかもしれませんが、クロージャがインラインで複雑さをさっと閉じ込めてくれることを思うと、こういう場合は、素朴なforeachループルーチンと置換えられるような手軽さが合っています。
個人的には、参照がありがたいと思って使うのはこれぐらいです。他に使い道ってあまりないなぁ。
あ、そうそう、参照を使ったら、忘れずに unset()
するか、すぐにそのスコープを抜けること。変数がいちど参照になると、同じ名前でその変数名を再利用することができないのです。以後そこに代入するのは、参照先の領域への書き込みという意味になってしまい、新たな値を持つ変数を作るという意味にはなりません。
えーと、PHPが糞言語なのはどう考えても参照をポインタだと思っているお前らが悪いって言ってごめんなさいなので、歌って気分を晴らしてください
"仕様が理解らないの〜 なぜだどうしてだ〜 アホかー"
参考: http://www.youtube.com/watch?v=fZ-CM7n3F5c
明日は @ockeghem こと徳丸先生です。楽しみですね。
書籍感想: PHP逆引きレシピ 第2版
出版社から頂いたので読ませて頂きました。ありがとうございます。
PHP逆引きレシピ 第2版
PHP逆引きレシピ 第2版 (PROGRAMMER’S RECiPE)
2800円なんですが、この価格に対して内容量おかしいぐらい豊富です。
体裁としては、まだ経験値が少ない初心者向けという雰囲気ですが、実際の動作に影響する点については、驚くほど正確な自己ツッコミが入っていて、実はすごい知識に支えられているんだというのがわかります。重箱の隅をつつけばつつくほど、勉強になる本だと感じました。
おそらく初版で意識していたPHP4〜5.2への配慮を、きっちりと、今の世代では切り捨てるべきものは今のやり方に置き換えてあるのは、素晴らしいです。これだけの書き換えをやって、全ての内容を検証してあるんだとしたら、本当に頭が下がります。
加筆された部分は、セキュリティ対策に関する解説が期待をはるかに上回っていました。PHP4から5の初期の時代に初版が出版された本であるという歴史を引きずっているため、たとえばURLパラメータにセッションIDを付けた場合や、あの magic_quote_gpc
にも言及していながら、それでいて、ちゃんと理屈をふまえたセキュリティリスクの総まとめになっていました。
他のセクションが一話完結なのに対して、セキュリティについては、まず理解するうえで絶対外せない説明があって、それから。php.ini の網羅まで進む。頭から通しで読んで内容が一貫しているという異例の構成となっています。たぶんそこだけで十分に買う価値があると思います。
一話完結なのでもったいないと思ったのは、自動テストでした。たぶん、前提としてオブジェクト指向設計を習得できているというのが必要なのに、対象読者を「クラスとかよくわかんない」な層と想定していて、単体テストがクラスに対応するところから始められない、といったジレンマはあったんだろうなあとは思いますが... であれば、セキュリティの章のように、PHPにおけるOOPも一貫して、ってできていたらよかっただろうな。そうしたら、PSR-0 からの Composer についてももっと馴染めたろうに、とは感じました。
とはいっても「目で見て確認します」「zipを展開します」ではなく、自動テストや自動的な依存解決へのアンテナを貼るのを、「var_dumpってなんですか」「XAMPPをインストールしましょう」という見出しを持った本で展開できるというのは、素晴らしいことです。ああ、それだけに、OO設計とフレームワーク概念が不足気味なのが本当に...
ごめんなさい、不満ついでにこれだけは、という点を3点ほど。
ひとつは、すべてのサンプルコードがWebサーバで動かすように書いてあること。ちゃんとコマンドラインで実行することも書いてあるし、PHPUnit も Composer もコマンドラインでしか動かないものを扱っているんだから、HTTPとは本質的な関係を持たないコードは、PHPインタプリタに食わせるスクリプトであっても良かったはずです。Webページでなければならないせいで、コードが冗長になってしまって読むべき場所がぼやけ、出力結果サンプルとの照合時に距離ができちゃってるんじゃないかなと思いました。
もうひとつはこれ:
require_once '../../hoge/fuga.php';
PHPプログラミングするひとは、お願いですから、__DIR__
をつけてください。初心者PHPer(あるいは初心者から成長していないプレーンPHPおじさん)には、なるべく早く、相対パス指定の癖を抜いて欲しいのです。カレントディレクトリは chdir()
で移動できます。自分のスクリプトではやっていなくても、require 先で起こっていてるかもしれません。カレントディレクトリは暗黙で扱いにくいグローバル変数なのです。
PHPをHTMLの埋め込みマクロとして使う、本当に簡易な使い方をする場合、「includeで別のディレクトリから共通ブロックを読み込み」とかやりますね。その場合、読み込まれた側(ヘッダなど)でライブラリ(ログインセッションの確認など)を相対パス require_once すると、「誰が自分を読み込んでいるかによって基準ディレクトリが変わる」ことになってしまい、これって「あらかじめ使う人を知っている部品」になってしまいます。
過去と未来、すべての __DIR__
なし require_once を生まれる前に消し去りたい。
require_once __DIR__ . '/../../hoge/fuga.php'; // もしくは require_once dirname(__FILE__) . '/../../hoge/fuga.php';
最後の不満は、ズバリ、PhpDocがないことです。紙面上でせっかくNetBeansをオススメしているのに、コードに型情報を書いてないと、IDEのパワーが半減しちゃいますよ。紙面上docコメント書くと狭いのはわかるんですが...
うーん、まあ、僕の不満点も、「どこからでも読める本」とするために、できるかぎり他のトピックの知識を必要としないように、という配慮の結果なんだろうけど。うーん。「まずは絶対にこれだけ押さえておかないと、この本は読めません」っていう導入があれば... いやそれだと買っただけで明日から使える逆引きとしては... もやもや。
だめですね、良し悪しじゃないですね。個人的にどうかというと、確実に「予想外にも読んでおいて良かった本」でした。それは大丈夫。2800円はお買い得です。不満なところは3つだけで、あとはもう、圧倒的なボリューム感でおなかいっぱいです。
たぶん、自分のもやもやは、かつてPHPerと呼ばれた層と、いま必要とされている技術に、外からは理解されていないギャップがあるのではないか、という点に行くのかなと思います。昔からやっていて、時代とともに成長している人はいいんだけど、これからやる人も、昔のまんまの人もいる、他の言語やっててPHP4以来のご無沙汰だから逆引き本を買っておこうという人もいる、その中心で全方位からの攻撃に備えよ、となると、やっぱり、PHP逆引きレシピの構成と厚みになるのかなーと思ったりしています。
むずかしいですね。でもまあ、そんなこと言ったらRubyの技術なんて明らかに紙が追いついてない感じだし、PHPもそろそろ「初心者にも簡単な言語」という解釈ではない、本当は難しいWebアプリケーション、という、いっちょまえのステータスをもっと評価される時代なのかもですね。
就職しました
退職エントリーがあるなら就職エントリーがあってもいいじゃないか。
というわけで10月からクックビズ株式会社に就職しました。
就職といっても、フルコミットより何割かゆるいコミットメントで、現在個人で扱っている案件をすべて切る必要はないという条件で、まずは半年単位の契約でやらせてもらうことになりました。長期的に確保されちゃうことはできないですが、機動力は衰えておりませんので皆様よろしくお願い致しますですよ。
クックビズは、飲食業界を専門とした人材サービスを行っている会社です。ITサービス/ソフトウェア開発そのものの会社ではないので、人の割合でいうと技術の人の割合は少なめです。
なんでまたそこに。
阪急梅田駅と御堂筋線梅田駅のすぐ近く。もちろんJR大阪駅も。ということは...グランフロントもヨドバシも眼と鼻の先です。阪急の高架を抜ければ茶屋町側に出るのもすぐなので、中津の某 1x1 勉強会にも歩いて行けます。ロケーションがいいですね。
で、この場所、事業内容のせいでそうそう移転する心配がないのです。というのも、コンサルタントがサービス利用者を呼んで面談をする必要があるので、カバー地域の中心に位置してることがマストなのです。
グランフロントのナレッジサロン使ってる人は来るとき教えてください。ゲストのふりしてランチにいきます。デザイナー職、エンジニア職の方は、ナレッジサロンから弊社見学に来てくださってもよろしくてよ。
ってだけで就職というとカッコ悪いので、言い訳も足しておきます。
ITの業界って、営業職/企画職といえば開発職の敵みたいなところがありますよね。クリエイティブ方面でも、代理店なりプロデューサーなりと実際のクリエイターの関係といえば、...ねぇ。彼ら営業職は、納品の瞬間がゴールなんですね。売る人たちって、平常運転に入ったらそのあとは、いかに動かないかが勝負、みないなモードになっちゃいます。
そういうビジネスのフローにいると、新規開発が終わった瞬間が一番儲かる、というのが当然みたいになっちゃって、いかに新規を継続的にリリースするかという仕事のやりかたになります。そうでないと赤になる、という考え方が当たり前だと思っちゃう世界(たぶんそれは錯覚)。難しいのは、これ、個人に近くなればなるほど、その傾向が強くなるんですよね。腕の立つ火消し連れて来い、予算は作業の実費ぶんだけある、って具合。
珍しく平常運転にバリュー(保守費ってだけじゃなくて顧客に対する企業価値も)が得られるって考え方の組織があっても、それは組織のもの、みたいなケースがほとんど。まあ、個人の価値を認めてくれるありがたいお客さんはいるので感謝はしてるんですが、そのお客さん自体が納品の対価で商売している以上、新しい技術課題のクリア以外に支払えるものとなると、やっぱりないんですよね。
そもそも売上が安定するはずがない業界ってのははじめから承知。なんだけど、そういう状況にいるとやっぱり気になるのは、システムやデザインの本当の価値ってのが、使い続けるところにある、変化し続けられるモデルを維持するっていう、核心のところなんですよ。
毎日安定して動くか、ユーザのモチベーションが下がらないか、なんかに関心を払うより、納品の瞬間が最高にピカピカしていて満足感が高いものを持ってきて売ってしまうほうが効率いい、みたいな商売の価値観に、少なからず影響される仕事しかしていなかった。なので、ユーザ会社の中に入ってしまうことで、逆に、そういう価値観の外で働く感覚を得るチャンスにできないかと思ったわけです。
ほんとぶっちゃけ、クックビズの営業職はIT製品をなにひとつ売っていないのですね。コンテンツもバナー領域もライセンスも何も売らない。扱っているものが人材と求人だけ。つまり対立ポイントがありえません。これがすごくいい。会社として営業力が高いというだけでも素晴らしいのに、しかもそれが衝突する相手でなく直交関係なんですよ。ここでは、社内の人が営業SEなんじゃなくて、リアルなエンドユーザーです。(普通ここ、自社の営業SEと相手会社の担当SEが挟まりますよね。) もしかしたら、自分でさえ、一部の機能に関してはユーザになりえます。
ちょっとITを直接商売にする場合に思いを馳せると、「リリース時のアウトプット100%の犠牲として保守性を犠牲に」という考え方、これ悪いことみたいだけど、請負であれば程度問題で正義でもあるんですよね。釈明が要らないモノは、担当営業SEの人的コストを下げるので。で、自分が入社する前の現行システム、まだ社内にWeb技術のセクションがなかった頃のコードが、まさにそんな感じでした。すごく頑張って機能を作っているのに、その見た目からは乖離した内部設計っていうアレです。
ああ、もし作る人が「保守開発と付き合う時間のほうが長い」という立場で作り始めていたら...
というわけで、社員として、迷いなく保守性のほうを選べるポジションで、毎日一定のペースで改善していけるソフトウェアが作れるチームを目指して、しばらくやってみようと思います。
cook+bizcoはPinocoとは無関係ですが、かわいいのでお友達になりたいです。すでにPinocoのコードはcookbiz.jpサイトの主な機能に使われています。やっぱ便利やで。
PHPとかいろいろ演算代入系の演算子のハナシ
に、続いて。アンリーダブルコードで勉強しようというのがあった、そのとある勉強会の発表ネタです。
これは、PHPのMarkdownパーサ の実装を可能な限りそのまま綺麗に変換してJavaScriptに移植しようという js-markdown-extra をやっていたとき、大ハマりして修正に苦労したバグの話から来てます。
演算代入。+=
とか *=
とかのやつ。関数型の人以外はきっと常用してますね。じゃあ問題。
<?php $tokens = array("a", "b", "c"); $tokens[0] .= array_shift($tokens); print_r($tokens);
こうするとどんな結果が出力されるでしょうか。PHPです。
自身の先頭から要素を取り出して、それを先頭要素に文字列追加する。array_shift()
では "a"
が先頭から取り出されて $tokens == array("b", "c")
になります。取り出された値は配列の先頭要素に「文字列として追加」されて配列の先頭要素に代入されます。結果を見ましょう。
$ php test.php Array ( [0] => ba [1] => c )
...って思いますよね。ここまで、いいですか、よくないですか。
ところがPHPは... という話だと思ったら大間違い。もしPHP以外の言語の人なら、間違っているのはあなたのプログラミング能力ですよと。PHPerはこれでOKです。
JavaScriptで書きました。
var tokens = ["a", "b", "c"]; tokens[0] += tokens.shift(); console.log(tokens);
なんとこれ、答えはこう出ます。
$ node test.js [ 'aa', 'c' ]
え、え、まあ JavaScript は変な言語だからさあ...
そこにPython委員長がやってきた。
tokens = ["a", "b", "c"] tokens[0] += tokens.pop(0) print tokens
↓
$ python test.js ['aa', 'c']
やばい!! PHPちゃんが多数決で負けちゃう。助けてRubyちゃん。(嫌なこと言われることもあるけど、最近嫌いじゃないのよね、あの子。)
tokens = ["a", "b", "c"] tokens[0] += tokens.shift p tokens
↓
$ ruby test.rb ["aa", "c"]
あああああーーーっ、Ruby子ちゃんまで。PHPちゃんボッチかわいそう。
検証
すんません、ちゃんとやります、はい。ようするに、a += b
が a = a + b
と等価ってのがわかればいいんですよ、きっと。JavaScriptで検証、やってみましょう。
function equal_and_plus() { var tokens = ["a", "b", "c"]; tokens[0] = tokens[0] + tokens.shift(); console.log(tokens); } function equal_plus_operator() { var tokens = ["a", "b", "c"]; tokens[0] += tokens.shift(); console.log(tokens); } equal_and_plus(); equal_plus_operator();
↓
[ 'aa', 'c' ] [ 'aa', 'c' ]
合格ですね。PythonもRubyもこれと同じですね。じゃあPHPはいったい...
<?php function equal_and_plus() { $tokens = array("a", "b", "c"); $tokens[0] = $tokens[0] . array_shift($tokens); ptint_r($tokens); } function equal_plus_operator() { $tokens = array("a", "b", "c"); $tokens[0] .= array_shift($tokens); ptint_r($tokens); } equal_and_plus(); equal_plus_operator();
↓
Array ( [0] => aa [1] => c ) Array ( [0] => ba [1] => c )
違う演算じゃんかこれ
そのとおり、PHPの場合は「加算と代入」は「加算代入」と等価ではなかったのです。ちょっと最初に動かす前に想像した結果に戻ってみてください。「取り出して」→(右辺の変数が変化して)→「代入」って考えましたよね。PHPはそのとおり動きました。
でも他の言語では実際には、「加算代入は加算と代入である」という等価関係を死守します。その結果、最初に評価されるのは tokens[0] = tokens[0] + shift(tokens)
左辺の第1項 tokens[0]
つまり "a"
です。それに、shift
で先頭要素を取り出して詰めた戻り値の "a"
が足されます。そして、["b", "c"]
という2要素の配列となったものの最初の要素 "b"
に "aa"
が代入されます。
たしかにPHPは、言語としての美しさの点では劣りました。が、そもそも「加算と代入」は「加算代入」と等価であるという言語仕様は、すべての処理系で保証されるのでしょうか。「加算代入」という等価変換できない別の演算があるのだと考えればどうでしょう? そう考えるとPHPは、人間らしい自然な思考に合わせて、「加算と代入」が「加算代入」と等価ではない処理系を選んでいるとも言えます。
まあ、こういうことですね。
<?php function eval_right_then_set_to_left() { $tokens = array("a", "b", "c"); // 右辺の値 $right = array_shift($tokens); // 左辺に入る値を算出 $left = $tokens[0] . $right; // 代入のところ $tokens[0] = $left; ptint_r($tokens); }
そもそも、二者が等価でなければならない理由ってあるんでしょうか。C言語と違うから? 違う言語は違う処理系を持っていて当然ですね。Cはコンパイラにとってコンパクトな仕様だったから等価にしただけかも。ルールが少ないほうが文法の学習コストが低い? 言語の文法なんて他の学習の総コストに比べたら低いですよね。マニュアルに「同じじゃないよ」と書いてあればそう学べばいいんですよ。
$a = 3; $a += 5; // $a を 8 にセットします。$a = $a + 5; と同じです。
ギャフン
こ...こんなことが宝石系の言語の人に知れたら、またはてブが炎上するに違いない。こりゃ門外不出だぜ。なんかアレみたいな話だな。
と思ったんですが、試しに普段あまり使わないPerlでもやってみました。
# 加算して代入 @tokens = ("a", "b", "c"); $tokens[0] = $tokens[0] . shift(@tokens); print join(',', @tokens) . "\n"; # 加算代入演算子 @tokens = ("a", "b", "c"); $tokens[0] .= shift(@tokens); print join(',', @tokens) . "\n";
とりゃー
aa,c b,c
なんと、誰も期待しなかった結果を返す言語は実はPerlでした。
これ、結果の解釈いろいろ深いですね。なんかこんな感じ? せっかく入れた "aa"
を最後に消しちゃう。んーよくわかんない。
@tokens = ("a", "b", "c"); $tokens[0] .= $tokens[0]; # shiftからの戻り値のみ shift(@tokens); # shiftの実行そのもの print join(',', @tokens) . "\n";
(よしこれで白い宝石の方からのマサカリは飛んで来ないぞ...と思うんだけど、もし、Perlではこう書くのが普通だよというのがあったら教えてください)
(ブクマコメントで @tokens[0]
がスライスになってると教えてもらいました。やってもうた。$
に修正して結果が同になることを確認しました。)
というわけで、これって怖い人に「PHPなんてものを使うことがそもそも...」とか言われる系の話じゃなくて、
見た目がまったく同じなのに言語処理系によって非互換性なところはあるので、どの言語を使うにしても、直感で人によって解釈が分かれるようなコードになるおそれがある場合は、他人が読んでもわかるよう、自分の頭の中にある理解の順を正しく表現したコードにしましょう、
というのが大事ですねという話でした。
これって、純粋関数型で再代入を禁止するべきとかそういう極端な話じゃなくて、プログラマーの素朴な人間力ってだけだと思います。処理系が違えば言語仕様は違って当たり前なのに、自分の好きなアレと違うからダメな言語と言ってしまうのは良くないよね、とか、字面が読みやすいかどうかではなく、こういうのを分けて書かないとアンリーダブルで大変なことになるんだという学びとか。
あ、フィクションじゃないですよ。ホントにあったんですよこれ。
そうそう PHP といえば
こちらもよろしくお願いします!
なぜ『PHPエンジニア養成読本』はAmazon部門ランキングでトップを取るのか
PHPエンジニア養成読本 〔現場で役立つイマドキ開発ノウハウ満載! 〕 (Software Design plus)
- 作者: 新原雅司,原田康生,小山哲志,田中久輝,保科一成,大村創太郎,増永玲,PHPエンジニア養成読本編集部
- 出版社/メーカー: 技術評論社
- 発売日: 2013/09/13
- メディア: 大型本
- この商品を含むブログを見る
なぜ『PHPエンジニア養成読本』はAmazon部門ランキングでトップを取るのか
またしても釣りっぽいタイトルをつけてしまいました。ご無沙汰しております。
PHPMatsuri 2013の参加レポートも書かずに何をやっていたのかというと、実は本の執筆に参加させて頂いており、ちょうどその頃忙しすぎて忘れていました。こんな本です。
PHPエンジニア養成読本 〔現場で役立つイマドキ開発ノウハウ満載! 〕 (Software Design plus)
- 作者: 新原雅司,原田康生,小山哲志,田中久輝,保科一成,大村創太郎,増永玲,PHPエンジニア養成読本編集部
- 出版社/メーカー: 技術評論社
- 発売日: 2013/09/13
- メディア: 大型本
- この商品を含むブログを見る
その名も「PHPエンジニア養成読本」。担当させてもらったのは、注目のフレームワーク、PHPUnit、Gitといった内容です。詳しい内容は先に書かれているエントリご覧ください。
- いまどきのPHP開発ノウハウを詰め込んだ『PHPエンジニア養成読本』が出るので、見所をまとめてみるよ - 頭ん中
- いまどきのPHPが分かる「PHPエンジニア養成読本」が出ます - Shin x blog
ようやく書影が定まり、こんな感じで本日(2013/8/27 ...あ、日付変わってるか)より告知スタートしています。で...
告知されるやいなや、なんとみるみるうちにAmazonのWeb開発書籍ランキングでトップに躍り出ました。
なんということでしょう。まだ予約しかできない段階だし「ナカ見」とかもないんですよ。
そしてさらに、書籍全体ランキングで『連続テレビ小説あまちゃん Part2 (NHKドラマ・ガイド)』と競うとかもう。
まあ、これは瞬間最大風速的なデータなのかもしれませんが、それでも、表紙や章立てしかわからないのにすぐに購入予約してくださる方がこんなに多いのって、すごくないですか。ありがとうございますありがとうございます。
ちょっと執筆参加したというのは抜きにして、本当に、なぜこの本がこんなに上がるんだろうということに思いを馳せてみました。
いまPHPの世界には、すごく大きな変化が訪れています。でも、その変化がずっと連続していて、これまでなかなか一冊の本にまとめられるタイミングがなかった状況が続いていたんじゃないかと思います。
PHPで仕事をしている人は数多くおられると思います。みなさん、毎日忙しく働いているいっぽうで、Twitterやブログであれこれと新しい技術の名前が流れてきたり、あんな勉強会に行ったこんな勉強会に行ったと羨ましい話を聞いたり。本当は全部試したいけれど、とてもじゃないがそんな時間はないという方が大多数なんじゃないでしょうか。
最近はアジャイルな空気が出てきて、J2EEの大型開発案件よりも、PHPを使った小さなツールを素早く作るような仕事が増えて、JavaプログラマのPHP流入も多いと聞きます。Javaの人からすれば、「Mavenはないのか」「コーディング規約どうなってる」「PHPでいうJUnitとかEclipseとかはどんな感じなのか」というもやもやも多いでしょう。
また、新しい技術はいつも英語で発信されます。「エンジニアたるもの英語のひとつも読めなければ」と言うのは簡単ですが、現実には、読もうと思って開いたページの英語は読めても、英語で書かれた情報の海にアンテナを張ることができる日本人は、かなり限られると思います。
これまで、入門者を対象にしたPHPの本は繰り返し出版されてきました。また、ひとつのフレームワークに特化して深く内容を掘り下げる本もいくつか出版されています。しかし、 パーフェクトPHP 以降、すでに入門を終えていてもっと成長したいのに、と感じているエンジニアに、PHPの文化を広く俯瞰できるような内容の本が提供されて来なかったんじゃないでしょうか。
それは、ライブラリやサービスの新バージョンがそれぞれ非同期にかつ継続的にリリースされ続け、一冊の本として書けるほど安定していなかったからではないかと思います。
そんな状況で、「ムック本だから執筆時点のスナップショットでいい」と割りきって紙にしてしまおうという方針が、もやもやしていたPHPerのハートを直撃したんじゃないかと思います。読者としては、こんなコンセプトの本は他にない、だから選ぶ余地なく予約しよう、ってことなのかなと。
執筆のキックオフ・ミーティング前後で「他の本で取り上げられていなかったことをやろう」と話していたのが結果につながったのなら幸いです。
ムック本なので個々の記事のボリュームには限度があるけれど、英語だったり散発的に聞きかじったりしているあれやこれの最初のアンテナとして、たしかにすごくリーズナブルな一冊かも、と、執筆していなかったら自分も予約していただろうと思いました。
この本はこんな方にお勧めです:
- 以前からPHPを使った開発をしているけれど、よりモダンなPHPを目指したい設計者
- PHPのバージョンを上げたはいいけど、それでどんな機能が使えるのか自覚していなかった人
- フレームワークやライブラリなど、話題の技術の最初の一歩でどれをやったらいいか迷っている人
- 本当にPHPを使い込んでいる人が技術のどの部分に注目するのか、気になる人
- どこから試していいのか、どれぐらいお金がかかるのかわからず、クラウドに手を出せなかった人
- モダンPHPに関係するキーワードが拾えていなくて一気に仕入れたい人
実際は使われていたり機能が良かったりするのに、これまでほとんど紙媒体に掲載されることがなかったキーワードをじゃんじゃん載せているのは本当です。これはすごくオススメポイント。
あと、共同執筆のムック本では珍しいと思うんですが、執筆者の多くが直接顔を合わせ、互いに内容をレビューし合って書かれていて、書籍としての一貫性や各章の関連性がわりと強い感じです。なので個人的には、いろんなことを知れる本であると同時に、一冊の本としてPHPで起こっている一本の大きな潮流を表現できていたらなぁと願っています。
最後になりますが、こんな素晴らしいプロジェクトに誘ってくださって、関係者の皆様本当にありがとうございます。この二ヶ月はとても貴重な体験でした。
p.s.
PHPカンファレンス東京へ行かれる方は、Amazonの予約で1冊、PHPカンファレンス会場でもう1冊買うのをオススメします。発売日が9/13なので、9/14日のカンファレンスの時点で入手できていない可能性があります。2冊も買うのかと思うかもしれませんが、1冊は自宅用で、もう1冊は布教用です。職場で回覧して「導入しましょう!!」ってやるためのやつです。なので会社名で領収書を切っておきましょう。