Page 1
実用メタプログラミング
Practical PHP meta programming
PHPカンファレンス沖縄2019 2019-10-12 #phpconokinawa
公開日:
by USAMI Kenta@tadsan
に沖縄県宜野湾市のCODEBASE OKINAWAで開催された『PHPカンファレンス沖縄2019』でレギュラートーク(30分)として発表しました。
Practical PHP meta programming
PHPカンファレンス沖縄2019 2019-10-12 #phpconokinawa
さて
メタプログラミングとは何か
そもそも
メタとは
出典: フリー百科事典『ウィキペディア(Wikipedia)』
https://ja.wikipedia.org/wiki/メタ最終更新 2019年1月10日 10:01 版
出典: フリー百科事典『ウィキペディア(Wikipedia)』
https://ja.wikipedia.org/wiki/メタ最終更新 2019年1月10日 10:01 版
出典: フリー百科事典『ウィキペディア(Wikipedia)』
https://ja.wikipedia.org/wiki/メタ最終更新 2019年1月10日 10:01 版
メタプログラミングとは何か
プログラム自身に言及するようなプログラミング
プログラムをプログラミングする
どういうこと?
たとえば実行時に関数を定義する
たとえば実行時に実行する関数を決める
たとえばプロパティやメソッドのアクセス権限を制御する
たとえば実装コードから自動でテストコードを生成する
厳密にプログラミングとメタプログラミングを分けるものはないが
フレームワークは大なり小なりメタプログラミングを利用している(ことが多い)
PHPの可能性を広げるのがメタプログラミング
メタプログラミングできることで有名な言語
Ruby
出典: O'Reilly Japan - メタプログラミングRuby 第2版https://www.oreilly.co.jp/books/9784873117430/
Paolo Perrotta 著 / 角征典 訳 / 2015年10月 発行
Lisp
出典: フリー百科事典『ウィキペディア(Wikipedia)』
https://ja.wikipedia.org/wiki/メタプログラミング
最終更新 2017年12月6日 23:08 版
PHPではできないことの方が多い
オープンクラスがないプリプロセスもない
PHPならではの戦いかたがある!
関数
FizzBuzzで考えてみましょう
出典: どうしてプログラマに・・・プログラムが書けないのか?
http://www.aoky.net/articles/jeff_atwood/why_cant_programmers_program.htm
Jeff Atwood著 / 青木靖 訳
出典: どうしてプログラマに・・・プログラムが書けないのか?
http://www.aoky.net/articles/jeff_atwood/why_cant_programmers_program.htm
Jeff Atwood著 / 青木靖 訳
<?php
foreach (range(1, 100) as $n) {$s = '';if ($n % 3 === 0) $s .= 'Fizz';if ($n % 5 === 0) $s .= 'Buzz';echo ($s === '') ? $n : $s, "\n";}
<?php function to_fizzbuzz(int $n) {$s = '';if ($n % 3 === 0) $s .= 'Fizz';if ($n % 5 === 0) $s .= 'Buzz';return ($s === '') ? $n : $s;
}
foreach (range(1, 100) as $n) {echo to_fizzbuzz($n), "\n";}
<?php
assert(to_fizzbuzz(1) === "1");assert(to_fizzbuzz(2) === "2");assert(to_fizzbuzz(3) === "Fizz");assert(to_fizzbuzz(4) === "4");assert(to_fizzbuzz(5) === "Buzz");
無茶振りをしましょう
関数に分けたいでも引数使いたくない
<?php
assert(fizzbuzz1() === "1");assert(fizzbuzz2() === "2");assert(fizzbuzz3() === "Fizz");assert(fizzbuzz4() === "4");assert(fizzbuzz5() === "Buzz");
function fizzbuzz1() { return "1"; }function fizzbuzz2() { return "2"; }function fizzbuzz3() { return "Fizz"; }function fizzbuzz4() { return "4"; }function fizzbuzz5() { return "Buzz"; }
手書きで100個書くの!?
<?php // このコードはsyntax errorになります
foreach (range(1, 100) as $n) {$s = '';if ($n % 3 === 0) $s .= 'Fizz';if ($n % 5 === 0) $s .= 'Buzz';
// 変数を使った関数定義はできない
function fizzbuzz$n(){return ($s === '') ? $n : $s, "\n";}}
<?php
foreach (range(1, 100) as $n) {$s = '';if ($n % 3 === 0) $s .= 'Fizz';if ($n % 5 === 0) $s .= 'Buzz';$s = ($s === '') ? $n : $s;// evalを使えばいちおうできる
eval("function fizzbuzz{$n} (){return '{$s}';}");}
(
関数も
create_function()
あったがPHP7.2で非推奨)
無名関数(クロージャ)を使いましょう
<?php$fizzbuzz = [];foreach (range(1, 100) as $n) {$s = '';if ($n % 3 === 0) $s .= 'Fizz';if ($n % 5 === 0) $s .= 'Buzz';$s = ($s === '') ? (string)$n : $s;
$fizzbuzz[$n] = function () use ($s) {return $s;};}
クロージャの特徴
関数定義文ではなく関数式
使い捨て関数を簡単に作れる
use
関数式
は
で
変数捕捉
できる
Closure
クラスのオブジェクト
で、
変数や引数として扱える
<?php
assert($fizzbuzz[1]() === "1");assert($fizzbuzz[2]() === "2");assert($fizzbuzz[3]() === "Fizz");assert($fizzbuzz[4]() === "4");assert($fizzbuzz[5]() === "Buzz");
クロージャを使ったコード
<?php
$x2 = function ($n){ return $n * 2; };
$a = range(1, 10);var_dump(array_map($x2, $a));// [2, 4, 6, 8, 10, 12, 14, 16, 18, 20]
クロージャをうまく使って「関数を作る関数」も作れる
<?php
$times = function ($n) {return function ($m) use ($n) {return $n * $m;};};
$a = range(1, 10);echo json_encode(array_map($times(3), $a));// [3,6,9,12,15,18,21,24,27,30]
マジックメソッド
オブジェクトの挙動を制御する特殊なメソッド
プロパティアクセスを乗っ取ろう
<?php class A {private $data = [];
public function __get($name) {$this->data[$name];}public function __set($name, $value) {$this->data[$name] = $value;}}
__isset() と __unset() も定義すべき
アクセス不能=privateなども含む
この仕様を悪用してこんなこともできる
<?php trait PrivateRead {public function __get($name) {$this->$name;}}
/*** @property-read int $id* @property-read int $name*/class A {use PrivateRead;private $id;private $name;}
<?php
trait ReadOnly {public function __set($name, $value) {throw new OutOfRangeException();}}
class B {use ReadOnly;}
メソッドアクセスも乗っ取ろう
オーバーロード(ただしPHP用語)
出典: PHP: オーバーロード - Manual
https://www.php.net/manual/ja/language.oop5.overloading.php
Copyright © 2001-2019 The PHP Group
一般的なプログラミング用語「オーバーロード」とは全然別物なので注意
<?php
class C {public function __call($name, $args){echo __CLASS__."::{$name}()が呼ばれた\n";
}}
<?php
/*** @mixin Foo*/class D {private $foo;public function (Foo $class) {$this->foo = $class;}public function __call($name, $args){return $this->foo->$name(...$args);}}
リフレクション
実行時に情報を取得(一部上書き)できるAPI
関数・クラス・オブジェクト・メソッド・プロパティ・パラメータ(引数)・返値の型・クラス定数など
https://www.php.net/manual/ja/book.reflection.php
PHPUnitのdataProvider(パラメタライズテスト)とも組み合わせられる
<?php
class C {public function __call($name, $args){echo __CLASS__."::{$name}()が呼ばれた\n";
}}
まとめ
メタプログラミングの技術を使うと「コードに書いてある通りではない」動きをさせられる
代償としてPhpStormなどの支援を受けにくくなることがある
大いなる力には大いなる責任が伴う
