PHP

【PHP超入門】式・文・構文・言語構造・制御構造について

はじめに

この記事は、変数や関数、if などの書き方を理解している方が対象です。
それらの基本的な説明はありませんので、理解していない方は調べてください。

PHPマニュアルを読んでいると、式・文・構文・言語構造・制御構造という用語が使われています。
それぞれの意味について理解すると、PHPマニュアルを読むときの理解度が段違いです。
今回、下記の流れでまとめました。

第1章 式とは
第2章 文とは
第3章 構文とは
第4章 言語構造とは
第5章 制御構造とは

私自身未熟ですので説明に誤りがあるかもしれません。
誤りがあれば、ご指摘ください ((_ _ (´ω` )ペコ

第1章 式とは

式は英語で expression(エクスプレッション)です。
この式について見ていきましょう。

式については、PHPマニュアルに説明があります。

PHPにおいては、ほとんど全てのものは式で記述されます。 最も簡単で最も正確な式の定義は、"全ての式には値がある。"です。

引用:PHP: 式 - Manual

この説明だけでは少しわかりにくいですね。
別の言い方をすると、値があれば式と呼べるということですね。
また別の言い方をすると、値をもつすべてのものが式であると言えます。
値がないものは式ではありません。
ポイントは値のあり、なしで、値があれば式、値がなければ式ではないと言えます。

$a という変数に 1 という値を代入する記述の場合は、どこが式になるのか見ていきましょう。
値と式はほぼ同じなので、どこに値があるのか探せば、どこが式かわかります。

PHP
$a = 1;

この記述は = という代入演算子を使って $a という変数に 1 という値を代入しています。
この記述には、値が二つあります。
値が二つあるということは、式が二つあるということです。

一つ目は 1 という値です。
これは 1 という値ですので式と言えます。

もう一つ値があります。
代入演算子は、値を代入するだけではなく、代入後の値を返します。
$a = 1 という記述は最終的に代入後の値である 1 を返しています。

本当に $a = 1 という記述自体が 1 という値を返しているのか、実際に確認してみましょう。
まずは問題なく代入されているか確認してみます。
$a という変数に 1 という値を代入して、echoで出力します。

PHP
$a = 1;
echo $a;

画面上に 1 と表示されます。
$a という変数に 1 という値が代入されていることが確認できました。
ここまでは問題ないかと思います。
次は、$a = 1 という記述自体が代入後の値を返しているのか確認してみましょう。

PHP
$b = ($a = 1);
echo $b;

画面上に 1 と表示されました。
()内にある $a = 1 という代入演算子が代入しか行わないなら、$b という変数には何の値も代入されませんが、代入後の値を返しているため、$b という変数に 1 という値が代入されています。
代入演算子は、代入の他に代入後の値を返していることが確認できました。
これが二つ目の値であり、二つ目の式ということです。

ちなみに演算子ではない要素をオペランド(operand)と呼びます。
日本語では被演算子と呼びます。
演算子の左側にあるオペランドを左オペランド、右側にあるオペランドを右オペランドと呼びます。

expression_operand.png

オペランドはPHPマニュアルでも使われている用語ですので、覚えておきましょう。
以降の説明でもオペランドを使用します。

代入演算子は右オペランドの値を左オペランドの変数に代入します。

PHP
$変数名 = ;

別の言い方をすれば、右オペランドには式を書けるということです。

PHP
$変数名 = ;

当たり前ですが、代入する値がなければ、左オペランドに代入することはできません。
値のないものは代入演算子の右オペランドには書けないということです。

私たちが定義した関数は右オペランドに書くことはできるのか、確認してみましょう。
足し算する関数を定義して、$a という変数に代入してみましょう。

PHP
function sum($int1,$int2) {
    return $int1 + $int2;
}

$a = sum(2,3);
echo $a;

表示結果を確認する前に、足し算関数の記述について見ていきます。
まず、足し算するために + という 代数演算子 を使っています。
この代数演算子は、先ほどの代入演算子と同じく、計算した結果の返り値があります。
この返り値について理解していない方は、足し算関数を定義するときに下記のように変数に一度代入してから、return するという考え方をします。

PHP
function sum($int1,$int2) {
    $a = $int1 + $int2;
    return $a;
}

計算した結果の返り値があるため、わざわざ変数に代入する必要はありません。
そのため、下記のように記述しています。

PHP
function sum($int1,$int2) {
    return $int1 + $int2;
}

本題に戻り、右オペランドに関数を記述できるか確認します。
$a = sum(2,3) の記述に問題がなければ、5 と画面に表示されます。

PHP
function sum($int1,$int2) {
    return $int1 + $int2;
}

$a = sum(2,3);
echo $a;

画面上には 5 と表示されます。
問題なく右オペランドに関数を記述できました。
関数を定義するときに return を使って足し算した値を返しているため、返り値があります。
返り値があるということは、式ですから問題なく右オペランドに記述できます。

関数は return を使わずに定義することも可能です。
その場合は、返り値がないということになり、右オペランドに書くとエラーになりそうな気がしますが、確認してみましょう。

PHP
function test() {
}

$a = test();
var_dump($a);

画面上には NULL と表示されました。
PHPマニュアルにも記載があるとおり、return 文を省略すると NULL を返します。

注意:
return を省略した場合は NULL を返します。

引用:PHP: 返り値 - Manual

ユーザー定義関数の場合 return に記述した値、または NULL という値を返します。
返り値があるため、式であると言えます。
そのため、代入演算子の右オペランドに記述できるということです。

先ほどは事前に関数を定義してから、代入するときに関数を呼び出しました。
代入するときに右オペランドに関数を定義して書くこともできるのでしょうか。
確認してみましょう。

代入時に関数を定義して書くこともできるの?

先に結論を述べると、できません。
先ほどのように関数を呼ぶと返り値はありますが、関数を定義しただけでは返り値はないため、下記のように記述するとエラーが表示されます。

PHP(エラー)
$a = function sum($int1,$int2) {
    return $int1 + $int2;
}
echo $a;

ただ、PHP5.3 から無名関数というものが導入されました。
無名関数とは名前の通り、名前のない関数です。
この無名関数は下記のように右オペランドに記述することができます。

PHP(無名関数の記述例)
$a = function ($int1,$int2) {
    return $int1 + $int2;
};

echo $a(2,3);

この記事では、無名関数について詳しく説明はしませんので、気になる方は調べてください。
無名関数と呼ばれるものがあるとだけ覚えておいてください。

次は、文について見ていきましょう。

第2章 文とは

文は英語で statement(ステートメント) です。
文についてもPHPマニュアルに記載があります。

すべての PHP スクリプトは、一連の文からなります。 文としては、代入、関数コール、ループ、条件文、そして何もしない文(空の文) さえ使用することができます。 文は、通常セミコロンで終了します。加えて、文は、中括弧によるグループ文で カプセル化することによりグループ化することが可能です。 グループ文は、同時に文にもなります。 本章では、様々な文の型について説明します。

引用:PHP: 導入 - Manual

簡単に言えば、最後に ; セミコロンがあれば、文になります。
先ほど $a という変数に 1 という値を代入するときに下記のように記述しました。

PHP
$a = 1;

最後に ; があるので文です。
$a = 1 は式ですが、最後に ; をつけて $a = 1; にすると文になります。

statement_diff.png

文は式とは違い、値を持ちません。
そのため、下記のように書くとエラーになります。

PHP(エラー)
$変数名 = ;

最後に ; を付けると文になります。
右オペランドに;を付けて ($a = 1;) という文を代入してみましょう。

PHP(エラー)
$b = ($a = 1;);
echo $b;

Parse error: syntax error, unexpected ';' in ... とエラーが表示されます。
右オペランドに文は書けないということです。

PHPのコードを書くときに、$a = 1;$b = 2; のように最後に ; を付けて記述します。
PHPのコードは文でできていると言えます。

次は構文について見ていきましょう。

第3章 構文とは

構文は英語で syntax(シンタックス)です。
構文の意味を確認しましょう。

構文

自然言語の文法。転じて、プログラミング言語の文法や書式のことで、それらに沿った記述を行わないと、コンパイル時にエラーが発生する。単に文ということもある。

引用:構文(こうぶん)とは - コトバンク

一部わからない用語もあると思いますが、構文とは文法・書式という意味です。
要は書き方ということですね。

条件分岐するときに if を記述しますが、書き方が決まっています。
if は下記のように書きます。

PHP(if構文)
if () {
  
}

これが、if の構文です。
式と文について説明したので、if はどのように書くかわかるかと思いますが、詳しく見ていきましょう。

PHP
$a = 3;
$b = 2;

if($a > $b) {
  echo "aはbより大きい";
}

if() 内には式を記述しますが、今回は $a > $b と記述しています。
>比較演算子 と呼ばれるもので、$a$b の値はどちらが大きいのか比較します。
比較した結果の値を返します。
$a$b より大きいときは、true という値を返し、小さいときは false という値を返します。
返り値の truefalse は、論理型(boolean) と呼ばれるもので、真偽の値を表しています。
真の場合、つまり条件通りの場合は、true で、偽の場合、つまり条件とは異なる場合は、 false という値です。
論理型は、正しいか(true)、正しくないか(false)の2つの値しかありません。
今回の場合は $a の値の方が大きいため、true という値を返します。
if() 内に記述した式の結果が true のときは {} 内に記述したコードが実行されます。
今回の場合なら aはbより大きい と画面表示されます。
ちなみに、今回のように {} 内に文が一つしかない場合は、{} を省略して下記のように記述できます。

PHP
$a = 3;
$b = 2;

if($a > $b)
  echo "aはbより大きい";

省略することも可能ですが、{} を記述した方が読みやすいと思います。

先ほど、比較演算子は比較した結果の値(true または false)を返すと説明しました。
ということは変数に代入できそうですね。
確かめてみましょう。

PHP
$a = 3;
$b = 2;
$c = ($a > $b);
var_dump($c);

var_dump($c)$c に代入されているデータ型と値を画面に出力してくれます。
bool(true) と表示されます。
boolというデータ型で、値は true です。
比較演算子は値を返してくれるため、問題なく右オペランドに記述できました。
PHPには様々な演算子がありますが、値を返してくれる演算子は右オペランドに記述できます。

ここで改めて最初の構文の説明を見てみましょう。

単に文ということもある。

先ほど、文というのは ; があるものと説明しましたが、構文も文と呼ぶことがあるということです。
そのため、 if 構文とも言いますが、if 文と言うこともあります。
$a = 1; のような記述のことを文と呼びますが、構文のことも文と呼びますので、覚えておいてください。

PHPのコードを書くときは、構文と呼ばれるものがあり、書き方が決まっていると説明しました。
if なら if(式) { ... } のように書いたり、文の最後には ; をつけたりと書き方が決まっています。
誤った書き方をしたときは、どのようになるのか確認してみましょう。

PHP
$a = 1
$b = 2

本当は $a = 1; のように最後に ; が必要ですが、書かずに実行してみます。

Parse error: syntax error, unexpected '$b' (T_VARIABLE) in index.php on line 3

エラーが表示されました。
syntax とは先ほど説明した、構文という意味です。
syntax error とは、書き方に問題があるということです。
PHPのコードを書いていると、このようなエラーをよく目にします。
このエラーは誰が教えてくれてるのでしょうか?
私たちが書いたPHPのコードを誰かが解析して誤りがあるかどうか検証して、エラーを表示しているということです。
その役割をしているのが、PHPの核となる部分である Zend Engine(ゼンドエンジン)です。
この Zend Engine 自体は C言語で書かれています。
これらのソースコードは GitHub(php/php-src) から確認できます。
このコードを理解するにはC言語の知識が必要です。
PHPの内部構造について詳しく知りたい方は、C言語を学んでコードを読んでみてください。

この Zend Engine にはバージョンがあり、PHPのバージョンとリンクしています。
PHP7 は Zend Engine 3 で、PHP5 は Zend Engine 2 です。
PHP7 と PHP5 では Zend Engine のバージョンが異なるため、Zend Engine の内部構造も異なります。
自分の環境の Zend Engine のバージョンを調べるには phpinfo または zend_version で確認できます。

PHP
phpinfo();
PHP
echo zend_version();

PHP と Zend Engine のバージョンの関係について知りたい方は下記をご覧ください。

基本的なことですが、この Zend Engine はクライアント側にあるのでしょうか。
それとも、サーバー側にあるのでしょうか。
PHPはサーバーサイドの言語なので、サーバー側で実行されます。
Zend Engine もサーバー側にあります。

zend_server.png

サーバー側にある Zend Engine がPHPのコードを実行して、その結果をクライアントに返しています。
クライアント?サーバー?何それ?という方は、下記をご覧ください。

ローカル環境でPHPを実行されている方は、おそらく XAMPP や MAMP などをインストールしたかと思います。
PHPを実行するには Zend Engine が必要です。
XAMPPや MAMP をインストールすることで、Zend Engine もインストールされ、PHPが実行できるということです。

PHPのコードが Zend Engine 内部でどのように実行されるのか、もう少し詳しく見ていきましょう。
Zend Engine 内部では、PHPのコードを、字句解析、構文解析、コンパイルという過程を経て実行しています。

zend_flow.png

それぞれの過程について簡単に見ていきましょう。
少し難しいので、どのような流れで実行されるのか、全体の流れを何となく理解しましょう。

1.PHPコード

今回は下記のPHPコードがどのように実行されるのか見ていきます。

PHP
$a = 1;
$b = 2;

2.字句解析

字句とはソースコードの中で意味を持つ最小単位のことです。
字句のことをトークンとも呼びます。
PHPのコードをトークンに変換します。
最初のPHPのコードなら下記のようなトークンに変換されます。

トークン
T_OPEN_TAG
T_VARIABLE T_WHITESPACE = T_WHITESPACE T_LNUMBER;
T_VARIABLE T_WHITESPACE = T_WHITESPACE T_LNUMBER;

PHPの開始タグ <?php なら T_OPEN_TAG 、変数なら T_VARIABLE のように、それぞれ決まったトークンがあります。
トークンの一覧は、PHPのマニュアルの パーサトークンの一覧 で確認できます。

字句解析のことをレキサー(lexer)とも呼びます。

3.構文解析

変換したトークンを解析して、書き方に誤りがないか検証します。
書き方に誤りがあるときは、Parse error: ... を表示します。
このエラーは先ほども表示されていました。
下記のコードを実行したときにエラーが表示されました。

PHP
$a = 1
$b = 2
PHP(エラー)
Parse error: syntax error, unexpected '$b' (T_VARIABLE) in index.php on line 3

構文解析の段階で、書き方に誤りないか判断してエラーを表示していたということです。
構文解析では、書き方に誤りがないか検証する他に、解析した結果の抽象構文木(AST)を作成します。
最初のPHPのコードなら、下記のような抽象構文木が作成されます。

zend_ast.png

PHP7(Zend Engine 3)と PHP5(Zend Engine 2)では、バージョンが異なるため Zend Engine の構造も異なります。
抽象構文木は PHP7 から作成するようになりました。
PHP5 では抽象構文木は作成されません。

構文解析のことをパーサー(parser)とも呼びます。

4.コンパイル

ソースコードを解析して、実行可能なプログラムに変換することをコンパイルと呼びます。
先ほど作成した抽象構文木を走査して、オペコードと呼ばれる実行可能なコードに変換します。
最初のPHPコードなら下記のようなオペコードに変換されます。

オペコード
L2  #0   ASSIGN             $a              1 
L3  #1   ASSIGN             $b              2
L3  #2   RETURN             1                   

私たちが書いたPHPのコードは、一度オペコードと呼ばれる実行可能なコードに変換されています。
オペコードの一覧は、PHPマニュアルの Zend Engine 2 オペコード から確認できます。
Zend Engine 3 のオペコード一覧ではありませんが、Zend Engine 2 とほぼ同じかと思います。

5.実行

先ほど変換したオペコードを実行します。
実行は仮想マシン(Zend Virtual Machine)と呼ばれるものが行っています。
実行できないエラーがあれば、Fatal error: ... を表示します。
Fatal error: ... は致命的なエラーで、定義していない関数を呼んだときなどに表示されます。

このように字句解析、構文解析の段階で書き方に誤りがないかなどの確認をして、その後にオペコードと呼ばれるものに変換したあとに実行するという流れでPHPのコードは実行されています。

単純にPHPのコードを上から順に実行しているわけではないので、下記のように後から関数を定義してもエラーは表示されません。

PHP
$a = sum(2,3);
echo $a;

function sum($int1,$int2) {
    return $int1 + $int2;
}

私たちが書いた変数や関数などは、コンパイル時または実行時に処理されます。
静的に確定できる値はコンパイル時に、動的に作られる値は実行時に処理されます。
関数定義はコンパイル時に処理されます。
詳しく知りたい方は下記の記事をご覧ください。

PHPのコードがどのように実行されるのか、一連の流れを見てきましたが、難しくて理解できないところもあったかと思います。
ここでは下記の3つだけ覚えておきましょう。

  • PHPのコードは上から順に実行されているわけではなく、一度オペコードと呼ばれるものに変換されてから実行される
  • Parse error: ... は構文解析の段階で表示される
  • Fatal error: ... は実行の段階で表示される

次は言語構造について見ていきましょう。

第4章 言語構造とは

言語構造は英語で language constructs(ランゲージコンストラクト)です。
言語を構成する要素のことで、ifelsefor などが言語構造になります。
言語構造は、最初から存在しているため、私たちが定義しなくても利用できます。

PHPマニュアルには キーワードのリスト というページがあります。
このリストに記載されいているものは言語構造です。
下記は抜粋です。

言語構造は字句解析の段階でトークンに変換されます。
そのため、言語構造の記述に誤りがあると Parse error: … と表示されます。
PHPマニュアルの パーサトークンの一覧 に掲載されている構文は言語構造です。
先ほどのキーワードのリスト とほぼ同じですが、演算子などが追加されています。

先ほどの抜粋一覧にもありましたが、echoprint は言語構造です。
それぞれのPHPマニュアルを見ると、なぜか関数に分類されていますが、関数ではなく、言語構造です。
echo は値を返さないので、式ではありません。

echo

返り値
値を返しません。

引用:PHP: echo - Manual

print は常に 1 という返り値があるため、式になります。

print

返り値
常に 1 を返します。

引用:PHP: print - Manual

echo は式ではないので、代入演算子の右オペランドには記述できません。

PHP(エラー)
$a = echo 'テストだよ';

Parse error: syntax error, unexpected 'echo' (T_ECHO) in index.php on line 3 とエラーが表示されます。

print は式なので、代入演算子の右オペランドに記述できます。

PHP
$a = print 'テストだよ';

画面上には テストだよ と表示されます。
print の返り値は 1 です。
$a には 1 という値が代入されています。
var_dump($a) で出力してみましょう。

PHP
$a = print 'テストだよ';
var_dump($a);

テストだよint(1) と画面上に表示されます。
$a に関する出力は、int(1) です。
intというデータ型で、値は 1 です。
1 という返り値があるのか確認できました。

言語構造だから返り値がないということではありません。
中には返り値がある言語構造もありますので、PHPマニュアルを確認してください。

次は制御構造について見ていきましょう。

第5章 制御構造とは

PHPのコードを書くときに、if を使って条件を判断して実行したり、for を使って繰り返し実行したり、処理の順番を制御して書いていきます。
これらを制御構造と呼びます。
PHPマニュアルの制御構造 のページに一覧で掲載されています。
下記が制御構造の一覧です。

これらは言語構造でもありますが、制御構造でもあります。

大別すると言語構造と関数の2つがあり、言語構造の中には制御構造があります。
一覧にすると下記のようになります。

figure.png

最後に

今回の記事で超初心者の理解が少しでも深まれば幸いです。
私自身未熟ですので説明に誤りがあるかもしれません。
誤りがあれば、ご指摘ください((_ _ (´ω` )ペコ

PHP関連の記事をまとめていますので、気になるものがあればご覧ください。
超入門というのは徹底入門ではなく、初学者向けに一つ一つ冗長に説明している超入門記事という意味です。

参考サイト

この記事を書くにあたり、参考にしたサイトです。
ありがとうございます。