実用メタプログラミング

公開日:

沖縄県宜野湾市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

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