Page 1
使ってはいけない
メタプログラミング 初級篇
Dangerous PHP Meta programming Tutorial
2017-08-30 PHP勉強会@東京 #phpstudy
公開日:
by USAMI Kenta@tadsan
に東京都渋谷区のGMO Yoursで開催された『第117回 PHP勉強会@東京』でライトニングトーク(5分)として発表しました。
Dangerous PHP Meta programming Tutorial
2017-08-30 PHP勉強会@東京 #phpstudy
はじめに
サンプルコードは言語機能を簡易に紹介するためのものです
利用の際は必要性と利便性を慎重に判断すること。
アジェンダ
0. 前提1. 初級篇2. 中級篇3. 上級篇
0. 前提1. 初級篇2. 中級篇3. 上級篇
メタプログラミング
非常に雑にまとめると、一見して意味のわかりにくい謎テク
前提
メタプログラミング(metaprogramming)とはプログラミング技法の一種で、ロジックを直接コーディングするのではなく、あるパターンをもったロジックを生成する高位ロジックによってプログラミングを行う方法、またその高位ロジックを定義する方法のこと。
https://ja.wikipedia.org/wiki/メタプログラミング
https://ja.wikipedia.org/wiki/メタプログラミング
突き詰めていくと関数呼び出しすら「直接コーディング」しないと解釈できる
それよりはちょっと複雑なものがメタプログラミング
PHPは、所謂LLとしては遊びが少ない
例) 原則として関数やクラスを再定義できない(致命的エラーが発生)
RubyやPythonと違ってオープンクラスではない(モンキーパッチができない)
謎なタイミングでクラス定義が変更されることが(基本的には)ない
=静的解析しやすい条件が揃ってる
変数スコープが(比較的)シンプル
その和を乱す邪悪な動的メタプログラミングテクニックが存在する
😇
「一見してわかりにくい言語機能」
「ユーザー定義関数では不可能なマジックを実現してくる謎関数」
初級編
callable
関数として呼出し可能な値を表す擬似型
$ary = ["apple", "orange", "banana"];//=> ["Apple", "Orange", "Banana"]
// 配列の中身を一個一個変更する
$bry = [];foreach ($ary as $i => $a) {$bry[$i] = ucfirst($a);}
$ary = ["apple", "orange", "banana"];//=> ["Apple", "Orange", "Banana"]
$ary = ["apple", "orange", "banana"];//=> ["Apple", "Orange", "Banana"]
$bry = [];foreach ($ary as $i => $a) {
$cry =array_map('ucfirst', $ary);
$bry[$i] => ucfirst($a);}
// Before
if ($cond) {foo($var);} else {bar($var);}
// After
$f = $cond ? 'foo' : 'bar';call_user_func($f, $var);
// こうやって書いてもいい
$f($var);
// After$f = $cond ? 'foo' : 'bar';call_user_func($f, $var);
// Before if ($cond) {foo($var);} else {bar($var);}
// こうやって書いてもいい$f($var);
僕はコードレビューで「foreachの方が読みやすい」ってマジレスすることもある
(★が多いほど邪悪)
(メタプログラミングと呼ぶかは議論がある)
PHPの「ふつう」はひとによって境界が異なる
compact()extract()
変数テーブルにアクセスできる関数
compact()変数テーブルから取り出して配列にする
$foo = foo();$bar = bar();
hoge(compact('foo','bar'));
hoge(['foo' => $foo,'bar' => $bar]);
何が起こるか把握して書くぶんには罠が少ない
(★が多いほど邪悪)
extract()変数テーブルから取り出して配列に
function hoge(array $vs){
extract($vs,
EXTR_PREFIX_ALL,
$hoge_foo = $vs['foo'];$hoge_bar = $vs['bar'];
'hoge');
何が起こるか把握して書くぶんには罠が少ない
(★が多いほど邪悪)
extract()変数定義が検出できない
読みにくくなるので邪悪
バグの温床であるばかりか、ふつうに脆弱性の温床になる
$$var
$$var可変変数(variable variables)
$world = "こんにちは世界";$hello = "world";$v = "hello";
var_dump($v); //=> "hello"var_dump($$v); //=> "world"var_dump($$$v); //=> "こんにちは世界"// $$ はいくつ付けてもよい!!
純粋に読みにくい……。error_reporingのレベルが低いと警告なしでnullになる。
(★が多いほど邪悪)
debug_backtrace()
スタックトレースを全部取得できる(ファイル・引数含む)
Baguette\FriendsClass\CalledByOutsiderException: It is not allowed for
Baguette\Ore to call in ~/friends/src/functions.php on line 35
Call Stack:
0.0003 359288 1. {main}() ~/friends/tests/test.php:0 0.0020 552824 2. Baguette\Omae->callFriend() ~/friends/tests/
test.php:33 0.0020 552864 3. Baguette\Ore->__construct() ~/friends/tests/test.php:20
0.0020 552864 4. Baguette\assert_called_by_friend() ~/friends/tests/test.php:12
(これを悪用すればどんな邪悪なことができるかは明白)
function db($query, array $param) {
$trace = implode('; ', array_map(function ($t) {return "{$t['file']}({$t['line']})";
}, debug_backtrace()));$stmt = PDO::query($query.' -- '.$trace);// SQLスロークエリログの末尾にトレースが// 残るので、原因の特定しやすくなる!
function hoge($a, $b, $c) {
$trace = debug_backtrace(true, 2);$class = $trace[1]['class'] ?? false;$func = $trace[1]['func'] ?? false;if ($class === "Foo" && $func === "bar") {// Foo::bar() はバグってるので値を上書き$b = "piyo";}
ロギングは問題なし。ワークアラウンドはギルティ。
(★が多いほど邪悪)
まとめ
今回は初級篇
プログラミングのおもしろテクを知ったら使ってみたくなるのが人情
大いなる力には大いなる責任が伴う
メタプログラミングの採用で劇的に便利になることもある
単に初心者殺しな意味不明なコードになることがある
(経験豊富な先輩に「若いなあ」となまあたたかく見られるかも)
用法容量を守ってたのしいプログラミングを
To Be Continued
