このスライドは突発で開催された**PHPerKaigi Online(仮)**でしゃべるために用意されたものです。
前回のあらすじ
コードを自在に操るためのPHP文法入門
PHPのソースコードを正確に検査したり、ソースコードの一部を書き換えたいと思ったことはありませんか? PHPにはPHP-Parserという構文解析ライブラリがあり、静的解析ツールのPHPStanやリファクタリングツールのRectorはPHP-Parserをベースにしたプラグインでソースコードを検査したり、ソースコードを書き換えたりすることができます。
しかしながら、構文木を操作するには普段何気なくPHPコードを書く以上のプログラミング言語についての知識が求められます。この発表では構文木を取り扱う前提となるプログラミング言語についての知識、PHP-Parserの構造、PHPStanとRectorそれぞれの拡張方法と実例についても紹介します。
気をとりなおして
こういう記事を先週書きました
echoとprintの違いを要約すると
- 関数マニュアルに載っているが関数ではない
-
echo
とprint
は出力バッファに書き込む -
echo
は文だがprint
は式(式文) -
echo
は複数の引数、print
は1つの引数
echo
は文だがprint
は式(式文)
というわけで、今回は文と式に絞って話をします
クイズ
syntaxエラーにならないのはどちらでしょう。
echo print 1;
print echo 1;
プログラミング言語を習うと初めにこういうことを習う
入門者に叩き込まれる謎の用語
-
if
文で条件分岐をします -
for
文で繰り返します - 代入文で変数に値を入れます
-
return
文で関数から値を返します
たいていの人は、それが何なのか悩む前に使いかたをマスターしてしまうので何も困らない
PHPにはいろんな文と式がある
キーワードのリスト
このリストには文と式がごちゃごちゃに載っている
プログラムの構成要素を分解してみよう
PHPの場合
<?php
function sum(array $values)
{
$sum = 0;
foreach ($values as $v) {
$sum += $v;
}
return $sum;
}
意味をつけていく
<?php // ← PHP開始タグ
function sum(array $values) // ← 関数定義
{
$sum = 0; // 変数代入
foreach ($values as $v) { // foreach
$sum += $v; // 変数代入
}
return $sum; // 戻り値
}
文と式を分けていく
<?php // ← PHP開始タグ(特殊)
function sum(array $values) // ← 関数定義「文」
{
$sum = 0; // 変数代入「式」
foreach ($values as $v) { // foreach「文」
$sum += $v; // 変数代入「式」
}
return $sum; // 戻り値「文」
}
PHPには書く場所が限られる要素がある
PHPではこういうコードは書けない
<?php
const A = hoge();
function a($a = foo())
{
return $a;
}
PHPではこういうコードは書けない
何を当たり前のことをという
foreach (function a(){return [];} a() as $b) {
return $b;
}
式(expression, expr)とは何か
式はこんなところに書ける
ここに書けるのは式
-
if
の条件を書くところif ($v == 1)
-
while
の条件を書くところwhile (count($a) !== 0)
- 関数呼び出しの引数
foo($a + 1)
- 代入の右辺
$v = 30 * MINUTES
-
return
に書けるものreturn foo() + bar()
-
echo
,print
,<?= ?>
に渡せるもの
よく使われる式 (その1)
- 関数・メソッド呼び出し
printf()
$obj->foo()
$func()
- 演算子式
- 論理否定
!$foo
- 比較式
$a == $b
$user instanceof User
- 算術演算
1 + 1
- 短絡論理演算子
$n <= 0 && $n < 24
foo() or bar()
- インクリメント・デクリメント
$i++
--$i
- 論理否定
よく使われる式 (その2)
- 無名関数式
function ($a) { return $a * 2; }
fn($a) => $a * 2
- 無名クラス
new class('foo'){ function __construct($name) { $this->name = $name; } }
- 配列リテラル
[$a, $b, 'foo' => foo()]
- 三項演算子
$foo == 'bar' ? 'A' : 'B'
よく使われる式 (その3)
-
()
式のグルーピング -
isset
,empty
eval
-
assert
- PHP5では関数だったがPHP7で言語構造になった
-
exit
die
- 変数・スカラーリテラル・定数・マジカル定数
-
$a
1
"a"
true
HOGE
-
式の特徴
- 式の中に再帰的に式を書ける
こんなものも式
$a = include __DIR__ . '/config.php'
$b = eval('return 1 + 1;')
$c = yield 1;
$d == 1 or die()
$e = foo() and print $e
とてもひどいことに、式の種類によって ()
が省略可能かどうかがバラバラで、個別に覚えないといけない。(または編集中にsyntax checkする)
式はこんなところにも書けるぞ
-
foreach
のas
の左側 foreach (foo() as $f)
-
include
,require
の引数 - 式の中には任意の組み合せの式が書ける
- ただし
[] + 1
のような式はPHP7ではパーサーレベルで怒られるようになった
- ただし
文
よく使われる文
- 関数/メソッド定義文
function
- クラス定義文
class
interface
use
- 制御構文
while
for
do
foreach
- 出力文
echo
<?=
- 定数定義文
const FOO = 1;
- インポート文
use
use function
use const
use ... as
- 式文
$v = BAR ?: buz();
文にはこんなものもある
- キャスト
(int)
(string)
(bool)
- トレイトの追加
use
- 実行ディレクティブ
declare(strict_types=1);
- HTML出力
?><?php
の外側 - 静的変数
static $var
- グローバル変数
global $var
文の分類
文の特徴
- 式は任意の式を組み合わせられる
- 文は文ごとに決まったルールがある
- 式を書けるもの
- 式と文を書けるもの
- それ以外の記述ができるもの
式文 (expression statement)
<?php
require_once ___DIR__ . '/foo.php'; // require式だけの文
$n = 100; // 代入式だけの文
printf("%f", 1.1); // 関数呼び出しだけの文
assert($n < 100); // assert式だけの文
print 1; // print式だけの文
式文 (expression statement)
- 全ての式は
;
または?>
で区切ることで文になる。 - 式を式文(expression statement)と呼ぶ。
- ただし、
;
がつくからと言って式文とは限らない。
- ただし、
- この記事のコード注釈で「式」と書いた場合は、その式を内包する文である
複文
複数の文が書ける。普通はif
やforeach
と組み合わせて使う。
{
foo();
bar();
}
条件文 if
, else
, elseif
この文
には文を一つだけ書けるが、通常は if ($cond) echo 1;
のように {}
を付けずに書くことは好まれず、 if (true) { echo 1; }
のように複文を使って改行して書くことが好まれる。
if (式) 文
if (式) 文 else 文
if (式) 文 elseif (式) 文
if (式) 文 elseif (式) 文 else 文
別構文
if (式): 文 endif;
if (式): 文 else: 文 endif;
if (式): 文 elseif: (式) 文 endif;
if (式): 文 elseif: (式) 文 else 文 endif;
繰り返し while
while (式) 文
別構文
while (式): 文 endwhile;
複雑な繰り返し for
for (最初に実行される式; 継続判定式; 繰り返しごとに実行される式) 文
別構文
for (最初に実行される式; 継続判定式; 繰り返しごとに実行される式): 文 endfor;
文と式文のややこしいケース
初心者殺し
// これは関数定義文なので最後に ; は要らない
function f() { ... }
// これは変数代入式なので最後に ; が必要
$f = function() { ... };
代入式と代入文のややこしいケース
// これは代入式
$a = 1;
// これはstatic変数の宣言文
static $b = 1;
特別な式が書ける文
- 静的に解決できる式だけ書ける (PHP 7)
const
- プロパティ
const SEC = 1;
const MINUTES = 60 * SEC;
const HOUR = 60 * MINUTES;
クイズの解答
echo print 1; // echo は文なので式(print)を引数にとれる
print echo 1; // echo は文なので式(print)の引数に書けない