Skip to content

使ってはいけないPHPメタプログラミングの温床

公開日:

東京都渋谷区GMO Yoursで開催された『第117回 PHP勉強会@東京』でライトニングトーク(5分)として発表しました。

Download PDF

スライドテキスト

Page 1

使ってはいけない

メタプログラミング 初級篇

Dangerous PHP Meta programming Tutorial

2017-08-30 PHP勉強会@東京 #phpstudy

Page 2

お前誰よ

  • うさみけんた (@tadsan) / Zonu.EXE
  • GitHub/Packagistでは id: zonuexe
  • Emacs Lisper, PHPer
  • pixiv Inc.でpixivを作ってるよ
  • Qiitaに記事を書いたり変なコメントしてるよ
  • 動的言語はコンピューティングの真髄

Page 3

はじめに

Page 4

サンプルコードは言語機能を簡易に紹介するためのものです

Page 5

利用の際は必要性と利便性を慎重に判断すること。

Page 6

アジェンダ

Page 7

0. 前提1. 初級篇2. 中級篇3. 上級篇

Page 8

0. 前提1. 初級篇2. 中級篇3. 上級篇

Page 9

Page 10

メタプログラミング

Page 11

非常に雑にまとめると、一見して意味のわかりにくい謎テク

Page 12

前提

Page 13

メタプログラミング(metaprogramming)とはプログラミング技法の一種で、ロジックを直接コーディングするのではなく、あるパターンをもったロジックを生成する高位ロジックによってプログラミングを行う方法、またその高位ロジックを定義する方法のこと。

https://ja.wikipedia.org/wiki/メタプログラミング

Page 14

直接コーディングするのではなく

https://ja.wikipedia.org/wiki/メタプログラミング

Page 15

突き詰めていくと関数呼び出しすら「直接コーディング」しないと解釈できる

Page 16

それよりはちょっと複雑なものがメタプログラミング

Page 17

PHPは、所謂LLとしては遊びが少ない

Page 18

例) 原則として関数やクラスを再定義できない(致命的エラーが発生)

Page 19

RubyやPythonと違ってオープンクラスではない(モンキーパッチができない)

Page 20

謎なタイミングでクラス定義が変更されることが(基本的には)ない

Page 21

=静的解析しやすい条件が揃ってる

Page 22

変数スコープが(比較的)シンプル

Page 23

その和を乱す邪悪な動的メタプログラミングテクニックが存在する

Page 24

😇

Page 25

「一見してわかりにくい言語機能」

「ユーザー定義関数では不可能なマジックを実現してくる謎関数」

Page 26

初級編

Page 27

callable

Page 28

関数として呼出し可能な値を表す擬似型

Page 29

map

$ary = ["apple", "orange", "banana"];//=> ["Apple", "Orange", "Banana"]

// 配列の中身を一個一個変更する

$bry = [];foreach ($ary as $i => $a) {$bry[$i] = ucfirst($a);}

Page 30

map

$ary = ["apple", "orange", "banana"];//=> ["Apple", "Orange", "Banana"]

// 配列の中身を一個一個変更する

$cry = array_map('ucfirst', $ary);

Page 31

map

$ary = ["apple", "orange", "banana"];//=> ["Apple", "Orange", "Banana"]

$bry = [];foreach ($ary as $i => $a) {

$cry =array_map('ucfirst', $ary);

$bry[$i] => ucfirst($a);}

Page 32

callable

// Before

if ($cond) {foo($var);} else {bar($var);}

Page 33

callable

// After

$f = $cond ? 'foo' : 'bar';call_user_func($f, $var);

// こうやって書いてもいい

$f($var);

Page 34

callable

// After$f = $cond ? 'foo' : 'bar';call_user_func($f, $var);

// Before if ($cond) {foo($var);} else {bar($var);}

// こうやって書いてもいい$f($var);

Page 35

使ってはいけない度

★★☆☆☆

僕はコードレビューで「foreachの方が読みやすい」ってマジレスすることもある

(★が多いほど邪悪)

Page 36

(メタプログラミングと呼ぶかは議論がある)

Page 37

PHPの「ふつう」はひとによって境界が異なる

Page 38

compact()extract()

Page 39

変数テーブルにアクセスできる関数

Page 40

compact()変数テーブルから取り出して配列にする

Page 41

compact()

$foo = foo();$bar = bar();

hoge(compact('foo','bar'));

hoge(['foo' => $foo,'bar' => $bar]);

Page 42

使ってはいけない度

★☆☆☆☆

何が起こるか把握して書くぶんには罠が少ない

(★が多いほど邪悪)

Page 43

extract()変数テーブルから取り出して配列に

Page 44

extract()

function hoge(array $vs){

extract($vs,

EXTR_PREFIX_ALL,

$hoge_foo = $vs['foo'];$hoge_bar = $vs['bar'];

'hoge');

本当はissetでチェックすべき

Page 45

使ってはいけない度

★★★★★

何が起こるか把握して書くぶんには罠が少ない

(★が多いほど邪悪)

Page 46

extract()変数定義が検出できない

読みにくくなるので邪悪

バグの温床であるばかりか、ふつうに脆弱性の温床になる

Page 47

$$var

Page 48

$$var可変変数(variable variables)

Page 49

可変変数

$world = "こんにちは世界";$hello = "world";$v = "hello";

var_dump($v); //=> "hello"var_dump($$v); //=> "world"var_dump($$$v); //=> "こんにちは世界"// $$ はいくつ付けてもよい!!

Page 50

使ってはいけない度

★★★★★

純粋に読みにくい……。error_reporingのレベルが低いと警告なしでnullになる。

(★が多いほど邪悪)

Page 51

debug_backtrace()

Page 52

スタックトレースを全部取得できる(ファイル・引数含む)

Page 53

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

Page 54

(これを悪用すればどんな邪悪なことができるかは明白)

Page 55

超べんりな用途 (ロギング)

function db($query, array $param) {

$trace = implode('; ', array_map(function ($t) {return "{$t['file']}({$t['line']})";

}, debug_backtrace()));$stmt = PDO::query($query.' -- '.$trace);// SQLスロークエリログの末尾にトレースが// 残るので、原因の特定しやすくなる!

Page 56

邪悪な用途

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";}

Page 57

使ってはいけない度

★★★☆☆

ロギングは問題なし。ワークアラウンドはギルティ。

(★が多いほど邪悪)

Page 58

まとめ

Page 59

今回は初級篇

Page 60

プログラミングのおもしろテクを知ったら使ってみたくなるのが人情

Page 61

大いなる力には大いなる責任が伴う

Page 62

メタプログラミングの採用で劇的に便利になることもある

Page 63

単に初心者殺しな意味不明なコードになることがある

Page 64

(経験豊富な先輩に「若いなあ」となまあたたかく見られるかも)

Page 65

用法容量を守ってたのしいプログラミングを

To Be Continued