Skip to content

実用メタプログラミング

公開日:

沖縄県宜野湾市CODEBASE OKINAWAで開催された『PHPカンファレンス沖縄2019』でレギュラートーク(30分)として発表しました。

Download PDF

スライドテキスト

Page 1

実用メタプログラミング

Practical PHP meta programming

PHPカンファレンス沖縄2019 2019-10-12 #phpconokinawa

Page 2

お前誰よ

  • うさみけんた (@tadsan) / Zonu.EXE
  • GitHub/Packagistでは id: zonuexe
  • ピクシブ株式会社 pixiv事業本部
  • Emacs Lisper, PHPer
  • Emacs PHP Modeのメンテナ引き継ぎました
  • 好きなリスプはEmacs Lispです
  • Qiitaに記事を書いたり変なコメントしてるよ

Page 3

Page 4

Page 5

Page 6

さて

Page 7

メタプログラミングとは何か

Page 8

そもそも

Page 9

メタとは

Page 10

出典: フリー百科事典『ウィキペディア(Wikipedia)』

https://ja.wikipedia.org/wiki/メタ最終更新 2019年1月10日 10:01 版

Page 11

出典: フリー百科事典『ウィキペディア(Wikipedia)』

https://ja.wikipedia.org/wiki/メタ最終更新 2019年1月10日 10:01 版

Page 12

出典: フリー百科事典『ウィキペディア(Wikipedia)』

https://ja.wikipedia.org/wiki/メタ最終更新 2019年1月10日 10:01 版

Page 13

メタプログラミングとは何か

Page 14

プログラム自身に言及するようなプログラミング

Page 15

プログラムをプログラミングする

Page 16

どういうこと?

Page 17

たとえば実行時に関数を定義する

Page 18

たとえば実行時に実行する関数を決める

Page 19

たとえばプロパティやメソッドのアクセス権限を制御する

Page 20

たとえば実装コードから自動でテストコードを生成する

Page 21

厳密にプログラミングとメタプログラミングを分けるものはないが

Page 22

フレームワークは大なり小なりメタプログラミングを利用している(ことが多い)

Page 23

PHPの可能性を広げるのがメタプログラミング

Page 24

メタプログラミングできることで有名な言語

Page 25

Ruby

Page 26

出典: O'Reilly Japan - メタプログラミングRuby 第2版https://www.oreilly.co.jp/books/9784873117430/

Paolo Perrotta 著 / 角征典 訳 / 2015年10月 発行

Page 27

Lisp

Page 28

出典: フリー百科事典『ウィキペディア(Wikipedia)』

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

最終更新 2017年12月6日 23:08 版

Page 29

PHPではできないことの方が多い

Page 30

オープンクラスがないプリプロセスもない

Page 31

PHPならではの戦いかたがある!

Page 32

関数

Page 33

FizzBuzzで考えてみましょう

Page 34

出典: どうしてプログラマに・・・プログラムが書けないのか?

http://www.aoky.net/articles/jeff_atwood/why_cant_programmers_program.htm

Jeff Atwood著 / 青木靖 訳

Page 35

出典: どうしてプログラマに・・・プログラムが書けないのか?

http://www.aoky.net/articles/jeff_atwood/why_cant_programmers_program.htm

Jeff Atwood著 / 青木靖 訳

Page 36

FizzBuzz模範回答

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

Page 37

FizzBuzz模範回答 関数分割

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

Page 38

関数に分けるとテストが書ける

<?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");

Page 39

無茶振りをしましょう

Page 40

関数に分けたいでも引数使いたくない

Page 41

愚直に書くとこうなる

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

Page 42

手書きで100個書くの!?

Page 43

こういうことはできない

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

Page 44

[非推奨] evalを使った無茶

<?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}';}");}

Page 45

evalはなんでも

できるがリスクも大きい

最後の手段

(

関数も

create_function()

あったがPHP7.2で非推奨)

Page 46

無名関数(クロージャ)を使いましょう

Page 47

クロージャを使えばできる

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

Page 48

クロージャの特徴

関数定義文ではなく関数式

使い捨て関数を簡単に作れる

use

関数式

変数捕捉

できる

Closure

クラスのオブジェクト

で、

変数や引数として扱える

Page 49

これで呼び出せるようになった

<?php

assert($fizzbuzz[1]() === "1");assert($fizzbuzz[2]() === "2");assert($fizzbuzz[3]() === "Fizz");assert($fizzbuzz[4]() === "4");assert($fizzbuzz[5]() === "Buzz");

Page 50

クロージャを使ったコード

Page 51

クロージャ+array_map

<?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]

Page 52

クロージャをうまく使って「関数を作る関数」も作れる

Page 53

クロージャを使った部分適用

<?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]

Page 54

マジックメソッド

Page 55

オブジェクトの挙動を制御する特殊なメソッド

Page 56

プロパティアクセスを乗っ取ろう

Page 57

アクセス不能プロパティを乗っ取る

<?php class A {private $data = [];

public function __get($name) {$this->data[$name];}public function __set($name, $value) {$this->data[$name] = $value;}}

__isset() と __unset() も定義すべき

Page 58

アクセス不能=privateなども含む

Page 59

この仕様を悪用してこんなこともできる

Page 60

外部から読み込みだけできる

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

Page 61

未定義プロパティに書き込み禁止

<?php

trait ReadOnly {public function __set($name, $value) {throw new OutOfRangeException();}}

class B {use ReadOnly;}

Page 62

メソッドアクセスも乗っ取ろう

Page 63

オーバーロード(ただしPHP用語)

Page 64

出典: PHP: オーバーロード - Manual

https://www.php.net/manual/ja/language.oop5.overloading.php

Copyright © 2001-2019 The PHP Group

Page 65

一般的なプログラミング用語「オーバーロード」とは全然別物なので注意

Page 66

未定義のメソッド呼び出しを捕捉

<?php

class C {public function __call($name, $args){echo __CLASS__."::{$name}()が呼ばれた\n";

}}

Page 67

Proxyパターンを簡単に実現

<?php

/*** @mixin Foo*/class D {private $foo;public function (Foo $class) {$this->foo = $class;}public function __call($name, $args){return $this->foo->$name(...$args);}}

Page 68

リフレクション

Page 69

実行時に情報を取得(一部上書き)できるAPI

Page 70

関数・クラス・オブジェクト・メソッド・プロパティ・パラメータ(引数)・返値の型・クラス定数など

https://www.php.net/manual/ja/book.reflection.php

Page 71

PHPUnitのdataProvider(パラメタライズテスト)とも組み合わせられる

Page 72

未定義のメソッド呼び出しを捕捉

<?php

class C {public function __call($name, $args){echo __CLASS__."::{$name}()が呼ばれた\n";

}}

Page 73

まとめ

Page 74

メタプログラミングの技術を使うと「コードに書いてある通りではない」動きをさせられる

Page 75

代償としてPhpStormなどの支援を受けにくくなることがある

Page 76

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