PHP/
メインページ > 工学 > 情報技術 > プログラミング > PHP PHPは、汎用スクリプト言語ですが、CGI/FastCGIあるいはウェブサーバー組込みモジュールを介してウェブサービスで利用されることか多く、本書でもウェブでの利用を念頭に解説します。
PHPのコードは通常、モジュール、デーモン、あるいはCommon Gateway Interface (CGI) 実行ファイルとして実装されたPHPインタプリターによってウェブサーバー上で処理されます。 ウェブサーバー上では、PHPコードが解釈され実行された結果、生成されたHTMLやバイナリ画像データなど、あらゆる種類のデータがHTTPレスポンスの全体または一部を形成することになります。 様々なウェブテンプレートシステム、ウェブコンテンツ管理システム、およびウェブフレームワークが存在し、これらのシステムを使ってレスポンスの生成を統制し、円滑化することができます。 さらに、PHPはウェブ以外のプログラミングにも応用することができ、スタンドアローンのグラフィカルなアプリケーションやロボットやドローン制御などの組込みシステムでも使用可能です。
PHPは2022年12月現在、唯一サポートされているバージョン PHP 8 が提供されています[1][2][3]。
PHPのプログラミング学習環境
[編集]PHPは、動的なウェブページを構築することを大きく意識したプログラミング言語で、開発用のウェブサーバーを内蔵しています。 そのため、特にApache HTTP Server・nginxやh2oなどでウェブサーバー環境を構築することなく、プログラミングの学習を開始できます。
実際の運用に開発用の内蔵ウェブサーバーを使うことは、パフォーマンスやセキュリティの問題から非推奨ですが、ウェブサーバーとPHP処理系を連携するセットアップ(相応のネットワーク管理とシステム管理者権限とPHPの知識が必要)はハードルを高くしてしまうので、学習の開始には内蔵ウェブサーバーを使うのが良い選択でしょう。
いまの環境にPHPがインストールされているか?
[編集]いまの環境にPHPがインストールされているか確認しましょう。
コマンドラインから
php -v
を実行します
PHP 8.2.12 (cli) (built: Jan 8 2024 06:21:20) (NTS) Copyright (c) The PHP Group Zend Engine v4.2.12, Copyright (c) Zend Technologies with Zend OPcache v8.2.12, Copyright (c), by Zend Technologies
- のように表示されていたらPHPはインストールされています。
- #内蔵ウェブサーバーを使ってみるに進んでください。
もし
-bash: php: command not found
- のようの表示されたら、PHPはインストールされていません。
- 次のような手順でPHPをインストールしてください。
使用するオペレーションシステム
[編集]PHPは多くのUnixとUnix互換OS、とWindowsのようなUnix非互換OSで動作します。
Windows
[編集]公式のWindows向けダウンロードページにビルド済みバイナリーが提供されています。
もしあなたが、Microsoft Visual Studio を使っているのであれば、Visual Studio Marketplace から、PHP Tools for Visual Studio 2022をインストールすることで、Visual Studio に統合された、PHP開発環境を使用できます。
もしあなたが、Visual Studio code(vscode)を使っているのであれば、Visual Studio Marketplace から、PHP Tools for Visual Studio Codeをインストールすることで、vscode に統合された、PHP開発環境を使用できます。
macOS
[編集]PHP は macOS X (10.0.0) から macOS Monterey (12.0.0) までの macOS にバンドルされています。デフォルトのウェブサーバーでPHPを有効にするには、Apacheの設定ファイルhttpd.confの数行をアンコメントする必要がありますが、CGIやCLIはデフォルトで有効になっています(ターミナルプログラムで簡単にアクセスできます)。
macOS Monterey 以降は、標準ではバンドルされていないので、homebrew を使ってインストールするか、ソースコードからビルドします。
brew install php
FreeBSD
[編集]ports/pkg コレクションに lang/php81 lang/php81-extensions があります。
portmaster でビルドしインストール
# portmaster lang/php82 lang/php82-extensions
pkg でビルド済みバイナリーをインストール
# pkg install lang/php82 lang/php82-extensions
GNU/Linux
[編集]多くのGNU/Linuxのディストリビューションでは、PHPが標準でインストールされています。
くわしくは、利用中のディストリビューションとパッケージマネージャーのマニュアルを参照してください。
テキストエディター
[編集]Windows の「メモ帳」(notepad.exe)のようなプレーンテキストの編集できるエディターが必要です。 HTMLの編集ができる環境であれば、そのまま使えます。
内蔵ウェブサーバーを使ってみる
[編集]内蔵ウェブサーバーは、アプリケーションの開発を支援するために設計されています。また、テスト目的や制御された環境で実行されるアプリケーションのデモにも有用です。このサーバーは、フルスペックのウェブサーバーではありません。公共のネットワークで使用することはできません。
内蔵ウェブサーバーの起動
% mkdir -p ~/public_html % cd ~/public_html % php -S localhost:8000 [Sun Dec 4 11:28:00 2022] PHP 8.2.0RC7 Development Server (http://localhost:8000) started
- のように起動します。
localhost:8000
は、ネットワークからアクセスできないホストにローカルなアドレスのポート8000でウェブブラウザーからのアクセスを待ちます。localhost
を0
に変えると到達可能な全てのホストからアクセス可能になります。- 本来のHTTPのポート 80 は、システム管理者権限がないと開けないので、8000に仮にしました。
- ポート8000 が衝突していたら、 8080…8888 のような上のポートに逃げるのが良いでしょう。
終了するには
^C
- CTRLを押しながら C をタイプします。
1行だけのプログラムを入力しサーバーを起動します。
echo '<?php phpinfo() ?>' > phpinfo.php php -S localhost:8000 [Sun Dec 4 11:28:00 2022] PHP 8.2.0RC7 Development Server (http://localhost:8000) started
ここで、http://localhost:8000/phpinfo.php
をウェブブラウザーで開くと、PHPの諸元が表示されます。
ターミナルを見ると
[Sun Dec 4 11:34:22 2022] PHP 8.2.0RC7 Development Server (http://localhost:8000) started [Sun Dec 4 11:34:36 2022] 153.187.15.4:8864 Accepted [Sun Dec 4 11:34:36 2022] 153.187.15.4:7852 Accepted [Sun Dec 4 11:34:36 2022] 153.187.15.4:8864 [200]: GET /phpinfo.php [Sun Dec 4 11:34:36 2022] 153.187.15.4:8864 Closing [Sun Dec 4 11:34:42 2022] 153.187.15.4:7852 Closed without sending a request; it was probably just an unused speculative preconnection [Sun Dec 4 11:34:42 2022] 153.187.15.4:7852 Closing のように内蔵ウェブサーバーとウェブブラウザーの様子が表示されます。
サーバー停止
^C
コマンドラインから phpinfo.php を実行(先頭五行)。
% php phpinfo.php | head -5 phpinfo() PHP Version => 8.2.0RC7 System => FreeBSD localhost 13.1-STABLE FreeBSD 13.1-STABLE #0 6480563d0: Thu Aug 25 19:34:52 JST 2022 root@localhost:/usr/obj/usr/src/amd64.amd64/sys/SV1G amd64 Build Date => Dec 2 2022 13:42:14
おなじコードでも、ウェブサーバーからの実行と、コマンドラインからの実行で出力に違いがありますが、実行そのものはできています。
- まとめ
- 学習のために内蔵ウェブサーバーが使えます
- 内蔵サーバーは、
php -S localhost:8000
で起動し、カレントディレクトリーにあるコンテンツが公開されます。 - 内蔵サーバーは、CTRL-Cで終了します。
コマンドラインからの利用
[編集]PHPは、ウェブサーバーでコンテンツを動的に生成する用途を念頭に開発されてきましたが、コマンドラインから単独でスクリプトを実行することができます。 このため、PHPをインストールするだけで、コマンドラインでPHPを使えるようになります。 サーバー側での開発が目的であれば、この章はとばしても構いませんが、サーバー側での開発でも、サーバーとの連携に問題があるのか、PHP言語単体でも問題が再現するの障害分離の目的だけでも、コマンドラインからの使用を習得する意味はあります。
コマンドラインからのスクリプトの実行
[編集]次のようなスクリプトを用意します。
- example.php
#!/usr/local/bin/php -p <?php declare(strict_types=1); header('Content-Type: text/plain'); echo "Hello World!", PHP_EOL; echo "こんにちは", PHP_EOL;
- コードの解説
- Shebang:
#!/usr/local/bin/php -p
- この行はShebangで、オペレーションシステムにインタープリターの位置を教えます。また -p はHTTPヘッダーを抑止するオプションです。Shebangは、最初の1行になければならず、余計なスペースも行頭に書くこともできません。
- 開始タグ:
<?php
- PHPタグの開始タグ
- declare():
declare(strict_types=1);
- strict な型チェックを指定
- header():
header('Content-Type: text/plain');
- ウェブコンテンツとして実行した時に、(ディフォルトの text/html ではなく)text/plain(プレーンテキスト)として表示。
- この指定は、コマンドライン向けだけでなく、HTMLの流し込みレンダリングが不要な場合にウェブコンテンツ向けのPHPスクリプトでも有用です。
- 同じ要領で、CSV ⇒ text/csv, JSON ⇒ application/json などファイルタイプを明示することができます。
- PHP_EOLは、プラットフォームによっての違いを調整済みの改行文字で、’\n’ とするとプラットフォームによっては正しく改行されません。
- 最後に
?>
がないのは間違いではなく、?>
があると末尾に余計な空白や改行が入るためで、特に require されるモジュールでは、末尾に?>
を書いてはいけません。
- コマンドラインでの操作と表示
% chmod +x example.php % ./example.php Hello World! こんにちは % _
- chmod で、スクリプトに実行属性を与えています。
- カレントディレクトリーにはPATHを通してはいけないので、 ./ で明示的に相対パスでスクリプトを指定し実行します。
- コマンドラインから php スクリプトを実行すると、ウェブページではなく標準出力(この場合は端末の画面)に出力します。
対話シェル
[編集]PHPにも、対話シェル( Interactive shell )とよばれるREPL( Read-Eval-Print Loop )があり、PHPインタープリターに直接コマンドを与え、即座に応答を得ることができます。
対話シェルの起動
[編集]- コマンドラインでの操作と表示
% php -a Interactive shell php > _
php>
が、で対話シェルのプロンプトです。- コマンドラインから、
php --interactive
でも同様に起動できます。
- 対話モードの終了
php > exit % _
quit
やCTRL-D
でも終了できます。
- PHPの文の評価
% php -a Interactive shell php > echo 1+1; 2 php > $a = 2**8; php > echo $a; 256 php > function f($x) { return $x*$x; } php > echo f(9); 81 php > ^D % _
- 多くのREPLと違い、echoを明示しないと評価した値は表示されません。
- 変数が使えます。
- 関数を定義できます。
;
セミコロンが来るまで改行しても文は評価されません。- スクリプトと同じなのですが、行入力中で勝手が違うのでよく
;
を忘れると思います。 - 慌てずに
;⏎
とタイプしましょう。
PHPの基本機能の紹介
[編集]Hello World
[編集]Hello World ハローワールド とは、画面に「Hello World!」と表示するプログラミングの本の最初に書いてあるプログラムです。
PHPのHello Worldプログラムは次のようになります。
- hello1.php
Hello World!
上記のコードをテキストエディタで書いて hello1.php
の名前で保存します。
これを、ウェブサーバー(CGI/MPM/FastCGIなどでPHPのスクリプトが実行可能にセットアップされているとします)にアップロードし、アップロードされたスクリプトをウェブブラウザで開くと
- ウェブブラウザーでの表示
Hello World!
と表示されます。
あるいは、コマンドラインから
- コマンドラインでの操作と表示
% cat hello1.php Hello World! % php hello1.php Hello World! % _
のように実行することができます。
- 「全然PHPのプログラムじゃない!」
と思った方もおられると思いますが、PHPはHTML(などのテキスト)に様々な処理を加えることで機能する「埋込み型言語」なので、地のテキストをそのまま表示するのはPHPの大事な機能です。
2例目
- hello2.php
<?php echo "Hello World!", PHP_EOL; ?>
- 大概の人が思い描く「PHPで書かれたHellow Worldプログラム」はこれだと思います。
- PHPのプログラムは、地のテキストの中に
<?php
(開始タグ) から?>
(終了タグ)の間(PHPタグ)に書きます[4]。 - この例では、
echo "Hello World!"
、がプログラム本体で echo コマンドに文字列リテラル "Hello World!" を渡しています。- echo コマンドは、受け取ったパラメーターを表示するコマンドです。
- PHP_EOLは、改行文字を表す定数で、オペレーションシステムによって異なる改行文字の違いを吸収します。
3例目
- hello3.php
<?= "Hello World!", PHP_EOL; ?>
<?= 値リスト ?>
は<?php echo 値リスト ?>
の短縮形式です。
4例目
- hello4.php
<?php $hour = localtime(null, true)['tm_hour']; ?> <?php if ($hour < 6 or $hour > 17) : ?> こんばんは。 <?php elseif ($hour < 10) : ?> おはようございます。 <?php else : ?> こんにちは。 <?php endif ?>
- 実行結果(0時から6時までと17時から24時まで)
こんばんは。
- 実行結果(6時から10時まで)
おはようございます。
- 実行結果(10時から17時まで)
こんにちは。
- 動的要素がないとPHPを使う意味はないので、時刻から、「おはようございます。」「こんにちは。」「こんばんは。」を切り替えるスクリプトを書きました。
- PHPタグは、1つのプログラムの中で複数回使うことができ、if 文や foreach 文 は複数のブロックに別れて書かれることがあります。
HTMLの生成
[編集]ウェブサーバー(たとえばapacheなど)あるいはコマンドラインから php にスクリプトを与え実行すると、HTMLに埋込まれた <?php
から ?>
のPHPの疑似タグの間のコードがPHPインタープリターに解釈され、地のHTMLに展開されます。
- hello.php
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="utf-8"><html> <title>こんにちは</title> </head> <body> <?php echo "Hello World!" ?><br> <?= "こんにちは" ?><br> </body> </html>
- 実行結果
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="utf-8"><html> <title>こんにちは</title> </head> <body> Hello World!<br> こんにちは<br> </body> </html>
- echo コマンドは、print コマンドと似ていますが、2つ以上のパラメーターを取れるところが違います。
<?= "こんにちは" ?>
は<?php echo "こんにちは" ?>
の短縮構文です。
HTMLにechoで文字列を埋込む
[編集]PHPでは、動的に文字列をHTMLをに埋込むことができます。 埋込まれたPHPのコードがどこに展開されるか見てみましょう。
- hello2.php
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="utf-8"><html> <title>こんにちは こんにちは</title> </head> <body> ここは、phpタグの外です。<br><?php echo "ここからphpタグ内側", "<br>"; echo "Hello World!", "<br>"; echo "こんにちは", "<br>"; ?>phpタグを脱出。 </body> </html>
- 実行結果
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="utf-8"><html> <title>こんにちは こんにちは</title> </head> <body> ここは、phpタグの外です。<br>ここからphpタグ内側<br>Hello World!<br>こんにちは<br>phpタグを脱出。 </body> </html>
- レンダリング結果
ここは、phpタグの外です。
ここからphpタグ内側
Hello World!
こんにちは
phpタグを脱出。
echoとprintの差異
[編集]PHPには、echoとprintと2つの標準出力に表示する機能がありますが、この2つの立ち位置は大きく違います。
echoとprintの差異 echo print 構文上の位置付け 文 演算子 値 返さない 返す matchのアクションに 使えない 使える パラメーター 複数可 1つ 短縮構文 <?php echo "abc"; ?>
⇓<?= "abc"; ?>
N/A
- また、printf() というC由来の「関数」もありますが
<?= sprintf("%03d", 42); ?>
のように、echo(の短縮構文)とsprintf() を組合わせると出力位置の把握が容易になります。
2つの短縮タグ
[編集]PHPタグには、2つの短縮形式があります。
- echo 文の短縮形式
<?php echo "abc"; ?> ⇓ <?= "abc" ?>
- php開始タグの短縮形式
<?php echo "abc"; ?> ⇓ <? echo "abc"; ?>
<?= 文字列式 ?>
[編集]「echo 文の短縮形式」は可読性が高く副作用もないので、積極的に使うべきです。
<? 文 ?>
[編集]これに対し「php開始タグの短縮形式」は
- XML宣言
<?xml version="1.1" encoding="UTF-8"?>
- と構文が衝突します。
- このため
php.ini
でshort_open_tag = Off
として「php開始タグの短縮形式」の使用を禁止することができます。 php.ini
の在処は% php -r 'echo phpinfo();' | grep 'Loaded Configuration File' Loaded Configuration File => /usr/local/etc/php.ini % _
- で確認できます。
シンプルなループ
[編集]PHPは、ただ単純にHTMLに文字列を埋め込めるだけでなく、ループで繰返し構造をHTMLに展開できます。
- loop.php
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="utf-8"> <title>シンプルなループ</title> </head> <body> <ul> <?php foreach(range(1, 10) as $i) { ?> <li><?= $i * $i ?></li> <?php } ?> </ul> </body> </html>
- 実行結果
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="utf-8"> <title>シンプルなループ</title> </head> <body> <ul> <li>1</li> <li>4</li> <li>9</li> <li>16</li> <li>25</li> <li>36</li> <li>49</li> <li>64</li> <li>81</li> <li>100</li> </ul> </body> </html>
- レンダリング結果
- 1
- 4
- 9
- 16
- 25
- 36
- 49
- 64
- 81
- 100
- ループ開始
<?php foreach(range(1, 10) as $i) { ?>
foreach()
は繰返し構文で、range(1, 10)
は 1 から 10 の数列を返します。そしてこれが、変数 $i に順に入り繰返します。- ループ文の開始の
{
が閉じられていないことに注意してください。 - ループの中
<li><?= $i * $i ?></li>
<?php ... ?>
の外の繰返しは、PHPのコードではなくHTMLのコードが丸ごと繰り返されます。- この場合は、I要素が量産されます。
- 量産されるI要素の内容にPHPのコードがあり変数を含む式が書かれています。
<?= 引数列 ?>
は、<?php echo 引数列 ?>
の短縮形です。- echo コマンドは、 ’,’(カンマ)で区切って複数の式を続けて表示することができます。
- これにたいし、print コマンドは、単一の式しか受付けらません。
- ループ終了
<?php } ?>
- 9行目の
{
は、ここで閉じられています。 - foreachの代替記法
<?php foreach(range(1, 10) as $i) : ?> <li><?= $i * $i ?></li> <?php endforeach ; ?>
- foreach の終わりを endforeach と書く代替構文があり、入組んだ構造のときにループの範囲を見失うリスクを下げることができます(: を忘れがち)。
TABLE要素を使った表組みなどの複合的な構造も、このようにHTMLとPHPのコードが出たり入ったりしながら、編み込むようにコード化します。 WordPressのテーマなどで、より複雑なPHPの(DOMとはまた違った)ドキュメント構造のデザインワークを見ることができます。 このように、PHPはテンプレート記述言語としての特徴を強く持っており、類似のものには ePerl や eRuby のようなスクリプト言語のテンプレート記述拡張がありますが、最初からテンプレート記述言語として設計されているスクリプト言語は、PHP以外に普及例は見当たりません。
コメント
[編集]PHPのコメントには3種類あります。
- 例
<?php declare(strict_types=1); // // から行末まではコメントです。 echo "Hello", /* このように範囲でもコメントが書けます。 */ " World", PHP_EOL; # シェルのように # から行末までもコメントです。 ?>
- 実行結果
Hello World
コメントは、プログラムのデータやロジックに影響を与えず、自由な文書をプログラムに含めることができます。 コメントには、なにを書いてもいいのですが、プログラムのロジックをそのまま記すのは、冗長であるうえにコードの変更にコメントが追従せずズレが生じる原因になります。
次のような要素がコメントによく書かれます
- 著作権者
- コピーライト
- ライセンス
- 参照した論文の DOI
- 関数の引数と戻値の型
- 型宣言仕様が追加されて以降は不要?
- 変数や定数の型
- XXX
- 検証が必要
PHPDoc
[編集]PHPDocは、PHPプログラムのドキュメントを自動的に生成するためのドキュメンテーションシステムです。PHPDocを使用することで、クラス、メソッド、関数、および変数の説明を含む網羅的なドキュメンテーションを作成することができます。このドキュメントを使用することで、他のプログラマーがコードを理解しやすくなり、プログラムの開発プロセスが簡単になるとともに、コードの保守性も向上します。CoCalcを使用してPHPDocを作成するには、PHPDocをインストールし、適切なコマンドを実行して、必要なPHPファイルに注釈を追加する必要があります。
構文
[編集]PHPDocは、PHPのドキュメント生成ツールで、コメントを用いて関数や変数の説明を記述するための構文です。 以下は、一般的なPHPDocの構文の例です。
/** * 関数の説明 * * @param タイプ $引数名 引数の説明 * @return タイプ 戻り値の説明 */ function 関数名($引数名) { // 関数の処理 }
上記の例では、以下のような要素が含まれています。
/**
と*/
で囲まれたコメントがPHPDocの開始と終了を表します。*
の後に、関数の説明文が続きます。@param
は、関数の引数の説明を示すタグです。第一引数には、引数の型、第二引数には、引数の名前、第三引数には、引数の説明を記述します。@return
は、関数の戻り値の説明を示すタグです。引数には、戻り値の型と説明を記述します。
例
[編集]- 1から100までの素数を表示
<?php /** * Displays all prime numbers between 1 and 100. * * @return array Array of prime numbers between 1 and 100 */ function getPrimes() { $primes = array(); for ($i = 2; $i <= 100; $i++) { // 2 is the smallest prime number $isPrime = true; for ($j = 2; $j < $i; $j++) { if ($i % $j == 0) { // if there is any number other than 1 and itself $isPrime = false; // then it is not a prime number break; } } if ($isPrime) { $primes[] = $i; // add the found prime number } } return $primes; } print_r(getPrimes());
使用されたアルゴリズムは比較的単純で、2から100までの各整数が素数かどうかを一つずつ調べる方法です。 このアルゴリズムは O(n2) の時間複雑度を持ちますが、入力範囲が固定された小規模な課題ではうまく機能します。 また、このコードには PHPdoc 形式のドキュメンテーションが含まれており、getPrimes
関数の動作について記述されています。
- 複素数クラス
/** * 複素数クラス */ class ComplexNumber { /** @var float 実数部 */ private $real; /** @var float 虚数部 */ private $imaginary; /** * コンストラクタ * @param float $real 実数部 * @param float $imaginary 虚数部 */ public function __construct($real, $imaginary) { $this->real = $real; $this->imaginary = $imaginary; } /** * 実数部を取得する * @return float 実数部 */ public function getReal() { return $this->real; } /** * 虚数部を取得する * @return float 虚数部 */ public function getImaginary() { return $this->imaginary; } /** * 複素数の加算を行う * @param ComplexNumber $number 加算する複素数 * @return ComplexNumber 計算結果の複素数 */ public function add($number) { $real = $this->real + $number->getReal(); $imaginary = $this->imaginary + $number->getImaginary(); return new ComplexNumber($real, $imaginary); } /** * 複素数の減算を行う * @param ComplexNumber $number 減算する複素数 * @return ComplexNumber 計算結果の複素数 */ public function subtract($number) { $real = $this->real - $number->getReal(); $imaginary = $this->imaginary - $number->getImaginary(); return new ComplexNumber($real, $imaginary); } /** * 複素数の乗算を行う * @param ComplexNumber $number 乗算する複素数 * @return ComplexNumber 計算結果の複素数 */ public function multiply($number) { $real = $this->real * $number->getReal() - $this->imaginary * $number->getImaginary(); $imaginary = $this->real * $number->getImaginary() + $this->imaginary * $number->getReal(); return new ComplexNumber($real, $imaginary); } /** * 複素数の除算を行う * @param ComplexNumber $number 除算する複素数 * @return ComplexNumber 計算結果の複素数 */ public function divide($number) { $denominator = pow($number->getReal(), 2) + pow($number->getImaginary(), 2); $real = ($this->real * $number->getReal() + $this->imaginary * $number->getImaginary()) / $denominator; $imaginary = ($this->imaginary * $number->getReal() - $this->real * $number->getImaginary()) / $denominator; return new ComplexNumber($real, $imaginary); } /** * 複素数の絶対値を取得する * @return float 絶対値 */ public function getAbsoluteValue() { return hypot($this->real, $this->imaginary); } }
上記のように、各メソッドに対してPHPdocを記述することで、ドキュメンテーションを行うことができます。
脚註
[編集]- ^ PHP7.4系列は、2022/11/28にEnd of lifeを迎えました。
- ^ Unsupported Branches
- ^ Supported Versions(2022-12-02)
- ^ PHPタグ
変数と式と演算子
[編集]変数
[編集]変数は、値に名前をつけてプログラム中から参照できるようにする仕組みです。 変数は必ず名前を持ち、名前は識別子のルールに従います。
変数の名前と識別子のルール
[編集]変数の名前は、$ で始まり識別子が続きます。
識別子は、先頭が _ または英字、それに 0 文字以上の _ または 英数字が続きます。
- 有効な変数
- $kk3k
- $_kkk54
- 無効な変数
- $89aa
- $7_loop
また、PHPでは、変数名の大文字小文字を区別します。
$name $NAME $Name $namE $nAME
- はすべて違う変数です。
関数、ラベル、定数、クラス、トレイト、インターフェース、名前空間なども名前を持つことができますが、それぞれ識別子の規則に従います。
変数を使う
[編集]変数を使ったプログラム。
- 例
<?php $a = 99; echo $a, PHP_EOL; ?>
- 実行結果
99
- 上記コードは変数 $a に値 99を代入し、それをecho文によって表示するプログラムです。
- このように変数は、文字列を入れることもできます。
- PHPでは、変数は宣言する必要はありません(できません)。強いて言うなら、最初の代入が変数のスコープのはじまりという意味で宣言に相当します。
- 引用符の中での変数の埋込み
ほかの例も下記に見ていきましょう。
- 例
<?php $a = "こんにちは"; echo "挨拶 : {$a}", PHP_EOL; ?>
- 実行結果
挨拶 : こんにちは
- このように、引用符の中に変数を埋め込むことができます。
- $aを{}で囲んでいるのは、分かち書きされていないと変数名を正しくparseできないからです。
文字列への変数の埋込み
[編集]- 例
<?php $a = 7; >> <?= $a, " is answer"; ?>
- 実行結果
7 is answer
これは、変数の文字列への埋込み機能を使うと次のようにも書けます。
- 例
<?php $a = 7; >> <?= "$a is answer"; ?>
- また、
$aが答えです。
では、どこまでが変数かわからないので {$a}が答えです。
と{ }
(波括弧)で囲います。
echo, print, printfとsprintf
[編集]print は上位互換で短縮表記もある echo があるので殆ど使われませんが、Cのprintf()/sprintf()と同じ機能の、 printf()/sprintf()がPHPにはあり、書式化出力に便利です。
- printf()
<?php $a = 7; printf("%d is answer", $a); ?>
- sprintf()
<?= sprintf("%d is answer", 7); ?>
- 実行結果
7 is answer
- sprintf()は、標準出力に書き出すのではなく、文字列を返します。
- Cのsprintf()と違いバッファの管理は処理系が行ってくれるので、バッファオーバーランを気にしなくても良いのがメリットです。
演算子
[編集]PHPでは他のプログラミング言語同様、数学風の演算子式で計算を行います。
- 例
<?php header('Content-Type: text/plain'); $x = 11; $y = 7; echo "$x + $y --> ", $x + $y, PHP_EOL; echo "$x - $y --> ", $x - $y, PHP_EOL; echo "$x * $y --> ", $x * $y, PHP_EOL; echo "$x / $y --> ", $x / $y, PHP_EOL; echo "intdiv($x, $y) --> ", intdiv($x, $y), PHP_EOL; echo "$x % $y --> ", $x % $y, PHP_EOL; ?>
- 実行結果
11 + 7 --> 18 11 - 7 --> 4 11 * 7 --> 77 11 / 7 --> 1.5714285714286 intdiv(11, 7) --> 1 11 % 7 --> 4
- PHPの
/
(除算)の結果は浮動小数点数です。 - 整数の範囲で割り算をしたい場合には、PHP7以降では
intdiv()
関数を使うことで実現できます。 - PHP7以前でも、小数点斬り捨ての関数 floor()などで、割り算の整数値を求められます。
- 負数の丸め方向には特に注意が必要です。
- 例
<?= 11 / 7; ?>
- 実行結果
1.5714285714286
- という浮動小数点数です。
整数除算は、intdiv(被除数, 除数) です
- intdiv()
<?= intdiv(70,12); ?>
- 実行結果
5
- まとめ
+
,-
,*
,/
,%
のような演算を表す記号を演算子とよびます(これらは二項演算子(中置演算子とも))。- それに対し、
intdiv()
のような形式は関数と呼びます。- とはいうものの、PHPは、演算子・関数・コマンドの区別が曖昧で、公式のドキュメントでも混用が見られます。
- なにか型ジャグリングと通じるものがありますね。
演算子の優先順位と結合方向
[編集]演算子の優先順位
[編集]PHPも、多くのプログラミング言語と同じく演算子に優先順位があります。
1 + 2 * 3
は
( 1 + 2 ) * 3
ではなく
1 + ( 2 * 3 )
の順で評価されます(それぞれの演算子の優先順位については、後ほど表にまとめます)。
演算子の結合方向
[編集]異なる優先順位の演算子であれば、優先順位に従い評価すればよいのですが、同じ優先順位の演算子が続いた場合はどうでしょう。
1 + 2 + 3
…これは交換則が成り立つので良い例ではないですね。
1 - 2 - 3
は
1 - ( 2 - 3 )
ではなく
( 1 - 2 ) - 3
の順で評価されます。このような評価順序の演算子を左結合の演算子と呼びます。
演算子は概ね左結合ですが、少数の例外があります。
$a = $b = 42;
これ(代入演算子)は、
$a = ( $b = 42 );
の順(右結合)で評価されます。
また、
2 ** 3 ** 4
(冪乗演算子)も
2 ** ( 3 ** 4 )
の順(右結合)で評価されます。
演算子の優先順位表
[編集]演算子の優先順位 結合方向 演算子 備考 (非) clone
new
clone と new 右 **
算術 (非) +
-
++
--
~
(int)
(float)
(string)
(array)
(object)
(bool)
@
算術 (単項 +
と-
), インクリメント/デクリメント, ビット演算, キャストとエラー制御左 instanceof
型 (非) !
論理 左 *
/
%
算術 左 +
-
.
算術 (binary +
と-
), array と string (.
はPHP 8.0.0以降)左 <<
>>
ビット演算 左 .
string (PHP 8.0.0以前) (無) <
<=
>
>=
比較演算子 (無) ==
!=
===
!==
<>
<=>
比較演算子 左 &
ビット演算 と リファレンス 左 ^
ビット演算 左 |
ビット演算 左 &&
論理積 左 ||
論理和 右 ??
null 合体演算子 (無) ? :
条件演算 (PHP 8.0.0より前は左結合) 右 =
+=
-=
*=
**=
/=
.=
%=
&=
|=
^=
<<=
>>=
??=
代入 (非) yield from
yield from (非) yield
yield (非) print
print 左 and
論理積 左 xor
排他的論理和 左 or
論理和
リテラル
[編集]リテラルとは、ハードコードされた値のことです。リテラルはスクリプトの中で特定の値を表現する手段となります。例えば代入の右辺には次のようなものがあります。
- 文字リテラルの例
<?php header('Content-Type: text/plain'); $myLiteral = "a fixed value"; var_dump($myLiteral); ?>
- 実行結果
string(13) "a fixed value"
- リテラルにはいくつかの種類があります。最も一般的なのは文字列リテラルですが、他にも整数や浮動小数点のリテラル、論理値のリテラル、配列や連想配列のリテラルなどがあります。
- 連想配列リテラルの例
<?php header('Content-Type: text/plain'); $myHash = [ 'name' => 'value', 'anotherName' => 'anotherValue', ]; var_export($myHash); ?>
- 実行結果
array ( 'name' => 'value', 'anotherName' => 'anotherValue', )
型と型名とリテラル
[編集]PHPの型と型名とリテラルの例を示します。
PHPの型と型名とリテラル 種類 型 型名 リテラル 説明 null null <?php null; ?>
- nullは値がない状態を示し、$a = null; のとき、isset($a) は false となります。
null | 型
の短縮表記は?型
です。- リテラルでは、大文字と小文字を区別しません。
論理型 bool <?php false, true; ?>
- 論理型は、falseとtrueしか取りえない enum のように振る舞います。
- リテラルでは、大文字と小文字を区別しません。
- 歴史的な理由で、boolean と表現される場合があります。
数値 整数 int <?php 123, // 10進数 0123, // 8進数 0o123, // 8進数 0xbadbeef, // 16進数 0b01011100, // 2進数 1_234_567; // _ で区切られた10進数 ?>
- 整数型は、10進数・8進数・16進数・2進数のいずれかです。
- 読みやすさのために、_(アンダースコアー)を挿入できます。
- 先頭と末尾には挿入できません。
- 2つ以上続けて _ を挿入することもできません。
- 歴史的な理由で、integer と表現される場合があります。
浮動小数点数 float <?php 1.23, // 小数表現 1.1234e-4, // 指数表現 1E99, // 指数表現 2.345_670_898e4, // _ で区切られた指数表現 Inf, // 無限大 -Inf; // 負の無限大 NAN; // 非数 ?>
- 浮動小数点数型は、ISO/IEC/IEEE 60559:2011のPHP実装です。
- 歴史的な理由で、double と表現される場合があります。
文字列 string <?php $a = "XYZ" 'abc def $a', // ⇒ ’abc def $a’ "abc def $a"; // ⇒ ’abc def XYZ’ $b = <<<EOS 123 abc \r\n EOS; $c = <<<'EOS' 123 abc \r\n EOS; ?>
- 文字列型は一連の文字です。
- 1 文字は 1 バイトと同じです。
- つまり、PHP は 256 文字の文字集合しかサポートいません。
- ネイティブの Unicode サポートはありません。
- つまり、PHP は 256 文字の文字集合しかサポートいません。
配列 array <?php $ary = [1,2,3]; // ブラケット表現 $ary = array(1,2,3); // array()表現 ?>
- 配列型は、任意の型の値を順序付けて保持します。
- 配列型は、連想配列型のキーを整数に限定した特殊なケースで、型名も同じ array となります。
連想配列 array <?php # ブラケット表現 $ary = [ "a" => 1, "b" => 2, "c" => 3, ]; # array()表現 $ary = array( "a" => 1, "b" => 2, "c" => 3, ); ?>
- 連想配列型は、整数または文字列のキーと任意の値のペアを順序なく保持します。
- キーの重複は許されません。
リソース resource (なし) - リソースは、データベースへの接続やファイルハンドルなど、外界のリソースと結びついたインスタンスの型で、delegate の一種と考えられます。
- 例
<?php header("Content-Type: text/plain"); $fh = fopen("/etc/hosts", "r"); echo get_debug_type($fh), PHP_EOL; fclose($fh); echo get_debug_type($fh), PHP_EOL;
- 実行結果
resource (stream) resource (closed)
オブジェクト object (なし) - オブジェクトは、クラスのインスタンスです。
- クラスを型と考えることもできます。
- 例
<?php header("Content-Type: text/plain"); echo get_debug_type(new stdClass), PHP_EOL; echo get_debug_type(new class {}), PHP_EOL;
- 実行結果
stdClass class@anonymous
定数
[編集]PHPには、$で始まる変数とは別に、イミュータブルなオブジェクト=定数を宣言することができます。
- 例
<?php const a = 42; echo "a = ", a , PHP_EOL; ?>
- 実行結果
a = 42
- 定数は変数と違い $ を前置しません。
- 定数への代入(エラーになります)
<?php const a = 99; echo "a = ", a , PHP_EOL; a = 10; ?>
- エラー
PHP Parse error: syntax error, unexpected token "=" in /workspace/Main.php on line 6
- このように、定数への代入はパース時に構文エラーになります。
- また変数と定数は名前空間が異なるので、同じ名前の変数と定数があっても問題ありません。
define()
[編集]組込み関数 define() を使っても定義できますが
- キーワードも定数として定義できてしまう
- 大文字小文字を区別しない定数が定義できてしまう
- PHP 8.0.0 以降では無効。この機能を使っていたプログラムはPHP 8.0.0 以降では動きません。
- const 宣言と機能が重複する
というバグ級の仕様上の問題(8.0.0で一部是正)があります。
const が使えるのであれば、define()は使うべきではありません。
- あえて define() を使うべきケース
-
- クラスのインスタンスを定数にしたい場合
- const では、クラスのインスタンスを定数にできません(8.1.13時点で、コンパイル時に確定している値しか定数にできません)。
- 例えば、複素数型の定数として虚数単位 i を定義したい場合困ります。
function i() { return new Complex(0, 1); }
でも良い気がしますが、毎回インスタンスが新たに作られるので、===
が思ったように機能しません。
readonlyプロパティ
[編集]const と似た仕組みに、PHP 8.1.0 以降のクラスのプロパティのreadonly修飾子があります。 ただし、readonly修飾子はプロパティにしか使えません。
また、クラスはクラススコープの定数「クラス定数」を定義することができます。
マジカル定数
[編集]PHPには9つのマジカル定数( Magic constants )があり、それらは使用される場所によって変化します[1]。 たとえば、__LINE__ の値は、スクリプトのどの行で使用されるかに依存します。 これらの「魔法の」定数は、実行時に解決される通常の定数とは異なり、すべてコンパイル時に解決されます。 これらの特殊な定数は大文字と小文字を区別しません。
PHP のマジカル定数 名称 説明 __LINE__ ファイル中の現在の行番号 __FILE__ シンボリックリンクを解決したファイルのフルパスとファイル名。インクルード内で使用された場合、インクルードファイルの名前が返されます。 __DIR__ ファイルが存在するディレクトリ。インクルード内で使用された場合、インクルードファイルのディレクトリが返されます。これは、dirname(__FILE__)と同等です。このディレクトリ名は、ルートディレクトリでない限り、末尾のスラッシュを持ちません。 __FUNCTION__ 関数名。匿名関数の場合は {closure}
。__CLASS__ クラス名。クラス名には、宣言された名前空間が含まれます (例: Foo\Bar)。trait メソッドで使用される場合、__CLASS__ は trait が使用されるクラスの名前になります。 __TRAIT__ トライト名。トレイト名には宣言された名前空間が含まれます (例: Foo\Bar)。 __METHOD__ クラスメソッド名。 __NAMESPACE__ 現在のネームスペース名。 クラス::class 完全修飾クラス名。
- 例
<?php declare(strict_types=1); namespace MyNamespace { header('Content-Type: text/plain'); trait MyTrait { public function myTraitFunction(): void { echo "--- In trait method call ---", PHP_EOL; echo "__DIR__ = ", __DIR__, PHP_EOL; echo "__FILE__ = ", __FILE__, PHP_EOL; echo "__LINE__ = ", __LINE__, PHP_EOL; echo "__FUNCTION__ = ", __FUNCTION__, PHP_EOL; echo "__METHOD__ = ", __METHOD__, PHP_EOL; echo "__CLASS__ = ", __CLASS__, PHP_EOL; echo "__TRAIT__ = ", __TRAIT__, PHP_EOL; echo "__NAMESPACE__ = ", __NAMESPACE__, PHP_EOL; echo "MyClass::class = ", MyClass::class, PHP_EOL, PHP_EOL; } } class MyClass { use MyTrait; public function __construct() { echo "--- In construcror ---", PHP_EOL; echo "__DIR__ = ", __DIR__, PHP_EOL; echo "__FILE__ = ", __FILE__, PHP_EOL; echo "__LINE__ = ", __LINE__, PHP_EOL; echo "__FUNCTION__ = ", __FUNCTION__, PHP_EOL; echo "__METHOD__ = ", __METHOD__, PHP_EOL; echo "__CLASS__ = ", __CLASS__, PHP_EOL; echo "__TRAIT__ = ", __TRAIT__, PHP_EOL; echo "__NAMESPACE__ = ", __NAMESPACE__, PHP_EOL; echo "MyClass::class = ", MyClass::class, PHP_EOL, PHP_EOL; } public function myFunction(): void { echo "--- In method call ---", PHP_EOL; echo "__DIR__ = ", __DIR__, PHP_EOL; echo "__FILE__ = ", __FILE__, PHP_EOL; echo "__LINE__ = ", __LINE__, PHP_EOL; echo "__FUNCTION__ = ", __FUNCTION__, PHP_EOL; echo "__METHOD__ = ", __METHOD__, PHP_EOL; echo "__CLASS__ = ", __CLASS__, PHP_EOL; echo "__TRAIT__ = ", __TRAIT__, PHP_EOL; echo "__NAMESPACE__ = ", __NAMESPACE__, PHP_EOL; echo "MyClass::class = ", MyClass::class, PHP_EOL, PHP_EOL; } } $obj = new MyClass(); $obj->myTraitFunction(); $obj->myFunction(); $sub = function() { echo "--- In anonymous function call ---", PHP_EOL; echo "__DIR__ = ", __DIR__, PHP_EOL; echo "__FILE__ = ", __FILE__, PHP_EOL; echo "__LINE__ = ", __LINE__, PHP_EOL; echo "__FUNCTION__ = ", __FUNCTION__, PHP_EOL; echo "__METHOD__ = ", __METHOD__, PHP_EOL; echo "__CLASS__ = ", __CLASS__, PHP_EOL; echo "__TRAIT__ = ", __TRAIT__, PHP_EOL; echo "__NAMESPACE__ = ", __NAMESPACE__, PHP_EOL; echo "MyClass::class = ", MyClass::class, PHP_EOL, PHP_EOL; }; $sub(); echo "--- In namespace top level ---", PHP_EOL; echo "__DIR__ = ", __DIR__, PHP_EOL; echo "__FILE__ = ", __FILE__, PHP_EOL; echo "__LINE__ = ", __LINE__, PHP_EOL; echo "__FUNCTION__ = ", __FUNCTION__, PHP_EOL; echo "__METHOD__ = ", __METHOD__, PHP_EOL; echo "__CLASS__ = ", __CLASS__, PHP_EOL; echo "__TRAIT__ = ", __TRAIT__, PHP_EOL; echo "__NAMESPACE__ = ", __NAMESPACE__, PHP_EOL; echo "MyClass::class = ", MyClass::class, PHP_EOL, PHP_EOL; }
- 実行結果
--- In construcror --- __DIR__ = /workspace __FILE__ = /workspace/Main.php __LINE__ = 25 __FUNCTION__ = __construct __METHOD__ = MyNamespace\MyClass::__construct __CLASS__ = MyNamespace\MyClass __TRAIT__ = __NAMESPACE__ = MyNamespace MyClass::class = MyNamespace\MyClass --- In trait method call --- __DIR__ = /workspace __FILE__ = /workspace/Main.php __LINE__ = 9 __FUNCTION__ = myTraitFunction __METHOD__ = MyNamespace\MyTrait::myTraitFunction __CLASS__ = MyNamespace\MyClass __TRAIT__ = MyNamespace\MyTrait __NAMESPACE__ = MyNamespace MyClass::class = MyNamespace\MyClass --- In method call --- __DIR__ = /workspace __FILE__ = /workspace/Main.php __LINE__ = 38 __FUNCTION__ = myFunction __METHOD__ = MyNamespace\MyClass::myFunction __CLASS__ = MyNamespace\MyClass __TRAIT__ = __NAMESPACE__ = MyNamespace MyClass::class = MyNamespace\MyClass --- In anonymous function call --- __DIR__ = /workspace __FILE__ = /workspace/Main.php __LINE__ = 56 __FUNCTION__ = MyNamespace\{closure} __METHOD__ = MyNamespace\{closure} __CLASS__ = __TRAIT__ = __NAMESPACE__ = MyNamespace MyClass::class = MyNamespace\MyClass --- In namespace top level --- __DIR__ = /workspace __FILE__ = /workspace/Main.php __LINE__ = 69 __FUNCTION__ = __METHOD__ = __CLASS__ = __TRAIT__ = __NAMESPACE__ = MyNamespace MyClass::class = MyNamespace\MyClass
その他
[編集]unset
[編集]今後に使う予定のない変数に unset() 関数を適用することで、明示的にその変数を使わないことを表明できます。 また、unsetした変数に束縛されていたメモリーオブジェクトのリファレンスカウントが一つ減るので、メモリーが開放される可能性があります。
unsetの適用後、変数を参照すると、言語処理系が警告をしてくれるので、バグなどを発見しやすくなります。
ためしに下記コード例では、unsetの適用後、変数を参照しています。
- 例
<?php header('Content-Type: text/plain'); $n = 70; $m = 12; echo $n, PHP_EOL; echo $m, PHP_EOL; unset($m); echo $m, PHP_EOL; echo $x, PHP_EOL; unset($x);
- 実行結果
70 12 PHP Warning: Undefined variable $m in /workspace/Main.php on line 10 PHP Warning: Undefined variable $x in /workspace/Main.php on line 11
- unset() された $m は、存在しない変数 $x と同じ挙動をします。
- unset($x) しても何も起こりません。
PHPでは、ループ変数のスコープはループではなく関数スコープ(あるいはグローバルスコープ)なので、ループを抜けたあと悪さをしないように、unset() しましょう(ループから脱走したリファレンスを使って代入すると発見困難なバグになります)。
ドメインによっては、 $ループ変数 = null; で標準化していることもありますが趣旨は同じです。
null
[編集]null は、変数の束縛を解く時につかわれ、その意味では unset() と似ています。 NULLが入った変数は、isset()はfalseを返します。
- 例
<?php header('Content-Type: text/plain'); $n = 70; $m = 12; echo 'var_export($n, true) --> ', var_export($n, true), PHP_EOL; echo 'var_export($m, true) --> ', var_export($m, true), PHP_EOL; $m = null; echo '$m = null;', PHP_EOL; echo 'var_export($m, true) --> ', var_export($m, true), PHP_EOL; echo 'var_export(isset($m), true) --> ', var_export(isset($m), true), PHP_EOL; echo 'var_export(isset($n), true) --> ', var_export(isset($n), true), PHP_EOL; echo 'var_export($n ?? "no", true) --> ', var_export($n ?? "no", true), PHP_EOL; echo 'var_export($m ?? "no", true) --> ', var_export($m ?? "no", true), PHP_EOL;
- 実行結果
var_export($n, true) --> 70 var_export($m, true) --> 12 $m = null; var_export($m, true) --> NULL var_export(isset($m), true) --> false var_export(isset($n), true) --> true var_export($n ?? "no", true) --> 70 var_export($m ?? "no", true) --> 'no'
- var_export()は与えられた式を、PHPのリテラルの形式にします。
- nullは、大文字小文字を区別しません。
??
は、NULL合体演算子です。式1 ?? 式2
は、isset(式1) ? 式1 : 式2
と同じ意味です。v7.0から導入されました。
未定義変数
[編集]2022年12月現在の最新の安定バージョン v8.1.13 では、未定義(Undefined)の変数参照すると警告( Warning )が出ます。
- 例
<?php echo $w; // 未定義変数の参照 $d = $w; print "hello"; ?>
- 実行結果
PHP Warning: Undefined variable $w in /workspace/Main.php on line 2 PHP Warning: Undefined variable $w in /workspace/Main.php on line 3 hello
- 意図的に未定義変数を参照することはありえません。
- 未定義変数の参照が起こるときは、概ねミススペルが原因です。
- 未定義変数の参照に関する警告を、エラー制御演算子(@)で回避するのは間違いです。未定義となった原因を排除するか、適切な初期値を与えましょう。
エラー制御演算子(@)
[編集]エラー制御演算子(@)は、名前と機能がやや乖離していて、「メッセージ抑制演算子」程度の機能しかありません。 エラー制御演算子には、トラブルシューティングに役立つエラーや警告を表示させないという害悪があるので、他の方法(例えば例外処理)を検討するべきです。
- 例
<?php $body = file ('/noexist') or print __LINE__ . ":" . error_get_last()['message'] . PHP_EOL; $body = @file ('/noexist') or print __LINE__ . ":" . error_get_last()['message'] . PHP_EOL;
- 実行結果
PHP Warning: file(/noexist): Failed to open stream: No such file or directory in /workspace/Main.php on line 2 2:file(/noexist): Failed to open stream: No such file or directory 3:file(/noexist): Failed to open stream: No such file or directory
型と型変換
[編集]型
[編集]PHPの値には「型」があります。
どのような型があるか見てみましょう。
- 例
<?php echo "var_dump(1) --> ", var_dump(1); echo "var_dump(1 == 1) --> ", var_dump(1 == 1); echo "var_dump(1 != 1) --> ", var_dump(1 != 1); echo "var_dump(null) --> ", var_dump(null); echo "var_dump(Null) --> ", var_dump(null); echo "var_dump(2.73) --> ", var_dump(2.73); echo "var_dump(1.0) --> ", var_dump(1.0); echo "var_dump(1+1.0) --> ", var_dump(1+1.0); echo "var_dump(NAN) --> ", var_dump(NAN); echo "var_dump(INF) --> ", var_dump(INF); echo "var_dump('ABC') --> ", var_dump('ABC'); echo "var_dump([1, 2, 3]) --> ", var_dump([1, 2, 3]); echo 'var_dump(["a" => 1, "b" => 2]) --> ', var_dump(["a" => 1, "b" => 2]); function f(){}; echo 'function f(){};', PHP_EOL; echo 'var_dump(f()) --> ', var_dump(f()); ?>
- 実行結果
var_dump(1) --> int(1) var_dump(1 == 1) --> bool(true) var_dump(1 != 1) --> bool(false) var_dump(null) --> NULL var_dump(Null) --> NULL var_dump(2.73) --> float(2.73) var_dump(1.0) --> float(1) var_dump(1+1.0) --> float(2) var_dump(NAN) --> float(NAN) var_dump(INF) --> float(INF) var_dump('ABC') --> string(3) "ABC" var_dump([1, 2, 3]) --> array(3) { [0]=> int(1) [1]=> int(2) [2]=> int(3) } var_dump(["a" => 1, "b" => 2]) --> array(2) { ["a"]=> int(1) ["b"]=> int(2) } function f(){}; var_dump(f()) --> NULL
- var_dump() は、文字列でパラメーターの "型(値)" を返す関数です。
- ここでは扱っていませんが、class, enum, resource も型。
型ジャグリング
[編集]PHP では、変数宣言の構文は持ちません。機能的には変数への始めての代入が初期化を伴った宣言とも考えられますが、v8.1の時点でも変数に型を指定したり、型アノテーションを与える方法はありません。 変数に型がない一方、値は必ず型を持ちます。
演算子式を考えてみましょう。
- 例
<?= "var_dump(-'12') --> ", var_dump(-'12'); ?> <?= "var_dump(-'12' == 12) --> ", var_dump(-'12' == -12); ?> <?= "var_dump(-'12' === 12) --> ", var_dump(-'12' === -12); ?> <?= "var_dump('-12') --> ", var_dump('-12'); ?> <?= "var_dump('-12' == 12) --> ", var_dump('-12' == -12); ?> <?= "var_dump('-12' === 12) --> ", var_dump('-12' === -12); ?>
- 実行結果
var_dump(-'12') --> int(-12) var_dump(-'12' == 12) --> bool(true) var_dump(-'12' === 12) --> bool(true) var_dump('-12') --> string(3) "-12" var_dump('-12' == 12) --> bool(true) var_dump('-12' === 12) --> bool(false)
- string('12')に単項演算子(-)を作用させると、int(-12)
- 文字列から整数に暗黙の変換が行われました
- -'12' は 12 と、==(緩やかな比較)で一致
- -'12' は 12 と、===(厳密な比較)でも一致
- string('-12')
- これは文字列リテラル
- '-12' は 12 と、==(緩やかな比較)で一致
- '-12' は 12 と、===(厳密な比較)では不一致
- CなどのPHP以外のプログラム言語でも暗黙の型変換は行われます(例:整数と浮動小数点数の加算の結果が浮動小数点数になる)。
- このように比較的弱い型付けの言語の中でも、文字列と数値の間で暗黙の型変換が行われるのは、AWK・Perlなどのテキストプロセッシング志向のプログラミング言語に限られます。
- 同じ動的型付けのプログラミング言語でも、PythonやRubyは文字列と数値の間で暗黙の型変換が行われません。
- このような、PHPやPerlの柔軟すぎる暗黙の型変換は、時に柔軟すぎるがゆえにマジカルでトリックフルになりがちなので「型ジャグリング」と揶揄される場合もあります。
このコードの二項演算子バージョンも書けますが、v7とv8で結果が異なる組合せがあったり、PHPの言語設計の弱点の1つに上げらます。
v8.1で関数の引数と戻値の型を指定する構文が追加されたので、状況はある程度改善されたのですが、変数に型を宣言することは、v8.1(及び開発中のv8.2)でもできません。 PHPにおいて、配列は非常に重要なデータ構造の一つです。配列を使うことで、多くのデータを効率的かつ簡単に扱うことができます。PHPにおいては、狭義の配列と連想配列の2種類の配列があります。狭義の配列は、数値添字の配列を表し、連想配列は、文字列添字の配列を表します。この章では、狭義の配列と連想配列の基本的な使い方を学び、配列を使ったプログラムを作成するための基礎を身につけます。
配列
[編集]配列( array )は、数値や文字列など任意の型の値を順番を持って保持するデータ型です。
配列リテラル
[編集]配列リテラル( array literal )は、要素を ,
(カンマ)で区切り全体を [ ]
(角括弧) で囲んで表します。
最後の要素のあとの,
はあってもなくても構いません。
- 例
<?php $a = [1,3.14,"ABC",]; $b = array(1,3.14,"ABC"); echo 'gettype($a) --> ', gettype($a), PHP_EOL; echo 'get_debug_type($a) --> ', get_debug_type($a), PHP_EOL; echo PHP_EOL; var_dump($a == $b); ?>
- 実行結果
gettype($a) --> array get_debug_type($a) --> array bool(true)
- 配列は、スカラーと同じく $a や $b のような変数に束縛できます。
- $ が @ になったりはしません。
[ … ]
はarray( … )
の短縮表記です。- gettype() と get_debug_type() は、オブジェクトの型を文字列で返す変数で、配列に関しては同じ結果を array を返します。
- 例
<?php $wdays = ["月", "火", "水", "木", "金", "土", "日"]; ?> <!DOCTYPE html> <html lang="ja"> <head> <meta charset="utf-8"><html> <title>配列の例</title> </head> <body> <?php for ($i = 0, $len = count($wdays); $i < $len; $i++): ?> <p><?= "[$i]: $wdays[$i]" ?></p> <?php endfor; ?> <pre><?= var_export($wdays,true) ?></pre> </body> </html>
- 実行結果(HTML)
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="utf-8"><html> <title>配列の例</title> </head> <body> <p>[0]: 月</p> <p>[1]: 火</p> <p>[2]: 水</p> <p>[3]: 木</p> <p>[4]: 金</p> <p>[5]: 土</p> <p>[6]: 日</p> <pre>array ( 0 => '月', 1 => '火', 2 => '水', 3 => '木', 4 => '金', 5 => '土', 6 => '日', )</pre> </body> </html>
- レンダリング例
[0]: 月
[1]: 火
[2]: 水
[3]: 木
[4]: 金
[5]: 土
[6]: 日
array ( 0 => '月', 1 => '火', 2 => '水', 3 => '木', 4 => '金', 5 => '土', 6 => '日', )
var_export()
関数は、変数の値を PHPのリテラルの形式で文字列化します、- なぜか => を使った形式ですが、これた配列が連想配列の特殊なケースだからで、詳しくは連想配列で解説します。
- 組込み関数 count は、配列の要素個数を返します。
$ary[] = 42 は array_push($ary, 42)
[編集]$ary[] = 42 は array_push($ary, 42) と同じ意味で、配列変数 $ary の末尾に 42 が追加されます。
- 例
<?php $ary[] = 42; echo var_export($ary,1), PHP_EOL; array_push($ary, 42); echo var_export($ary,1), PHP_EOL; ?>
- 実行結果
array ( 0 => 42, ) array ( 0 => 42, 1 => 42, )
- ただし、配列がない時(からの配列があるのではなく $ary そのものがない時)は、
array_push($ary, 42);
はエラーになり、$ary[] = 42;
は$ary[0] = 42;
と解されます。
疎な配列
[編集]数学で、疎行列(スパース行列)と言うと殆どの要素が 0 の行列ですが、PHPの疎な配列は値のない要素がある配列です。
- 例
<?php $ary = [2,3,5]; echo var_export($ary,1), PHP_EOL; $ary[100] = 123; echo var_export($ary,1), PHP_EOL; echo 'count($ary) --> ', count($ary), PHP_EOL; foreach ($ary as $el) { echo "$el "; } ?>
- 実行結果
array ( 0 => 2, 1 => 3, 2 => 5, ) array ( 0 => 2, 1 => 3, 2 => 5, 100 => 123, ) count($ary) --> 4 2 3 5 123
- 添字3から添字99までは対応する要素がないという状態です。
- これでは、foreachは機能しますが、for(;;)は機能しません。
- 配列の順位を間違えると、疎行列を作ってしまうので配列の末尾に追加したい場合は
$配列変数[] = 値
の形式にするのが良いでしょう。
負のインデックス
[編集]PHPの配列のインデックスに負の整数を使うのは合法です。
- 例
<?php $ary[-12] = 12; echo var_export($ary,1), PHP_EOL; $ary[] = 123; $ary[] = 666; echo var_export($ary,1), PHP_EOL; echo 'count($ary) --> ', count($ary), PHP_EOL; foreach ($ary as $el) { echo "$el "; } ?>
- 実行結果
array ( -12 => 12, ) array ( -12 => 12, -11 => 123, -10 => 666, ) count($ary) --> 3 12 123 666
- 負の順位の値を代入すると、負の順位を持つ要素ができます。
- この状態で、
$配列変数[] = 値
の形式でプッシュすると、最大の順位の次の順位(1つ大きい順位)に要素が作られます。 - 疎配列と合わせて奇異な挙動ですが、一貫性はあります。
多次元配列
[編集]配列の中に配列を入れることができ、これは二次元配列になります。
配列の中に二次元配列を入れることができ、これは三次元配列になります。
配列の中に三次元配列を入れることができ、これは四次元配列になります。
︙
- 例
<?php $ary1 = [0,1]; $ary2 = [[1,0],[0,1]]; $ary3 = [[[1,0],[0,1]],[[0,1],[1,0]]]; var_dump($ary1); echo PHP_EOL; var_dump($ary2); echo PHP_EOL; var_dump($ary3); echo PHP_EOL; ?>
- 実行結果
array(2) { [0]=> int(0) [1]=> int(1) } array(2) { [0]=> array(2) { [0]=> int(1) [1]=> int(0) } [1]=> array(2) { [0]=> int(0) [1]=> int(1) } } array(2) { [0]=> array(2) { [0]=> array(2) { [0]=> int(1) [1]=> int(0) } [1]=> array(2) { [0]=> int(0) [1]=> int(1) } } [1]=> array(2) { [0]=> array(2) { [0]=> int(0) [1]=> int(1) } [1]=> array(2) { [0]=> int(1) [1]=> int(0) } } }
連想配列
[編集]連想配列( associative array )は、キー(整数や文字列など任意のスカラー型値)と値(任意の型)をペアとした集合を保持するデータ型です。 値は重複して構いませんが、キーは1つの連想配列の中ではユニークでなければいけません。
PHPでは、配列と連想配列の区別がありません。配列は整数をキーとする連想配列です。
連想配列リテラル
[編集]連想配列リテラル( associative array literal )は、キー => 値
ペアを ,
(カンマ)で区切り全体を [ ]
(角括弧) で囲んで表します。
最後の要素のあとの,
はあってもなくても構いません。
- 例
<?php $hash = [ "pi" => 3.14159265359, "e" => 2.71828182846, 3.14159265359 => "pi", 2.71828182846 => "e", ]; echo 'gettype($hash) --> ', gettype($hash), PHP_EOL; echo 'get_debug_type($hash) --> ', get_debug_type($hash), PHP_EOL; echo "\$hash('pi') => ", $hash['pi'], PHP_EOL; echo PHP_EOL; foreach ($hash as $key => $value) { echo "\$key($key) => \$value($value)", PHP_EOL; } var_export($hash); ?>
- 実行結果
gettype($hash) --> array get_debug_type($hash) --> array $hash('pi') => 3.14159265359 $key(pi) => $value(3.14159265359) $key(e) => $value(2.71828182846) $key(3) => $value(pi) $key(2) => $value(e) array ( 'pi' => 3.14159265359, 'e' => 2.71828182846, 3 => 'pi', 2 => 'e', )
- 配列は、スカラーと同じく $hash のような変数に束縛できます。
- $ が % になったりはしません。
[ … ]
はarray( … )
の短縮表記です。- gettype() と get_debug_type() は、オブジェクトの型を文字列で返す変数で、連想配列に関しては同じ結果を array を返します。
- キーに数値を与えると、整数に丸められます(エラーにはなりません)。
- 例
<?php $wdaysE2J = [ "Mon" => "月", "Tue" => "火", "Wed" => "水", "Thu" => "木", "Fri" => "金", "Sat" => "土", "Sun" => "日", ]; ?> <!DOCTYPE html> <html lang="ja"> <head> <meta charset="utf-8"><html> <title>連想配列の例</title> </head> <body> <?php foreach ($wdaysE2J as $en => $ja): ?> <p><?= "$en => $ja" ?></p> <?php endforeach; ?> <pre><?= var_export($wdaysE2J, true) ?></pre> </body> </html>
- 実行結果(HTML)
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="utf-8"><html> <title>連想配列の例</title> </head> <body> <p>Mon => 月</p> <p>Tue => 火</p> <p>Wed => 水</p> <p>Thu => 木</p> <p>Fri => 金</p> <p>Sat => 土</p> <p>Sun => 日</p> <pre>array ( 'Mon' => '月', 'Tue' => '火', 'Wed' => '水', 'Thu' => '木', 'Fri' => '金', 'Sat' => '土', 'Sun' => '日', )</pre> </body> </html>
- レンダリング例
Mon => 月
Tue => 火
Wed => 水
Thu => 木
Fri => 金
Sat => 土
Sun => 日
array ( 'Mon' => '月', 'Tue' => '火', 'Wed' => '水', 'Thu' => '木', 'Fri' => '金', 'Sat' => '土', 'Sun' => '日', )
var_export()
関数は、変数の値を PHPのリテラルの形式で文字列化します、
疎な配列と負のインデックスの意味と意義
[編集]配列の項目で疎な配列と負のインデックスについて扱いましたが、配列がキーを整数に限定した特殊なケースであることがわかると、一見奇異な挙動が、連想配列の基本機能からくるものだったとわかります。このように連想配列のインフラを使って配列を実装している処理系に JavaScript があります。
この実装上の選択の理由はいくつかありますが
- 動的言語は、実行時に配列要素が増えることがある(ほぼ確実に)
- PythonのようにListとTupleに分ける方法もありますが、PythonのListは名前に反してHash型の実装です。
- 添字によらず参照時間を一定にしたい
- 線形リストで実装するとインデックスが大きいと応答が遅くなり、アルゴリズムによっては理論値とかけ離れた性能劣化となる。
- AVL木のような方法で平滑化されたアクセス時間を目指すと、メモリーフットプリントの増加が無視できない。
⇒ インデックス(=整数値)をハッシュキーとしハッシュテーブル(連想配列)のインフラを使い実装する
制御構造
[編集]制御構造とは、プログラムの実行順序を変更するための構造のことを指します。PHPには、様々な制御構造が用意されており、プログラムを効率的に制御することができます。 この章では、制御構造の中でも代表的な「条件分岐」と「繰り返し」について解説します。 条件分岐には「if文」「switch文」「match文」があり、繰り返しには「for文」「while文」「do-while文」があります。 それぞれの文法や使い方を理解し、プログラムの制御に活用していきましょう。
条件分岐
[編集]条件分岐は、プログラムにとって非常に重要な要素であり、実行する命令文の流れを切り替えるために使用されます。 条件分岐を使用することで、特定の条件が満たされた場合にのみ、特定の命令文が実行されるようにすることができます。
PHPには、if, switch, match の3つの分岐構文があります。
以下に、if文、switch文、match式の利点と欠点を表にまとめました。
利点 | 欠点 | |
---|---|---|
if文 |
|
|
switch文 |
|
|
match式 |
|
|
それぞれの構文には利点と欠点があり、使用する際にはその目的に応じた最適な構文を選択する必要があります。
if文は、複雑な条件分岐をカバーすることができますが、ネストが深くなりやすいため見づらくなることがあります。
switch文は、複数の分岐を一つの構文で書けるため、見やすいという利点がありますが、比較演算子が単純な場合にしか使えず、フォールスルーに注意が必要です。
match式は、値に基づいて分岐でき、見やすく分かりやすいという利点がありますが、正規表現を使う場合には不向きです。
if
[編集]PHPには、C風のifがあります。else if
の短縮構文があるほか、テキストに埋込むのに適した代替構文があります。
例:
<?php
$n = NAN;
// 基本構文
if ($n < 0) {
echo '$n < 0', PHP_EOL;
} elseif ($n > 0) {
echo '$n > 0', PHP_EOL;
} elseif ($n == 0) {
echo '$n < 0', PHP_EOL;
} else {
echo $n, PHP_EOL;
}
echo PHP_EOL;
// 代替構文
if ($n < 0):
echo '$n < 0', PHP_EOL;
elseif ($n > 0):
echo '$n > 0', PHP_EOL;
elseif ($n == 0):
echo '$n < 0', PHP_EOL;
else:
echo $n, PHP_EOL;
endif;
?>
<?php if ($n < 0): ?>
<?= '$n < 0' ?>
<?php elseif ($n > 0): ?>
<?= '$n < 0' ?>
<?php elseif ($n == 0): ?>
<?= '$n == 0' ?>
<?php else: ?>
<?= $n ?>
<?php endif; ?>
- 基本構文
if ( 条件1 ) 文1 else if ( 条件式2 ) 文2 elseif ( 条件式3 ) 文3 … else 文
else if
はelse if
のシノニムでそれぞれ0回以上繰り返せる。else 文
は省略可能。- 代替構文
if ( 条件式1 ): 文1 elseif ( 条件式2 ): 文2 ︙ else: 文 endif;
- 代替構文では
else if
は使えない - 埋込構文
<?php if ( 条件1 ): ?> テキスト1 <?php elseif ( 条件式2 ): ?> テキスト2 <?php elseif ( 条件式3 ): ?> テキスト3 ︙ <?php elseif ( 条件式n ): ?> テキストn <?php else: ?> テキスト <?php endif; ?>
switch
[編集]PHPには、C風のswitchがあり、フォールスルーの挙動などほとんど同じですが、case の式に定数式以外の式が使える他、文字列などの比較も「緩やかな比較」で行えます。
- 例
<?php $s = "as"; switch ($s) { case "abstract": echo "KW_abstract", PHP_EOL; break; case "and": echo "KW_and", PHP_EOL; break; case "as": echo "KW_as", PHP_EOL; break; case "break": echo "KW_break", PHP_EOL; break; default: echo "Unknown"; } echo PHP_EOL; switch ($s) : case "abstract": echo "KW_abstract", PHP_EOL; break; case "and": echo "KW_and", PHP_EOL; break; case "as": echo "KW_as", PHP_EOL; break; case "break": echo "KW_break", PHP_EOL; break; default: echo "Unknown"; endswitch; ?> <?php switch ($s) : ?> <?php case "abstract":?> <?= "KW_abstract", PHP_EOL;?> <?php break;?> <?php case "and":?> <?= "KW_and", PHP_EOL;?> <?php break;?> <?php case "as":?> <?= "KW_as", PHP_EOL;?> <?php break;?> <?php case "break":?> <?= "KW_break", PHP_EOL;?> <?php break;?> <?php default:?> <?= "Unknown";?> <?php endswitch; ?>
- 実行結果
- C風のswitchで、もし
break
がないと次のcase
以降も実行します。 - 基本構文
switch ( 式 ) { case 式1 : 文1 case 式2 : 文2 ︙ case 式n : 文n default : 文 }
- 代替構文
switch ( 式 ) : case 式1 : 文1 case 式2 : 文2 ︙ case 式n : 文n default : 文 endswitch ;
- 埋込構文
<?php switch ( 式 ) : ?> <?php case 式1 : ?> テキスト1 <?php case 式2 : ?> テキスト2 ︙ <?php case 式n : ?> テキストn <?php default:?> テキスト <?php endswitch; ?>
match
[編集]PHPのmatchは、switchと似ていますが、文ではなく式なので値が取れるほか、比較には「厳密な比較」が使われます。
matchには、代替構文も埋込み構文もありません。
- 例
<?php $s = "as"; echo match ($s) { "abstract" => "KW_abstract", "and" => "KW_and", "as" => "KW_as", default => "Unknown", }, PHP_EOL; ?>
- #switchの例と同じロジックです。簡素にかけていますが、
- 連想配列を使った実装
<?php $s = "as"; echo [ "abstract" => "KW_abstract", "and" => "KW_and", "as" => "KW_as", ][$s] ?? "Unknown", PHP_EOL; ?>
- と書いたほうがより簡素だと考えるひともいるでしょう。
- また、
=>
の右には「式」が要求されるので、echo
のようなコマンドは使えません。 - 厳密でない比較の使用
<?php $age = 18; echo "{$age}歳は", match (true) { $age < 1 => "乳児", $age < 6 => "幼児", $age < 18 => "少年", default => "成人", }, "です。", PHP_EOL; ?>
- こんな事もできます。
- 基本構文
- 代替構文
- (ありません)
- 埋込構文
- (ありません)
分岐処理のネストや複雑な条件式について
[編集]分岐処理を多重にネストすると、可読性が低下し、コードが複雑になります。また、ネストされた分岐により、バグを見つけることがより困難になる場合があります。
そのため、分岐処理をシンプルに保つために、以下の点に注意することが重要です。
- ネストを減らす
- ネストを深くすると、可読性が低下するため、できる限り浅くするように心がけます。
- 分岐処理を多重に行わないようにすることで、コードの複雑性を減らすことができます。
- 論理演算子を活用する
- 複雑な条件式を書く場合は、論理演算子を使うことで、簡潔に書くことができます。
- ガード節を利用する
- 条件に合わない場合は、その時点で処理を中断する「ガード節」を利用することで、コードの見通しをよくすることができます。
- 分岐処理の抽象化
- 分岐処理が複雑になる場合は、別の関数やクラスに分岐処理をまとめることで、コードの可読性を向上させることができます。
以上のようなポイントに注意することで、分岐処理をシンプルに保つことができます。
ガード節
[編集]ガード節は、プログラムにおいて条件式が満たされない場合に、関数の実行を早期に終了させる方法です。通常、条件式が満たされなかった場合には関数内で複数のif文が使われている場合がありますが、ガード節を使うことで、コードを簡潔に保ち、可読性を高めることができます。
例えば、次のような関数があったとします。
function calcPrice($price, $tax) { if (!is_numeric($price)) { return 0; } if (!is_numeric($tax)) { return 0; } $result = $price * (1 + $tax); return $result; }
この関数では、引数が数値でない場合に、0を返しています。しかし、この関数は、引数が数値でない場合はすぐに処理を終了させる必要があります。この場合、ガード節を使うことで、以下のように書き換えることができます。
function calcPrice($price, $tax) { if (!is_numeric($price) || !is_numeric($tax)) { return 0; } $result = $price * (1 + $tax); return $result; }
このように、ガード節を使うことで、コードの可読性を高めることができます。
省略表記や三項演算子による分岐の書き方
[編集]省略表記や三項演算子を使用すると、コードの可読性を向上させることができます。以下は、その書き方の例です。
【省略表記】
- 三項演算子
// 通常のif文 if ($condition) { $result = 'true'; } else { $result = 'false'; } // 三項演算子 $result = ($condition) ? 'true' : 'false';
【null合体演算子】 null合体演算子??
を使用することで、変数のnullチェックを簡潔に書くことができます。
// 通常のif文 if (isset($name)) { $userName = $name; } else { $userName = 'Guest'; } // null合体演算子 $userName = $name ?? 'Guest';
【switch文の省略表記】 switch文の省略表記を使用することで、複数の条件に対して同じ処理を行うことができます。
// 通常のswitch文 switch ($value) { case 1: $result = 'A'; break; case 2: $result = 'B'; break; case 3: case 4: case 5: $result = 'C'; break; default: $result = 'D'; break; } // 省略表記 switch ($value): case 1: $result = 'A'; break; case 2: $result = 'B'; break; case 3: case 4: case 5: $result = 'C'; break; default: $result = 'D'; break; endswitch;
ただし、省略表記や三項演算子を多用しすぎると可読性が低下するため、適切な場面で使用するようにしましょう。また、複雑な条件分岐には適していないため、その場合は通常のif文やswitch文を使用することが望ましいです。
2つの比較演算子
[編集]if や while なでの条件式で、使われる比較演算子には2種類あります。
==
- 型を厳格には区別しない「緩やかな比較」
===
- 型を厳格に区別する「厳密な比較」
なお、=
等号1個は単なる代入命令ですが条件式で使われると代入された値を真理値として評価されます。
緩やかな比較
[編集]==
は、型を厳格には区別しない「緩やかな比較」演算子です。
<?php $a = 1; if ($a == 1) { echo "整数:", PHP_EOL; var_dump($a, 1) ; } if ($a == 1.0) { echo "浮動小数点数:", PHP_EOL; var_dump($a, 1.0) ; } if ($a == "1") { echo "文字列:", PHP_EOL; var_dump($a, "1") ; } ?>
- 実行結果
整数: int(1) int(1) 浮動小数点数: int(1) float(1) 文字列: int(1) string(1) "1"
- すべて一致します。
厳密な比較
[編集]===
は、型まで一致していることを要求する「厳密な比較」演算子です。
<?php $a = 1; if ($a === 1) { echo "整数:", PHP_EOL; var_dump($a, 1) ; } if ($a === 1.0) { echo "浮動小数点数:", PHP_EOL; var_dump($a, 1.0) ; } if ($a === "1") { echo "文字列:", PHP_EOL; var_dump($a, "1") ; } ?>
- 実行結果
整数: int(1) int(1)
- 型の一致は、数値であるだけでは不十分で、整数か浮動小数点数かは区別しています。
値または型の不一致演算子
[編集]!==
は型まで一致していない、あるいは値が一致していないと真をかえす演算子です。
繰り返し
[編集]繰り返しは、同じ処理を複数回繰り返すことができる制御構造の一つです。ループとも呼ばれ、同じ処理を繰り返し実行することで、膨大な量の処理を自動化することができます。繰り返し処理は、プログラミングにおいて非常に重要な役割を果たしており、Webアプリケーションの開発でも頻繁に利用されます。
ここでは、PHPで繰り返し処理を行うための方法を学びます。まずは、基本的な「for文」、「while文」、「do-while文」の使い方を解説します。 次に、より高度な繰り返し処理に必要な概念である「配列」や「連想配列」、そして「foreach文」の使い方についても学びます。
PHPには、while, do-while, for, foreach の4つの繰り返し文と、break, continue の2つの繰り返し制御文があります。
また、goto 演算子はありますが、ループからの大域脱出はbreak, continue の引数(オプション;ディフォルトは 1)でループのレベルを変えるなど、goto が使われがちな状況に代替手段を提供しているので、ほかのプログラミング言語ほどは goto の出番はありません。
加えて、PHPの繰り返し文や複文は、スコープを持ちません。ループを抜けてもループ変数は「生きている」ので、ループ変数は参照可能です。
while
[編集]whileは、まず条件式を評価し、真なら続く文(単文あるいは複文)を実行し条件式を再び評価します。もし、偽ならばループの次に移ります。
- whileの例
<?php $i = 1; while ($i < 100) { echo '$i = ', $i, PHP_EOL; $i *= $i + 1; } echo "Done!($i)", PHP_EOL; echo PHP_EOL; $j = 1; while ($j < 100): echo '$j = ', $j, PHP_EOL; $j *= $j + 1; endwhile; echo "Done!($j)", PHP_EOL; ?> <?php $k = 1;?> <?php while ($k < 100):?> <?= '$j = ', $k, PHP_EOL; ?> <?php $k *= $k + 1;?> <?php endwhile; ?> <?= "Done!($k)", PHP_EOL; ?>
- 実行結果
$i = 1 $i = 2 $i = 6 $i = 42 Done!(1806) $j = 1 $j = 2 $j = 6 $j = 42 Done!(1806) $j = 1 $j = 2 $j = 6 $j = 42 Done!(1806)
- 基本構文
while ( 条件式 ) 文
- while では、条件式を省略できません。
- 代替構文
while ( 条件式 ) : 文 endwhile ;
- 埋込構文
<?php while ( 条件式 ) : ?> テキスト <?php endwhile ; ?>
do-while
[編集]do-whileは、文(単文あるいは複文)を実行し条件式を評価します。真なら文を再び評価します。もし、偽ならばループの次に移ります。
- do-whileの例
<?php $i = 1; do { echo '$i = ', $i, PHP_EOL; $i *= $i + 1; } while ($i < 0); echo "Done!($i)", PHP_EOL; ?>
- 実行結果
$i = 1 Done!(2)
- 基本構文
do 文 while ( 条件式 ) ;
- 代替構文
- (ありません)
- 埋込構文
- (ありません)
for
[編集]forは、C風の3つの項を持つfor文です。
#whileの例と同じロジックを for(;;) で書き直しました(結果は同じなので省略します)。
- forの例
<?php for ($i = 1; $i < 100; $i *= $i + 1) { echo '$i = ', $i, PHP_EOL; } echo "Done!($i)", PHP_EOL; echo PHP_EOL; for ($j = 1; $j < 100; $j *= $j + 1): echo '$j = ', $j, PHP_EOL; endfor; echo "Done!($j)", PHP_EOL; ?> <?php for ($k = 1; $k < 100; $k *= $k + 1): ?> <?= '$k = ', $k, PHP_EOL ?> <?php endfor; ?> <?= "Done!($k)", PHP_EOL ?>
- 基本構文
for ( 初期化文 ; 条件式 : 繰り返し文 ) 文
- 初期化文
- forの最初に一回だけ実行されます。
- 省略可
- 条件式
- 真なら文を実行します。
- 省略されると永久ループになります。
- 繰り返し文
- 文を実行ししたあとに実行されます。
- 省略可
- 文
- 単文あるいは複文あるいは他の制御構造。
- 代替構文
for ( 初期化文 ; 条件式 : 繰り返し文 ) : 文 endfor ;
- 埋込構文
<?php fro ( 初期化文 ; 条件式 : 繰り返し文 ) : ?> テキスト <?php endfor ; ?>
foreach
[編集]foreachは、配列に代表される「繰り返し可能オブジェクト」(iterable object)を繰り返しします。
foreachには、配列版と連想配列版があります。
- froeachの例
<?php $seasons = ["春", "夏", "秋", "冬"]; foreach ($seasons as $season) { echo "基本: $season", PHP_EOL; } echo PHP_EOL; # for(;;) による等価なコード for ($i = 0, $len = count($seasons); $i < $len; $i++) { $season = $seasons[$i]; echo "等価: $season", PHP_EOL; } echo PHP_EOL; # 代替構文 foreach ($seasons as $season): echo "代替: $season", PHP_EOL; endforeach; ?> <?php foreach ($seasons as $season) : ?> <?= "埋込: $season" ?> <?php endforeach; ?> <?php $seasonsMap = [ "春" => "Spring", "夏" => "Summer", "秋" => "Fall / Autumn", "冬" => "Winter", ]; foreach ($seasonsMap as $ja => $en) { echo "基本: $ja --> $en", PHP_EOL; } echo PHP_EOL; # while による等価なコード while ($ja = key($seasonsMap)) { $en = $seasonsMap[$ja]; echo "等価: $ja --> $en", PHP_EOL; next($seasonsMap); } echo PHP_EOL; # 代替構文 foreach ($seasonsMap as $ja => $en): echo "代替: $ja --> $en", PHP_EOL; endforeach; ?> <?php foreach ($seasonsMap as $ja => $en) : ?> <?= "埋込: $ja --> $en" ?> <?php endforeach; ?>
- 実行結果
基本: 春 基本: 夏 基本: 秋 基本: 冬 等価: 春 等価: 夏 等価: 秋 等価: 冬 代替: 春 代替: 夏 代替: 秋 代替: 冬 埋込: 春 埋込: 夏 埋込: 秋 埋込: 冬 基本: 春 --> Spring 基本: 夏 --> Summer 基本: 秋 --> Fall / Autumn 基本: 冬 --> Winter 等価: 春 => Spring 等価: 夏 => Summer 等価: 秋 => Fall / Autumn 等価: 冬 => Winter 代替: 春 --> Spring 代替: 夏 --> Summer 代替: 秋 --> Fall / Autumn 代替: 冬 --> Winter 埋込: 春 --> Spring 埋込: 夏 --> Summer 埋込: 秋 --> Fall / Autumn 埋込: 冬 --> Winter
- 基本構文(配列版)
foreach ( 繰り返し可能オブジェクト as $ループ変数 ) 文
- 代替構文(配列版)
foreach ( 繰り返し可能オブジェクト as $ループ変数 ) : 文 endwhile ;
- 埋込構文(配列版)
<?php foreach ( 繰り返し可能オブジェクト as $ループ変数 ) : ?> テキスト <?php endwhile ; ?>
- 基本構文(連想配列版)
foreach ( 繰り返し可能オブジェクト as $キー変数 => $値変数 ) 文
- 代替構文(連想配列版)
foreach ( 繰り返し可能オブジェクト as $キー変数 => $値変数 ) : 文 endwhile ;
- 埋込構文(連想配列版)
<?php foreach ( 繰り返し可能オブジェクト as $キー変数 => $値変数 ) : ?> テキスト <?php endwhile ; ?>
- key()/next() で「while による等価なコード」を書きましたが、foreach()は内部繰り返し子を変更しません。
- 繰り返し可能オブジェクトは、配列・連想配列のほか、ジェネレター
ループ変数を、参照にすることで配列の要素を変更できます。
- ループ変数を参照にしたforeach()の例
<?php $ary = [1, 2, 3, 4]; var_export($ary); echo PHP_EOL; foreach ($ary as &$value) { $value = $value * 2; } var_export($ary); echo PHP_EOL; $value = 0; var_export($ary); ?>
- 実行結果
array ( 0 => 1, 1 => 2, 2 => 3, 3 => 4, ) array ( 0 => 2, 1 => 4, 2 => 6, 3 => 8, ) array ( 0 => 2, 1 => 4, 2 => 6, 3 => 0, )
このコードでは、まず配列 $ary
を定義しています。その後、foreach
ループを使って配列 $ary
の要素を 2 倍に更新し、変数 $value
に参照をとることで配列の値が更新されるようにしています。つまり、foreach
ループが終了した時点で、配列 $ary
の中身は、すべて 2 倍になっています。
その後、$value
変数に 0 を代入していますが、foreach
ループ内で参照していたのは $ary
配列の要素の参照であり、変数 $value
自体は配列要素への参照ではありません。そのため、$value
に 0 を代入しても、配列 $ary
の最後の要素だけが 0 に更新されることになります。
このように、ループ変数は、foreach()を抜けても参照は生きているので、代入するとループの外からも配列を壊すことができてしまいます。
unset($value)
のように参照関係を明示的に破壊する必要があります。
break
[編集]break文は、ループ内で使用され、ループ処理を中断するために使用されます。break文はif文内に配置されることができます。ループ内でbreak文が実行されると、ループは即座に終了し、次の処理に進みます。
以下は、ループ処理中にbreak文を使用する例です。
<?php for ($i = 1; $i <= 10; $i++) { if ($i == 6) { break; } echo $i . "<br>"; } ?>
この例では、1から10までの数字を表示しますが、6の前に処理が停止します。
continue
[編集]continue文は、ループ内で使用され、現在のループの反復をスキップして、次の反復に進むために使用されます。
以下は、ループ処理中にcontinue文を使用する例です。
<?php for ($i = 1; $i <= 10; $i++) { if ($i == 6) { continue; } echo $i . "<br>"; } ?>
この例では、1から10までの数字を表示しますが、6だけをスキップします。
goto
[編集]goto文は、指定されたラベルにジャンプするために使用されます。goto文は、ループ、スイッチ、または関数の中で使用されます。ただし、goto文を使用することは、プログラムの可読性を損なうことがあり、使用は推奨されません。
以下は、goto文を使用する例です。
<?php goto a; echo 'この部分は表示されません。'; a: echo 'この部分は表示されます。'; ?>
この例では、goto文が使用され、aというラベルにジャンプします。これにより、2番目のecho文が表示されず、3番目のecho文だけが表示されます。
例外処理
[編集]例外処理(れいがいしょり、exception handling)とは、プログラム実行中に予期せぬ異常が発生した場合に、通常の実行フローを中断し、エラーメッセージの表示や適切な処理を行うプログラムの手法です。
PHPでの例外処理は、try
, catch
, throw
, finally
キーワードを使って行われます。例外が発生する可能性のあるコードを try
ブロック内に記述し、それに対する例外処理を catch
ブロックで行います。
以下は基本的な例です:
try { // 例外が発生する可能性のあるコード // 例外を発生させる場合は、throwキーワードを使って例外オブジェクトを投げることができます if ($somethingUnexpected) { throw new Exception("Something unexpected occurred!"); } // 例外が発生しなかった場合の処理 } catch (Exception $e) { // 例外が発生した場合の処理 echo "Caught exception: " . $e->getMessage(); } finally { // finallyブロックは例外の有無に関わらず、最終的なクリーンアップを行うためのブロックです // このブロックは省略可能です echo "Finally block"; }
上記の例では、try
ブロック内で例外が発生する可能性があります。もし例外が発生した場合、catch
ブロックが実行されて例外オブジェクトが $e
としてキャッチされ、その後の処理が実行されます。finally
ブロックは例外の有無にかかわらず、最終的な処理を実行するために使用されます。
ゼロ除算を捕捉するプログラム
[編集]以下はゼロ除算の例外処理を含んだPHPコードです。
- コード例
<?php function divide($numerator, $denominator) { try { if ($denominator === 0) { throw new Exception("Division by zero is not allowed"); } $result = $numerator / $denominator; echo "Result of division: ", $result, PHP_EOL; } catch (Exception $e) { echo "Caught exception: ", $e->getMessage(), PHP_EOL; } finally { echo " Finally block", PHP_EOL; } } // 例外が発生する可能性のある関数を呼び出す divide(10, 2); // 正常なケース:10を2で割る divide(8, 0); // 例外ケース:ゼロ除算 ?>
この例では、divide()
関数を定義し、引数 $numerator
を $denominator
で割る処理を行います。try
ブロック内で $denominator
がゼロの場合に例外を投げます。catch
ブロックでは例外が発生した場合にそのメッセージを表示し、finally
ブロックでは最終的なクリーンアップを行います。
PHPの例外処理の関係クラス
[編集]PHPの例外処理に関連する主なクラスは次のとおりです。
- Exception: これは PHP の標準的な例外クラスです。このクラスを継承してカスタム例外クラスを作成することもできます。
getMessage()
、getCode()
、getFile()
、getLine()
、getTrace()
などのメソッドが利用可能で、これらは例外の詳細情報を取得するために使用されます。 - Error: これは例外ではなくエラーを表すためのクラスです。このクラスも例外クラスを継承しており、エラーの詳細情報を取得するためのメソッドがあります。
カスタム例外を作成することもできます。例えば、特定の種類のエラーやアプリケーション固有の例外を処理するために、以下のようにしてカスタム例外を作成することができます:
class CustomException extends Exception { // カスタム例外クラスの実装 } // 使用例 try { // 何かしらの処理 if ($somethingUnexpected) { throw new CustomException("Something unexpected occurred!"); } } catch (CustomException $e) { echo "Caught custom exception: ", $e->getMessage(), PHP_EOL; } catch (Exception $e) { // 他の例外をキャッチする echo "Caught exception: ", $e->getMessage(), PHP_EOL; }
上記の例では、CustomException
という独自の例外クラスを作成しています。そして try
ブロック内で、何かしらの条件でその例外を発生させ、catch
ブロックでその例外をキャッチしています。
このようにして、標準の Exception
クラスを継承してカスタム例外を作成し、それを使用して特定の種類の例外を処理することができます。
PHPの例外処理のベストプラクティス
[編集]PHPにおける例外処理のベストプラクティスにはいくつかあります。その中でも重要なものをいくつか挙げてみましょう。
- 適切な例外クラスの使用: 標準の
Exception
クラスを適切に使用することは重要です。また、必要に応じてカスタム例外クラスを作成することで、異なる種類のエラーに対処できます。 - エラーメッセージの適切な管理: 例外が発生した際には、適切なエラーメッセージを提供し、デバッグやエラーの追跡を容易にすることが大切です。
getMessage()
メソッドを使って適切な情報を取得し、ログに記録するなどして詳細を把握しやすくします。 - 適切なエラーハンドリング:
try
,catch
,finally
ブロックを使ってエラーをキャッチし、適切なハンドリングを行います。エラーをキャッチした際に適切な対処を行い、コードの安全性を確保します。 - 例外の階層構造の活用: PHPには様々な種類の例外があり、これらを階層的に管理することで、特定の例外に対する処理をより細かく制御できます。
- 適切なロギング: 例外が発生したときには、それをログに記録することが重要です。これにより、エラーが発生した際に問題をより迅速に特定し、解決することができます。
- ユーザーフレンドリーなエラー表示: ユーザーにはわかりやすく、安心感を与えるエラーメッセージを提供することが重要です。しかし、セキュリティ上のリスクを避けるために、エラーの詳細はログなどに記録し、ユーザーにはそれを表示しないようにします。
これらのベストプラクティスを遵守することで、PHPアプリケーションのエラー処理を効果的に行うことができます。
附録
[編集]代替構文
[編集]代替構文は、通常の制御構造の代わりに、より読みやすいコードを書くために使用される構文です。 代替構文は、制御構造の一部に波括弧 { } を含めずに書くことができ、それによってコードをよりシンプルにすることができます。
例えば、次のif文の代替構文は:
if ($a == 1) { echo "a is 1"; }
以下のように書くことができます:
if ($a == 1): echo "a is 1"; endif;
同様に、以下のfor文の代替構文は:
for ($i = 0; $i < 10; $i++) { echo $i; }
以下のように書くことができます:
for ($i = 0; $i < 10; $i++): echo $i; endfor;
代替構文は、コードの可読性を向上させることができますが、適切に使用しない場合はコードの可読性を損なうこともあります。したがって、代替構文を使用する際には、適切に判断することが重要です。
制御構造 | 代替構文 |
---|---|
if (条件式) { 実行文; } | if (条件式): 実行文; endif; |
if (条件式) { 実行文1; } else { 実行文2; } | if (条件式): 実行文1; else: 実行文2; endif; |
for (初期化式; 条件式; 更新式) { 実行文; } | for (初期化式; 条件式; 更新式): 実行文; endfor; |
foreach (配列 as $value) { 実行文; } | foreach (配列 as $value): 実行文; endforeach; |
while (条件式) { 実行文; } | while (条件式): 実行文; endwhile; |
do { 実行文; } while (条件式); | do: 実行文; while (条件式); |
代替構文は、コードの見た目をより読みやすくするために導入された構文です。 代替構文を使用する場合、制御構造の終了を示すためにそれぞれ endif、endfor、endforeach、endwhile を使用します。 しかし、代替構文を使っても制御構造自体の動作には変わりはありません。
チートシート
[編集]// 条件分岐 // if文 if (条件式1) { // 条件式1が true の場合の処理 } elseif (条件式2) { // 条件式1が false で条件式2が true の場合の処理 } else { // 上記の条件式がすべて false の場合の処理 } // switch文 switch (値) { case 値1: // 値が値1と一致する場合の処理 break; case 値2: // 値が値2と一致する場合の処理 break; // ... default: // 値がどの case にも一致しない場合の処理 break; } // match式(PHP 8.0以降) match (値) { 値1 => // 値が値1と一致する場合の処理, 値2 => // 値が値2と一致する場合の処理, // ... default => // 値がどの case にも一致しない場合の処理, } // 繰り返し // for文 for (初期化式; 条件式; 変化式) { // 条件式が true の場合に繰り返す処理 } // while文 while (条件式) { // 条件式が true の場合に繰り返す処理 } // do-while文 do { // 最初に必ず一度実行される処理 // 条件式が true の場合に繰り返す処理 } while (条件式); // foreach文 foreach ($配列 as $キー => $値) { // 配列の要素数分繰り返す処理 } // 制御文 // break文 break; // 現在のループまたはswitch文から抜ける // continue文 continue; // 現在のループの次の繰り返しに移る // goto文 goto ラベル名; // 指定されたラベルにジャンプする // ... ラベル名: // ...
用語集
[編集]- 条件式(Condition):条件分岐やループ処理において、判定に使用する式のことです。真偽値を返す必要があります。
- if文(if statement):条件分岐のための制御構造の一つで、指定された条件式が true の場合に実行するブロックと、 false の場合に実行するブロックを分けて指定します。
- elseif文(elseif statement):複数の条件分岐を実行する場合に使用される制御構造の一つで、直前の if 文の条件式が false で、自身の条件式が true の場合に実行するブロックを指定します。
- else文(else statement):if文の条件式が false の場合に実行するブロックを指定する制御構造の一つです。
- switch文(switch statement):複数の条件分岐を実行する場合に使用される制御構造の一つで、指定された変数の値によって実行するブロックを選択します。
- case文(case statement):switch 文で使用され、値に応じて実行するブロックを選択します。
- default文(default statement):switch 文で使用され、どの case 文にも当てはまらない場合に実行するブロックを指定します。
- while文(while statement):指定された条件式が true の場合に、実行するブロックを繰り返し実行する制御構造の一つです。
- do-while文(do-while statement):一度は必ず実行し、指定された条件式が true の場合に実行するブロックを繰り返し実行する制御構造の一つです。
- for文(for statement):指定された回数分、実行するブロックを繰り返し実行する制御構造の一つです。
- foreach文(foreach statement):配列やオブジェクトのプロパティを1つずつ処理するために使用される制御構造。
- break文(break statement):繰り返し処理を強制的に終了するための制御構造の一つです。
- continue文(continue statement):繰り返し処理の一部をスキップし、次のループに進むための制御構造の一つです。
- goto文(goto statement):指定されたラベルにジャンプするための制御構造の一つです。PHPではほとんど使用されません。
制御構造とは、プログラムの実行順序を変更するための構造のことを指します。PHPには、様々な制御構造が用意されており、プログラムを効率的に制御することができます。 この章では、制御構造の中でも代表的な「条件分岐」と「繰り返し」について解説します。 条件分岐には「if文」「switch文」「match文」があり、繰り返しには「for文」「while文」「do-while文」があります。 それぞれの文法や使い方を理解し、プログラムの制御に活用していきましょう。
条件分岐
[編集]条件分岐は、プログラムにとって非常に重要な要素であり、実行する命令文の流れを切り替えるために使用されます。 条件分岐を使用することで、特定の条件が満たされた場合にのみ、特定の命令文が実行されるようにすることができます。
PHPには、if, switch, match の3つの分岐構文があります。
以下に、if文、switch文、match式の利点と欠点を表にまとめました。
利点 | 欠点 | |
---|---|---|
if文 |
|
|
switch文 |
|
|
match式 |
|
|
それぞれの構文には利点と欠点があり、使用する際にはその目的に応じた最適な構文を選択する必要があります。
if文は、複雑な条件分岐をカバーすることができますが、ネストが深くなりやすいため見づらくなることがあります。
switch文は、複数の分岐を一つの構文で書けるため、見やすいという利点がありますが、比較演算子が単純な場合にしか使えず、フォールスルーに注意が必要です。
match式は、値に基づいて分岐でき、見やすく分かりやすいという利点がありますが、正規表現を使う場合には不向きです。
if
[編集]PHPには、C風のifがあります。else if
の短縮構文があるほか、テキストに埋込むのに適した代替構文があります。
例:
<?php
$n = NAN;
// 基本構文
if ($n < 0) {
echo '$n < 0', PHP_EOL;
} elseif ($n > 0) {
echo '$n > 0', PHP_EOL;
} elseif ($n == 0) {
echo '$n < 0', PHP_EOL;
} else {
echo $n, PHP_EOL;
}
echo PHP_EOL;
// 代替構文
if ($n < 0):
echo '$n < 0', PHP_EOL;
elseif ($n > 0):
echo '$n > 0', PHP_EOL;
elseif ($n == 0):
echo '$n < 0', PHP_EOL;
else:
echo $n, PHP_EOL;
endif;
?>
<?php if ($n < 0): ?>
<?= '$n < 0' ?>
<?php elseif ($n > 0): ?>
<?= '$n < 0' ?>
<?php elseif ($n == 0): ?>
<?= '$n == 0' ?>
<?php else: ?>
<?= $n ?>
<?php endif; ?>
- 基本構文
if ( 条件1 ) 文1 else if ( 条件式2 ) 文2 elseif ( 条件式3 ) 文3 … else 文
else if
はelse if
のシノニムでそれぞれ0回以上繰り返せる。else 文
は省略可能。- 代替構文
if ( 条件式1 ): 文1 elseif ( 条件式2 ): 文2 ︙ else: 文 endif;
- 代替構文では
else if
は使えない - 埋込構文
<?php if ( 条件1 ): ?> テキスト1 <?php elseif ( 条件式2 ): ?> テキスト2 <?php elseif ( 条件式3 ): ?> テキスト3 ︙ <?php elseif ( 条件式n ): ?> テキストn <?php else: ?> テキスト <?php endif; ?>
switch
[編集]PHPには、C風のswitchがあり、フォールスルーの挙動などほとんど同じですが、case の式に定数式以外の式が使える他、文字列などの比較も「緩やかな比較」で行えます。
- 例
<?php $s = "as"; switch ($s) { case "abstract": echo "KW_abstract", PHP_EOL; break; case "and": echo "KW_and", PHP_EOL; break; case "as": echo "KW_as", PHP_EOL; break; case "break": echo "KW_break", PHP_EOL; break; default: echo "Unknown"; } echo PHP_EOL; switch ($s) : case "abstract": echo "KW_abstract", PHP_EOL; break; case "and": echo "KW_and", PHP_EOL; break; case "as": echo "KW_as", PHP_EOL; break; case "break": echo "KW_break", PHP_EOL; break; default: echo "Unknown"; endswitch; ?> <?php switch ($s) : ?> <?php case "abstract":?> <?= "KW_abstract", PHP_EOL;?> <?php break;?> <?php case "and":?> <?= "KW_and", PHP_EOL;?> <?php break;?> <?php case "as":?> <?= "KW_as", PHP_EOL;?> <?php break;?> <?php case "break":?> <?= "KW_break", PHP_EOL;?> <?php break;?> <?php default:?> <?= "Unknown";?> <?php endswitch; ?>
- 実行結果
- C風のswitchで、もし
break
がないと次のcase
以降も実行します。 - 基本構文
switch ( 式 ) { case 式1 : 文1 case 式2 : 文2 ︙ case 式n : 文n default : 文 }
- 代替構文
switch ( 式 ) : case 式1 : 文1 case 式2 : 文2 ︙ case 式n : 文n default : 文 endswitch ;
- 埋込構文
<?php switch ( 式 ) : ?> <?php case 式1 : ?> テキスト1 <?php case 式2 : ?> テキスト2 ︙ <?php case 式n : ?> テキストn <?php default:?> テキスト <?php endswitch; ?>
match
[編集]PHPのmatchは、switchと似ていますが、文ではなく式なので値が取れるほか、比較には「厳密な比較」が使われます。
matchには、代替構文も埋込み構文もありません。
- 例
<?php $s = "as"; echo match ($s) { "abstract" => "KW_abstract", "and" => "KW_and", "as" => "KW_as", default => "Unknown", }, PHP_EOL; ?>
- #switchの例と同じロジックです。簡素にかけていますが、
- 連想配列を使った実装
<?php $s = "as"; echo [ "abstract" => "KW_abstract", "and" => "KW_and", "as" => "KW_as", ][$s] ?? "Unknown", PHP_EOL; ?>
- と書いたほうがより簡素だと考えるひともいるでしょう。
- また、
=>
の右には「式」が要求されるので、echo
のようなコマンドは使えません。 - 厳密でない比較の使用
<?php $age = 18; echo "{$age}歳は", match (true) { $age < 1 => "乳児", $age < 6 => "幼児", $age < 18 => "少年", default => "成人", }, "です。", PHP_EOL; ?>
- こんな事もできます。
- 基本構文
- 代替構文
- (ありません)
- 埋込構文
- (ありません)
分岐処理のネストや複雑な条件式について
[編集]分岐処理を多重にネストすると、可読性が低下し、コードが複雑になります。また、ネストされた分岐により、バグを見つけることがより困難になる場合があります。
そのため、分岐処理をシンプルに保つために、以下の点に注意することが重要です。
- ネストを減らす
- ネストを深くすると、可読性が低下するため、できる限り浅くするように心がけます。
- 分岐処理を多重に行わないようにすることで、コードの複雑性を減らすことができます。
- 論理演算子を活用する
- 複雑な条件式を書く場合は、論理演算子を使うことで、簡潔に書くことができます。
- ガード節を利用する
- 条件に合わない場合は、その時点で処理を中断する「ガード節」を利用することで、コードの見通しをよくすることができます。
- 分岐処理の抽象化
- 分岐処理が複雑になる場合は、別の関数やクラスに分岐処理をまとめることで、コードの可読性を向上させることができます。
以上のようなポイントに注意することで、分岐処理をシンプルに保つことができます。
ガード節
[編集]ガード節は、プログラムにおいて条件式が満たされない場合に、関数の実行を早期に終了させる方法です。通常、条件式が満たされなかった場合には関数内で複数のif文が使われている場合がありますが、ガード節を使うことで、コードを簡潔に保ち、可読性を高めることができます。
例えば、次のような関数があったとします。
function calcPrice($price, $tax) { if (!is_numeric($price)) { return 0; } if (!is_numeric($tax)) { return 0; } $result = $price * (1 + $tax); return $result; }
この関数では、引数が数値でない場合に、0を返しています。しかし、この関数は、引数が数値でない場合はすぐに処理を終了させる必要があります。この場合、ガード節を使うことで、以下のように書き換えることができます。
function calcPrice($price, $tax) { if (!is_numeric($price) || !is_numeric($tax)) { return 0; } $result = $price * (1 + $tax); return $result; }
このように、ガード節を使うことで、コードの可読性を高めることができます。
省略表記や三項演算子による分岐の書き方
[編集]省略表記や三項演算子を使用すると、コードの可読性を向上させることができます。以下は、その書き方の例です。
【省略表記】
- 三項演算子
// 通常のif文 if ($condition) { $result = 'true'; } else { $result = 'false'; } // 三項演算子 $result = ($condition) ? 'true' : 'false';
【null合体演算子】 null合体演算子??
を使用することで、変数のnullチェックを簡潔に書くことができます。
// 通常のif文 if (isset($name)) { $userName = $name; } else { $userName = 'Guest'; } // null合体演算子 $userName = $name ?? 'Guest';
【switch文の省略表記】 switch文の省略表記を使用することで、複数の条件に対して同じ処理を行うことができます。
// 通常のswitch文 switch ($value) { case 1: $result = 'A'; break; case 2: $result = 'B'; break; case 3: case 4: case 5: $result = 'C'; break; default: $result = 'D'; break; } // 省略表記 switch ($value): case 1: $result = 'A'; break; case 2: $result = 'B'; break; case 3: case 4: case 5: $result = 'C'; break; default: $result = 'D'; break; endswitch;
ただし、省略表記や三項演算子を多用しすぎると可読性が低下するため、適切な場面で使用するようにしましょう。また、複雑な条件分岐には適していないため、その場合は通常のif文やswitch文を使用することが望ましいです。
2つの比較演算子
[編集]if や while なでの条件式で、使われる比較演算子には2種類あります。
==
- 型を厳格には区別しない「緩やかな比較」
===
- 型を厳格に区別する「厳密な比較」
なお、=
等号1個は単なる代入命令ですが条件式で使われると代入された値を真理値として評価されます。
緩やかな比較
[編集]==
は、型を厳格には区別しない「緩やかな比較」演算子です。
<?php $a = 1; if ($a == 1) { echo "整数:", PHP_EOL; var_dump($a, 1) ; } if ($a == 1.0) { echo "浮動小数点数:", PHP_EOL; var_dump($a, 1.0) ; } if ($a == "1") { echo "文字列:", PHP_EOL; var_dump($a, "1") ; } ?>
- 実行結果
整数: int(1) int(1) 浮動小数点数: int(1) float(1) 文字列: int(1) string(1) "1"
- すべて一致します。
厳密な比較
[編集]===
は、型まで一致していることを要求する「厳密な比較」演算子です。
<?php $a = 1; if ($a === 1) { echo "整数:", PHP_EOL; var_dump($a, 1) ; } if ($a === 1.0) { echo "浮動小数点数:", PHP_EOL; var_dump($a, 1.0) ; } if ($a === "1") { echo "文字列:", PHP_EOL; var_dump($a, "1") ; } ?>
- 実行結果
整数: int(1) int(1)
- 型の一致は、数値であるだけでは不十分で、整数か浮動小数点数かは区別しています。
値または型の不一致演算子
[編集]!==
は型まで一致していない、あるいは値が一致していないと真をかえす演算子です。
繰り返し
[編集]繰り返しは、同じ処理を複数回繰り返すことができる制御構造の一つです。ループとも呼ばれ、同じ処理を繰り返し実行することで、膨大な量の処理を自動化することができます。繰り返し処理は、プログラミングにおいて非常に重要な役割を果たしており、Webアプリケーションの開発でも頻繁に利用されます。
ここでは、PHPで繰り返し処理を行うための方法を学びます。まずは、基本的な「for文」、「while文」、「do-while文」の使い方を解説します。 次に、より高度な繰り返し処理に必要な概念である「配列」や「連想配列」、そして「foreach文」の使い方についても学びます。
PHPには、while, do-while, for, foreach の4つの繰り返し文と、break, continue の2つの繰り返し制御文があります。
また、goto 演算子はありますが、ループからの大域脱出はbreak, continue の引数(オプション;ディフォルトは 1)でループのレベルを変えるなど、goto が使われがちな状況に代替手段を提供しているので、ほかのプログラミング言語ほどは goto の出番はありません。
加えて、PHPの繰り返し文や複文は、スコープを持ちません。ループを抜けてもループ変数は「生きている」ので、ループ変数は参照可能です。
while
[編集]whileは、まず条件式を評価し、真なら続く文(単文あるいは複文)を実行し条件式を再び評価します。もし、偽ならばループの次に移ります。
- whileの例
<?php $i = 1; while ($i < 100) { echo '$i = ', $i, PHP_EOL; $i *= $i + 1; } echo "Done!($i)", PHP_EOL; echo PHP_EOL; $j = 1; while ($j < 100): echo '$j = ', $j, PHP_EOL; $j *= $j + 1; endwhile; echo "Done!($j)", PHP_EOL; ?> <?php $k = 1;?> <?php while ($k < 100):?> <?= '$j = ', $k, PHP_EOL; ?> <?php $k *= $k + 1;?> <?php endwhile; ?> <?= "Done!($k)", PHP_EOL; ?>
- 実行結果
$i = 1 $i = 2 $i = 6 $i = 42 Done!(1806) $j = 1 $j = 2 $j = 6 $j = 42 Done!(1806) $j = 1 $j = 2 $j = 6 $j = 42 Done!(1806)
- 基本構文
while ( 条件式 ) 文
- while では、条件式を省略できません。
- 代替構文
while ( 条件式 ) : 文 endwhile ;
- 埋込構文
<?php while ( 条件式 ) : ?> テキスト <?php endwhile ; ?>
do-while
[編集]do-whileは、文(単文あるいは複文)を実行し条件式を評価します。真なら文を再び評価します。もし、偽ならばループの次に移ります。
- do-whileの例
<?php $i = 1; do { echo '$i = ', $i, PHP_EOL; $i *= $i + 1; } while ($i < 0); echo "Done!($i)", PHP_EOL; ?>
- 実行結果
$i = 1 Done!(2)
- 基本構文
do 文 while ( 条件式 ) ;
- 代替構文
- (ありません)
- 埋込構文
- (ありません)
for
[編集]forは、C風の3つの項を持つfor文です。
#whileの例と同じロジックを for(;;) で書き直しました(結果は同じなので省略します)。
- forの例
<?php for ($i = 1; $i < 100; $i *= $i + 1) { echo '$i = ', $i, PHP_EOL; } echo "Done!($i)", PHP_EOL; echo PHP_EOL; for ($j = 1; $j < 100; $j *= $j + 1): echo '$j = ', $j, PHP_EOL; endfor; echo "Done!($j)", PHP_EOL; ?> <?php for ($k = 1; $k < 100; $k *= $k + 1): ?> <?= '$k = ', $k, PHP_EOL ?> <?php endfor; ?> <?= "Done!($k)", PHP_EOL ?>
- 基本構文
for ( 初期化文 ; 条件式 : 繰り返し文 ) 文
- 初期化文
- forの最初に一回だけ実行されます。
- 省略可
- 条件式
- 真なら文を実行します。
- 省略されると永久ループになります。
- 繰り返し文
- 文を実行ししたあとに実行されます。
- 省略可
- 文
- 単文あるいは複文あるいは他の制御構造。
- 代替構文
for ( 初期化文 ; 条件式 : 繰り返し文 ) : 文 endfor ;
- 埋込構文
<?php fro ( 初期化文 ; 条件式 : 繰り返し文 ) : ?> テキスト <?php endfor ; ?>
foreach
[編集]foreachは、配列に代表される「繰り返し可能オブジェクト」(iterable object)を繰り返しします。
foreachには、配列版と連想配列版があります。
- froeachの例
<?php $seasons = ["春", "夏", "秋", "冬"]; foreach ($seasons as $season) { echo "基本: $season", PHP_EOL; } echo PHP_EOL; # for(;;) による等価なコード for ($i = 0, $len = count($seasons); $i < $len; $i++) { $season = $seasons[$i]; echo "等価: $season", PHP_EOL; } echo PHP_EOL; # 代替構文 foreach ($seasons as $season): echo "代替: $season", PHP_EOL; endforeach; ?> <?php foreach ($seasons as $season) : ?> <?= "埋込: $season" ?> <?php endforeach; ?> <?php $seasonsMap = [ "春" => "Spring", "夏" => "Summer", "秋" => "Fall / Autumn", "冬" => "Winter", ]; foreach ($seasonsMap as $ja => $en) { echo "基本: $ja --> $en", PHP_EOL; } echo PHP_EOL; # while による等価なコード while ($ja = key($seasonsMap)) { $en = $seasonsMap[$ja]; echo "等価: $ja --> $en", PHP_EOL; next($seasonsMap); } echo PHP_EOL; # 代替構文 foreach ($seasonsMap as $ja => $en): echo "代替: $ja --> $en", PHP_EOL; endforeach; ?> <?php foreach ($seasonsMap as $ja => $en) : ?> <?= "埋込: $ja --> $en" ?> <?php endforeach; ?>
- 実行結果
基本: 春 基本: 夏 基本: 秋 基本: 冬 等価: 春 等価: 夏 等価: 秋 等価: 冬 代替: 春 代替: 夏 代替: 秋 代替: 冬 埋込: 春 埋込: 夏 埋込: 秋 埋込: 冬 基本: 春 --> Spring 基本: 夏 --> Summer 基本: 秋 --> Fall / Autumn 基本: 冬 --> Winter 等価: 春 => Spring 等価: 夏 => Summer 等価: 秋 => Fall / Autumn 等価: 冬 => Winter 代替: 春 --> Spring 代替: 夏 --> Summer 代替: 秋 --> Fall / Autumn 代替: 冬 --> Winter 埋込: 春 --> Spring 埋込: 夏 --> Summer 埋込: 秋 --> Fall / Autumn 埋込: 冬 --> Winter
- 基本構文(配列版)
foreach ( 繰り返し可能オブジェクト as $ループ変数 ) 文
- 代替構文(配列版)
foreach ( 繰り返し可能オブジェクト as $ループ変数 ) : 文 endwhile ;
- 埋込構文(配列版)
<?php foreach ( 繰り返し可能オブジェクト as $ループ変数 ) : ?> テキスト <?php endwhile ; ?>
- 基本構文(連想配列版)
foreach ( 繰り返し可能オブジェクト as $キー変数 => $値変数 ) 文
- 代替構文(連想配列版)
foreach ( 繰り返し可能オブジェクト as $キー変数 => $値変数 ) : 文 endwhile ;
- 埋込構文(連想配列版)
<?php foreach ( 繰り返し可能オブジェクト as $キー変数 => $値変数 ) : ?> テキスト <?php endwhile ; ?>
- key()/next() で「while による等価なコード」を書きましたが、foreach()は内部繰り返し子を変更しません。
- 繰り返し可能オブジェクトは、配列・連想配列のほか、ジェネレター
ループ変数を、参照にすることで配列の要素を変更できます。
- ループ変数を参照にしたforeach()の例
<?php $ary = [1, 2, 3, 4]; var_export($ary); echo PHP_EOL; foreach ($ary as &$value) { $value = $value * 2; } var_export($ary); echo PHP_EOL; $value = 0; var_export($ary); ?>
- 実行結果
array ( 0 => 1, 1 => 2, 2 => 3, 3 => 4, ) array ( 0 => 2, 1 => 4, 2 => 6, 3 => 8, ) array ( 0 => 2, 1 => 4, 2 => 6, 3 => 0, )
このコードでは、まず配列 $ary
を定義しています。その後、foreach
ループを使って配列 $ary
の要素を 2 倍に更新し、変数 $value
に参照をとることで配列の値が更新されるようにしています。つまり、foreach
ループが終了した時点で、配列 $ary
の中身は、すべて 2 倍になっています。
その後、$value
変数に 0 を代入していますが、foreach
ループ内で参照していたのは $ary
配列の要素の参照であり、変数 $value
自体は配列要素への参照ではありません。そのため、$value
に 0 を代入しても、配列 $ary
の最後の要素だけが 0 に更新されることになります。
このように、ループ変数は、foreach()を抜けても参照は生きているので、代入するとループの外からも配列を壊すことができてしまいます。
unset($value)
のように参照関係を明示的に破壊する必要があります。
break
[編集]break文は、ループ内で使用され、ループ処理を中断するために使用されます。break文はif文内に配置されることができます。ループ内でbreak文が実行されると、ループは即座に終了し、次の処理に進みます。
以下は、ループ処理中にbreak文を使用する例です。
<?php for ($i = 1; $i <= 10; $i++) { if ($i == 6) { break; } echo $i . "<br>"; } ?>
この例では、1から10までの数字を表示しますが、6の前に処理が停止します。
continue
[編集]continue文は、ループ内で使用され、現在のループの反復をスキップして、次の反復に進むために使用されます。
以下は、ループ処理中にcontinue文を使用する例です。
<?php for ($i = 1; $i <= 10; $i++) { if ($i == 6) { continue; } echo $i . "<br>"; } ?>
この例では、1から10までの数字を表示しますが、6だけをスキップします。
goto
[編集]goto文は、指定されたラベルにジャンプするために使用されます。goto文は、ループ、スイッチ、または関数の中で使用されます。ただし、goto文を使用することは、プログラムの可読性を損なうことがあり、使用は推奨されません。
以下は、goto文を使用する例です。
<?php goto a; echo 'この部分は表示されません。'; a: echo 'この部分は表示されます。'; ?>
この例では、goto文が使用され、aというラベルにジャンプします。これにより、2番目のecho文が表示されず、3番目のecho文だけが表示されます。
例外処理
[編集]例外処理(れいがいしょり、exception handling)とは、プログラム実行中に予期せぬ異常が発生した場合に、通常の実行フローを中断し、エラーメッセージの表示や適切な処理を行うプログラムの手法です。
PHPでの例外処理は、try
, catch
, throw
, finally
キーワードを使って行われます。例外が発生する可能性のあるコードを try
ブロック内に記述し、それに対する例外処理を catch
ブロックで行います。
以下は基本的な例です:
try { // 例外が発生する可能性のあるコード // 例外を発生させる場合は、throwキーワードを使って例外オブジェクトを投げることができます if ($somethingUnexpected) { throw new Exception("Something unexpected occurred!"); } // 例外が発生しなかった場合の処理 } catch (Exception $e) { // 例外が発生した場合の処理 echo "Caught exception: " . $e->getMessage(); } finally { // finallyブロックは例外の有無に関わらず、最終的なクリーンアップを行うためのブロックです // このブロックは省略可能です echo "Finally block"; }
上記の例では、try
ブロック内で例外が発生する可能性があります。もし例外が発生した場合、catch
ブロックが実行されて例外オブジェクトが $e
としてキャッチされ、その後の処理が実行されます。finally
ブロックは例外の有無にかかわらず、最終的な処理を実行するために使用されます。
ゼロ除算を捕捉するプログラム
[編集]以下はゼロ除算の例外処理を含んだPHPコードです。
- コード例
<?php function divide($numerator, $denominator) { try { if ($denominator === 0) { throw new Exception("Division by zero is not allowed"); } $result = $numerator / $denominator; echo "Result of division: ", $result, PHP_EOL; } catch (Exception $e) { echo "Caught exception: ", $e->getMessage(), PHP_EOL; } finally { echo " Finally block", PHP_EOL; } } // 例外が発生する可能性のある関数を呼び出す divide(10, 2); // 正常なケース:10を2で割る divide(8, 0); // 例外ケース:ゼロ除算 ?>
この例では、divide()
関数を定義し、引数 $numerator
を $denominator
で割る処理を行います。try
ブロック内で $denominator
がゼロの場合に例外を投げます。catch
ブロックでは例外が発生した場合にそのメッセージを表示し、finally
ブロックでは最終的なクリーンアップを行います。
PHPの例外処理の関係クラス
[編集]PHPの例外処理に関連する主なクラスは次のとおりです。
- Exception: これは PHP の標準的な例外クラスです。このクラスを継承してカスタム例外クラスを作成することもできます。
getMessage()
、getCode()
、getFile()
、getLine()
、getTrace()
などのメソッドが利用可能で、これらは例外の詳細情報を取得するために使用されます。 - Error: これは例外ではなくエラーを表すためのクラスです。このクラスも例外クラスを継承しており、エラーの詳細情報を取得するためのメソッドがあります。
カスタム例外を作成することもできます。例えば、特定の種類のエラーやアプリケーション固有の例外を処理するために、以下のようにしてカスタム例外を作成することができます:
class CustomException extends Exception { // カスタム例外クラスの実装 } // 使用例 try { // 何かしらの処理 if ($somethingUnexpected) { throw new CustomException("Something unexpected occurred!"); } } catch (CustomException $e) { echo "Caught custom exception: ", $e->getMessage(), PHP_EOL; } catch (Exception $e) { // 他の例外をキャッチする echo "Caught exception: ", $e->getMessage(), PHP_EOL; }
上記の例では、CustomException
という独自の例外クラスを作成しています。そして try
ブロック内で、何かしらの条件でその例外を発生させ、catch
ブロックでその例外をキャッチしています。
このようにして、標準の Exception
クラスを継承してカスタム例外を作成し、それを使用して特定の種類の例外を処理することができます。
PHPの例外処理のベストプラクティス
[編集]PHPにおける例外処理のベストプラクティスにはいくつかあります。その中でも重要なものをいくつか挙げてみましょう。
- 適切な例外クラスの使用: 標準の
Exception
クラスを適切に使用することは重要です。また、必要に応じてカスタム例外クラスを作成することで、異なる種類のエラーに対処できます。 - エラーメッセージの適切な管理: 例外が発生した際には、適切なエラーメッセージを提供し、デバッグやエラーの追跡を容易にすることが大切です。
getMessage()
メソッドを使って適切な情報を取得し、ログに記録するなどして詳細を把握しやすくします。 - 適切なエラーハンドリング:
try
,catch
,finally
ブロックを使ってエラーをキャッチし、適切なハンドリングを行います。エラーをキャッチした際に適切な対処を行い、コードの安全性を確保します。 - 例外の階層構造の活用: PHPには様々な種類の例外があり、これらを階層的に管理することで、特定の例外に対する処理をより細かく制御できます。
- 適切なロギング: 例外が発生したときには、それをログに記録することが重要です。これにより、エラーが発生した際に問題をより迅速に特定し、解決することができます。
- ユーザーフレンドリーなエラー表示: ユーザーにはわかりやすく、安心感を与えるエラーメッセージを提供することが重要です。しかし、セキュリティ上のリスクを避けるために、エラーの詳細はログなどに記録し、ユーザーにはそれを表示しないようにします。
これらのベストプラクティスを遵守することで、PHPアプリケーションのエラー処理を効果的に行うことができます。
附録
[編集]代替構文
[編集]代替構文は、通常の制御構造の代わりに、より読みやすいコードを書くために使用される構文です。 代替構文は、制御構造の一部に波括弧 { } を含めずに書くことができ、それによってコードをよりシンプルにすることができます。
例えば、次のif文の代替構文は:
if ($a == 1) { echo "a is 1"; }
以下のように書くことができます:
if ($a == 1): echo "a is 1"; endif;
同様に、以下のfor文の代替構文は:
for ($i = 0; $i < 10; $i++) { echo $i; }
以下のように書くことができます:
for ($i = 0; $i < 10; $i++): echo $i; endfor;
代替構文は、コードの可読性を向上させることができますが、適切に使用しない場合はコードの可読性を損なうこともあります。したがって、代替構文を使用する際には、適切に判断することが重要です。
制御構造 | 代替構文 |
---|---|
if (条件式) { 実行文; } | if (条件式): 実行文; endif; |
if (条件式) { 実行文1; } else { 実行文2; } | if (条件式): 実行文1; else: 実行文2; endif; |
for (初期化式; 条件式; 更新式) { 実行文; } | for (初期化式; 条件式; 更新式): 実行文; endfor; |
foreach (配列 as $value) { 実行文; } | foreach (配列 as $value): 実行文; endforeach; |
while (条件式) { 実行文; } | while (条件式): 実行文; endwhile; |
do { 実行文; } while (条件式); | do: 実行文; while (条件式); |
代替構文は、コードの見た目をより読みやすくするために導入された構文です。 代替構文を使用する場合、制御構造の終了を示すためにそれぞれ endif、endfor、endforeach、endwhile を使用します。 しかし、代替構文を使っても制御構造自体の動作には変わりはありません。
チートシート
[編集]// 条件分岐 // if文 if (条件式1) { // 条件式1が true の場合の処理 } elseif (条件式2) { // 条件式1が false で条件式2が true の場合の処理 } else { // 上記の条件式がすべて false の場合の処理 } // switch文 switch (値) { case 値1: // 値が値1と一致する場合の処理 break; case 値2: // 値が値2と一致する場合の処理 break; // ... default: // 値がどの case にも一致しない場合の処理 break; } // match式(PHP 8.0以降) match (値) { 値1 => // 値が値1と一致する場合の処理, 値2 => // 値が値2と一致する場合の処理, // ... default => // 値がどの case にも一致しない場合の処理, } // 繰り返し // for文 for (初期化式; 条件式; 変化式) { // 条件式が true の場合に繰り返す処理 } // while文 while (条件式) { // 条件式が true の場合に繰り返す処理 } // do-while文 do { // 最初に必ず一度実行される処理 // 条件式が true の場合に繰り返す処理 } while (条件式); // foreach文 foreach ($配列 as $キー => $値) { // 配列の要素数分繰り返す処理 } // 制御文 // break文 break; // 現在のループまたはswitch文から抜ける // continue文 continue; // 現在のループの次の繰り返しに移る // goto文 goto ラベル名; // 指定されたラベルにジャンプする // ... ラベル名: // ...
用語集
[編集]- 条件式(Condition):条件分岐やループ処理において、判定に使用する式のことです。真偽値を返す必要があります。
- if文(if statement):条件分岐のための制御構造の一つで、指定された条件式が true の場合に実行するブロックと、 false の場合に実行するブロックを分けて指定します。
- elseif文(elseif statement):複数の条件分岐を実行する場合に使用される制御構造の一つで、直前の if 文の条件式が false で、自身の条件式が true の場合に実行するブロックを指定します。
- else文(else statement):if文の条件式が false の場合に実行するブロックを指定する制御構造の一つです。
- switch文(switch statement):複数の条件分岐を実行する場合に使用される制御構造の一つで、指定された変数の値によって実行するブロックを選択します。
- case文(case statement):switch 文で使用され、値に応じて実行するブロックを選択します。
- default文(default statement):switch 文で使用され、どの case 文にも当てはまらない場合に実行するブロックを指定します。
- while文(while statement):指定された条件式が true の場合に、実行するブロックを繰り返し実行する制御構造の一つです。
- do-while文(do-while statement):一度は必ず実行し、指定された条件式が true の場合に実行するブロックを繰り返し実行する制御構造の一つです。
- for文(for statement):指定された回数分、実行するブロックを繰り返し実行する制御構造の一つです。
- foreach文(foreach statement):配列やオブジェクトのプロパティを1つずつ処理するために使用される制御構造。
- break文(break statement):繰り返し処理を強制的に終了するための制御構造の一つです。
- continue文(continue statement):繰り返し処理の一部をスキップし、次のループに進むための制御構造の一つです。
- goto文(goto statement):指定されたラベルにジャンプするための制御構造の一つです。PHPではほとんど使用されません。
関数
[編集]関数は、プログラムを書く上で非常に重要な概念です。関数を使うことによって、同じ処理を何度も繰り返す必要がなくなり、コードの再利用性が高まります。また、関数は複雑な処理を単純な手順に分割することができ、プログラムの可読性や保守性を向上させます。 PHPにおいても、関数は非常に重要な役割を担っています。PHPには多くの便利な組み込み関数が用意されており、また、自分で関数を定義することもできます。この章では、PHPで関数を使う方法や、関数の定義方法、引数や戻り値の扱い方などを解説します。関数を使いこなすことで、より高度なプログラミングを行うことができるようになるでしょう。
関数定義と呼出し
[編集]PHPの関数定義の構文は以下の通りです。
- PHPの関数定義の構文
function 関数名(引数1, 引数2, ...) { // 処理内容 return 戻り値; }
function
キーワードで関数定義を開始します。関数名
には任意の名前を付けます。関数名は英数字とアンダースコア(_)の組み合わせで構成されます。また、関数名は大文字小文字を区別しません。()
内に引数を定義します。引数は任意の数だけ定義できます。引数がない場合は、()
だけを記述します。{}
内に処理内容を記述します。return
キーワードを使って、関数から返す値を定義します。return
文がない場合、関数は何も返しません。
以下は、具体的な例です。
- 関数定義と呼出しの例
<?php function greet($name) { echo "Hello, {$name}!", PHP_EOL; } greet("John"); // "Hello, John!" と出力される ?>
- このコードは、引数として受け取った
$name
を使って、「Hello, 名前!」というメッセージを表示する関数greet()
を定義し、その関数を"John"
という引数で呼出しています。 - 具体的には、
greet()
関数に"John"
という文字列を引数として渡し、関数内で"Hello, {$name}!"
という文字列を表示しています。{$name}
は、変数$name
の値を埋め込んだ文字列として扱われます。 - そして、
greet("John");
という呼出しを行うことで、関数greet()
が引数"John"
を受け取り、"Hello, John!"
という文字列を表示する処理が実行されます。結果として、画面に"Hello, John!"
というメッセージが表示されます。 - このように、関数を定義することで、同じような処理を何度も簡単に実行することができます。また、関数に引数を渡すことで、処理の柔軟性が高まり、より汎用的な関数を作ることができます。
- PHP_EOLは、PHPの内部定数であり、OSごとの改行文字列を表します。つまり、Windowsでは"\r\n"、LinuxやmacOSでは"\n"が使用されます。
- この定数は、プログラムを複数のプラットフォームで実行する場合に便利です。改行文字列を明示的に指定することで、プログラムがどのプラットフォームでも同じ出力を生成することができます。
関数の引数と戻値
[編集]PHPの関数は、0個以上の引数を渡すことができます。引数は関数内で変数として扱われます。引数の値は、関数呼出しの際に渡されます。
- 関数の引数と戻値の例
<?php function add($a, $b) { return $a + $b; } $result = add(3, 5); // $resultには8が代入される ?>
- 上記の例では、
add
という関数を定義しています。この関数は、2つの引数 $a と $b を受け取り、それらを加算した値を返すという処理を行っています。 - そして、
add
関数に引数として 3 と 5 を渡して呼出し、その戻り値を $result 変数に代入しています。
可変長引数
[編集]また、PHPには可変長引数という機能があります。これは、引数の数が可変である場合に使用することができます。可変長引数は、...
を引数の前に付けて定義します。
- 可変長引数の例
<?php function sum(...$numbers) { $total = 0; foreach ($numbers as $number) { $total += $number; } return $total; } $result1 = sum(1, 2, 3); // $result1には6が代入される $result2 = sum(4, 5, 6, 7); // $result2には22が代入される ?>
- 上記の例では、sum という関数を定義しています。この関数は、可変長引数 $numbers を受け取り、引数に渡された数値をすべて加算してその結果を返すという処理を行っています。
- そして、sum 関数に引数として 1、2、3 を渡して呼出し、その戻り値を $result1 変数に代入し、次に引数として 4、5、6、7 を渡して呼出し、その戻り値を $result2 変数に代入しています。
残余引数
[編集]残余引数も可変長引数と同様に多数の引数を渡すことができますが、配列として渡されます。また、可変長引数とは異なり、残余引数は引数のリストの途中に配置することができます。
以下は、残余引数を受け入れる関数の例です。
<?php function scores($subject, $student, ...$scores) { $total = 0; foreach ($scores as $score) { $total += $score; } $average = $total / count($scores); echo "{$student} got an average of {$average} in {$subject}."; } scores("Math", "John", 80, 85, 90); //==> John got an average of 85 in Math. ?>
引数のデフォルト値とキーワード引数
[編集]さらに、PHP 8.0 からは、引数のデフォルト値を設定したり、キーワード引数を使った関数定義もサポートされました。引数にデフォルト値を設定する場合は、引数の後に = を付けてデフォルト値を指定します。また、キーワード引数を使う場合は、引数名を指定して値を渡すことができます。
- 引数のデフォルト値とキーワード引数の例
<?php function greet($name = "World") { echo "Hello, {$name}!", PHP_EOL; } greet(); // "Hello, World!" と出力される greet("John"); // "Hello, John!" と出力される greet(name: "Alice"); // "Hello, JAlice" と出力される ?>
型宣言
[編集]型宣言は、関数の引数や返り値、そして PHP 7.4.0 以降ではクラスのプロパティに追加することができます。 これらは、呼出し時に指定した型の値であることを保証し、 一致しない場合は TypeError がスローされます。 strict_types を 1 にセットすると、型宣言漏れをエラーにできます。 また、strict_types を有効にすると、関数の返り値の型宣言についても厳密なチェックが行われます。 つまり、返り値が宣言された型と異なる場合は TypeError がスローされます。
以下は、引数と返り値に型宣言を追加した greet() 関数の例です。
- 型宣言の例
<?php declare(strict_types=1); function greet(string $name): string { // return 0; // PHP Fatal error: Uncaught TypeError: greet(): Return value must be of type string, int returned return "Hello, {$name}!"; } echo greet("John"); // "Hello, John!" と出力される // echo greet(123); // PHP Fatal error: Uncaught TypeError: greet(): Argument #1 ($name) must be of type string, int given // echo greet(); // PHP Fatal error: Uncaught ArgumentCountError: Too few arguments to function greet(), 0 passed ?>
- このコードは、厳密な型チェックを有効にするために
declare(strict_types=1)
が宣言されている関数greet()
を定義しています。 greet()
関数は、1 つの文字列型の引数$name
を受け取り、戻り値として文字列型の値を返します。このように、関数に対して厳密な型宣言がされているため、関数の引数と戻り値は指定された型と一致しなければなりません。greet()
関数が文字列型ではなく整数型を返す場合にTypeError
がスローされます。greet()
関数に整数型を渡すと、TypeError
がスローされ、エラーメッセージが表示されます。- また、
greet()
関数に引数を渡さずに呼出すと、引数が足りないため、ArgumentCountError
がスローされます。 string
型の引数を渡す必要があります。このように、引数と戻り値の型が厳密に指定されているため、不正な型が渡されると、TypeError
がスローされます。
PHPの関数定義の構文
[編集]- PHPの関数定義の構文
function 関数名(引数1の型 $引数1の名前 = デフォルト値, 引数2の型 $引数2の名前 = デフォルト値, ...可変長引数) : 戻り値の型 { // 関数の処理 return 戻り値; }
有効な型
[編集]プリミティブ型のほか、クラスやインターフェースの名前は、型名として認識されます。
以下は、PHPでサポートされる型とサポートされるようになったバージョンの一覧です。
PHPの関数の引数や戻値の型 型 説明 バージョン int 整数 5.0.0 float 浮動小数点数 5.0.0 string 文字列 5.0.0 bool 真偽値 4.2.0 array 配列 4.0.0 object オブジェクト 4.0.0 callable コールバック(関数名やクロージャなど) 5.4.0 iterable イテレータ 7.1.0 self 自分自身のクラス 5.0.0 parent 親クラス 5.0.0 ?int nullまたは整数 7.1.0 ?float nullまたは浮動小数点数 7.1.0 ?string nullまたは文字列 7.1.0 ?bool nullまたは真偽値 7.1.0 ?array nullまたは配列 7.1.0 ?object nullまたはオブジェクト 7.2.0 ClassName クラス名 5.0.0 Interface インターフェース名 5.0.0 array<int> 整数型の配列 7.0.0 array<mixed> 任意の型の配列 7.0.0 array<T> 型Tの配列。Tにはint,stringなどの型名を指定することができます。 7.3.0
注意点として、iterableやarray<T>などは、PHP 7.1.0以降でのみサポートされているため、それ以前のバージョンでは使用できません。 また、array<T>のようなジェネリック型は、厳密な型検査がされるわけではなく、実行時にエラーが発生する可能性があるので注意が必要です。
変数のスコープ
[編集]PHPには、ローカルスコープ、グローバルスコープ、そして静的スコープの3つのスコープがあります。
ローカルスコープ
[編集]ローカルスコープは、関数内で宣言された変数が含まれます。この変数は、関数の実行が終了した時点で破棄されます。例えば、次のコードでは、関数 greet()
の中で $name
という変数が宣言されています。
- ローカルスコープの例
<?php function greet() { $name = "John"; echo "Hello, {$name}!"; } greet(); // "Hello, John!" と出力される echo $name; // PHP Warning: Undefined variable $name ?>
- 関数の外から
$name
変数にアクセスすると、Undefined variable
のエラーが発生します。
グローバルスコープ
[編集]グローバルスコープは、関数の外で宣言された変数が含まれます。この変数は、プログラムの終了まで有効です。例えば、次のコードでは、$name
という変数が関数の外で宣言されています。
- ローカルスコープの例
<?php function greet() { static $count = 0; $count++; echo "Hello, this function has been called {$count} times!", PHP_EOL; } greet(); // "Hello, this function has been called 1 times!" と出力される greet(); // "Hello, this function has been called 2 times!" と出力される greet(); // "Hello, this function has been called 3 times!" と出力される ?>
global
キーワードを使って、関数内でグローバル変数にアクセスすることができます。
静的スコープ
[編集]静的スコープは、関数内で宣言された変数が含まれますが、その変数は関数の実行が終了しても破棄されず、次回の関数実行時にも値を保持します。例えば、次のコードでは、greet()
関数内で $count
変数が宣言されており、呼出し回数をカウントするために使われます。
- 静的スコープの例
$name = "John"; function greet() { global $name; echo "Hello, {$name}!"; } greet(); // "Hello, John!" と出力される echo $name; // "John" と出力される
static
キーワードを使って、静的変数を宣言することができます。$count
変数は、関数が終了しても破棄されず、次回の関数実行時にも値を保持します。
スーパーグローバル変数
[編集]PHPには、どこからでもアクセスできるスーパーグローバル変数がいくつかあります。これらは、関数やクラスのスコープを超えて、グローバルにアクセス可能な変数です。
スーパーグローバル変数 変数名 説明 $GLOBALS スクリプト全体で利用可能なグローバル変数の連想配列 $_SERVER サーバー情報(HTTPヘッダ、パス、スクリプトの位置など)の連想配列 $_GET URLパラメータで渡された値の連想配列 $_POST HTTP POSTメソッドで渡された値の連想配列 $_FILES HTTP POSTメソッドでアップロードされたファイルの連想配列 $_COOKIE クライアントから送信されたクッキーの連想配列 $_SESSION セッション変数の連想配列 $_REQUEST HTTP POST、HTTP GET、およびクッキーの値を含む連想配列 $_ENV 環境変数の連想配列
これらの変数は、スクリプトのどこからでもアクセス可能で、グローバルなスコープを持ちます。 また、変数名の前に「$_」がつくのが特徴です。これは、変数名がスーパーグローバルであることを明示するための命名規則です。
マジック定数
[編集]PHPには、特定の状況下で自動的に設定されるマジック定数と呼ばれる定数があります。これらの定数は、スクリプトのどこからでもアクセスできます。
マジック定数 マジック定数 説明 __LINE__ 現在の行番号 __FILE__ 現在のファイル名(ファイルパスを含む) __DIR__ 現在のファイルのディレクトリ名 __FUNCTION__ 現在の関数名 __CLASS__ 現在のクラス名 __TRAIT__ 現在のトレイト名 __METHOD__ 現在のメソッド名(クラス名も含む) __NAMESPACE__ 現在の名前空間名
- これらのマジック定数は、プログラム実行中に自動的に設定され、その値を取得することができます。
- 例えば、__LINE__は現在の行番号を表し、__FILE__は現在のファイル名を表します。
- また、__FUNCTION__は現在の関数名を表し、__CLASS__は現在のクラス名を表します。
- これらのマジック定数を使うことで、デバッグやログ出力などで便利に利用することができます。
高階関数
[編集]高階関数とは、引数として別の関数を受け取る関数のことを指します。一般的に、高階関数は、関数を引数として受け取り、その関数を実行することによって機能を拡張するために使用されます。PHPにおいても、高階関数が使用されることがあります。
以下に、PHPにおける高階関数の例を示します。
<?php // map関数 function map($arr, $func) { $result = []; foreach ($arr as $elem) { $result[] = $func($elem); } return $result; } // 配列の要素を2倍にする関数 function double($x) { return $x * 2; } // 配列の各要素を2倍にする $arr = [1, 2, 3, 4]; $arr2 = map($arr, 'double'); // $arr2 = [2, 4, 6, 8] ?>
- 上記の例では、
map
関数が高階関数として定義されています。この関数は、第1引数に配列、第2引数に関数を受け取り、配列の各要素に関数を適用した結果の配列を返します。double
関数は、引数を2倍にする単純な関数で、map
関数によって使用されています。
このように、高階関数は、汎用性が高く、再利用性の高いコードを実現するために有用です。
無名関数
[編集]無名関数とは、名前を持たずに関数の機能を持つPHPの関数のことです。通常の関数と同様に、引数を受け取り、何らかの処理を行い、戻り値を返すことができます。 無名関数は、コールバック関数やクロージャーとして使用されます。コールバック関数は、ある処理が完了したときに呼出される関数であり、クロージャーは、定義された環境内の変数にアクセスできる無名関数です。 以下は、無名関数の基本的な構文の例です。
$func = function($arg1, $arg2) { // 無名関数の中身 };
無名関数を使用する場合、変数に関数を代入することができます。また、関数の引数に渡したり、コールバック関数やクロージャーとして使用することもできます。
コールバック関数
[編集]PHPにおいて、コールバック関数とは、ある関数の引数として渡され、その関数内で呼び出される関数のことを指します。つまり、コールバック関数は、関数を引数として渡すことができる高度な機能であり、PHPの強力な機能の一つと言えます。
コールバック関数は、通常は無名関数を使って定義されますが、名前付き関数も渡すことができます。以下は、無名関数を使って、コールバック関数を定義する例です。
<?php function executeCallback($callback) { if (is_callable($callback)) { $callback(); } else { echo "The callback is not callable."; } } executeCallback(function() { echo "This is a callback function."; }); ?>
- 上記の例では、
executeCallback
という関数を定義し、引数として渡された$callback
がcallableであれば、その$callback
を実行する処理が書かれています。 - そして、
executeCallback
を呼び出す際に、無名関数を渡しています。この無名関数がコールバック関数として使用されます。
コールバック関数を使うことで、汎用的な関数を作成することができます。また、PHPには、配列を操作するための便利なコールバック関数が多数用意されており、それらを利用することで、配列を簡単に操作することができます。例えば、array_map
関数は、与えられた関数を、配列の各要素に適用し、新しい配列を返す関数です。以下は、array_map
を使って、配列の各要素を二乗する例です。
- 配列の各要素を二乗する例
<?php $numbers = [1, 2, 3, 4, 5]; $squares = array_map(function($number) { return $number * $number; }, $numbers); print_r($squares); // [1, 4, 9, 16, 25] を出力 ?>
- 上記の例では、
array_map
関数を使って、配列$numbers
の各要素を二乗した新しい配列$squares
を作成しています。 array_map
関数の第一引数には、配列の各要素に適用する関数が、第二引数には、適用する配列が指定されています。- この例では、第一引数として、無名関数を定義しています。この無名関数は、引数として与えられた数値を二乗した結果を返すように定義されています。:
array_map
関数が呼び出されると、無名関数が、配列$numbers
の各要素に対して
クロージャー
[編集]クロージャー (Closure) は、無名関数を格納するためのオブジェクトであり、関数を定義する時点で外部変数を捕捉し、その変数を関数の中で使うことができるようにします。
クロージャーは、外部変数が割り当てられた後に定義され、その変数にアクセスすることができます。クロージャーが定義されると、その変数はクロージャーによって保持され、クロージャーが解放されるまで維持されます。
以下は、PHPにおけるクロージャーの例です。
- クロージャーの例
<?php function add($a) { return function ($b) use ($a) { return $a + $b; }; } $addFive = add(5); echo $addFive(3); // 8 ?>
- この例では、
add()
関数はクロージャーを返し、そのクロージャーは$a
変数を保持しています。 $addFive
変数にクロージャーを割り当て、引数として3
を渡すことで、$a
の値が5
であるため、5 + 3
の結果である8
が出力されます。
再帰的呼出し
[編集]再帰的呼出しとは、ある関数が自分自身を呼出すことを指します。この呼出しは、同じ処理を何度も繰り返す必要がある場合や、複雑なアルゴリズムを実装する場合に便利です。
PHPでも、再帰的呼出しをすることができます。以下は、簡単な例として、1から与えられた数までの自然数の和を再帰的に計算する関数です。
- 再帰的呼出しの例
<?php function sum($n) { if ($n === 1) { return 1; } else { return $n + sum($n - 1); } } echo sum(5); // 15 ?>
- このコードは、再帰的呼出しを使って、1から引数の数値までの整数の和を求める関数を定義しています。
- まず、関数
sum
の引数$n
が1と等しい場合、1を返します。これは、再帰呼出しの終了条件となります。 - それ以外の場合、関数
sum
は$n
とsum($n - 1)
を足した値を返します。sum($n - 1)
は、引数$n - 1
を渡して自身を再帰的に呼出しており、これによって$n
が1になるまで再帰的に呼出されます。 - 最終的に
sum(1)
が呼出されると、終了条件によって1が返され、再帰呼出しは終了します。その後、すべての再帰呼出しが返した値が足し合わされ、1から引数の数値までの整数の和が求められます。
再帰的呼出しは、アルゴリズムが再帰的な場合には簡潔なコードにすることができますが、理解やデバッグの難易度を高めることがあるため、注意が必要です。 さらに、再帰的に呼出す関数の処理が大きくなりすぎると、スタックオーバーフローの原因になる場合があります。 また、適切な条件判定や終了処理の実装が必要です。
ジェネレーター関数
[編集]PHPにおけるジェネレーター関数は、反復可能なオブジェクトを生成するための特殊な関数です。
ジェネレーター関数は、yield
キーワードを使用して値を返し、内部の状態を保持します。
yield
キーワードを使用すると、ジェネレーター関数の実行が一時停止され、呼出し元に値が返されます。
呼出し元は、ジェネレーター関数が再開されるときに、再びジェネレーター関数を呼出すことができます。
- 例
<?php declare(strict_types=1); function gen_square() : iterable { for ($i = 0; $i < 10; $i++) { yield $i * $i; } } foreach (gen_square() as $value) { echo "$value\n"; } var_export(array(...gen_square()));
- 実行結果
0 1 4 9 16 25 36 49 64 81 array ( 0 => 0, 1 => 1, 2 => 4, 3 => 9, 4 => 16, 5 => 25, 6 => 36, 7 => 49, 8 => 64, 9 => 81, )
- このコードは、ジェネレーター関数
gen_square()
を定義しています。この関数は、0から9までの整数の2乗を生成し、それぞれyield
キーワードで返します。そして、foreach
ループを使ってgen_square()
の戻り値を反復処理し、値を出力します。 - また、
array(...gen_square())
を使って、gen_square()
の返すイテレータを配列に展開しています。この記法は PHP 7.4 から導入されたスプレッド演算子を使用しており、配列リテラル内で...
を使用することで、配列を展開することができます。 - 関数の戻り値型として、
iterable
を指定しています。これは、PHP 7.1 から導入された型で、反復処理可能なオブジェクトを表す型です。ジェネレーター関数はiterable
を返すことができます。
ジェネレーターは配列に似ていますが、配列は要素数 × 要素1つあたりのサイズのメモリーフットプリントを消費します。それに対し、ジェネレーターは yield
で返す値は都度計算できるため、メモリ消費が配列に比べて小さくなります。
コードキャラリー
[編集]まとまった規模で、実用的な目的に叶うコードを読まないと機能の存在理由などの言語設計に思い至りにくいので、コードの断片から少し踏み込んだプログラムを掲載します。
array_*関数の拡張
[編集]PHPの組込み関数には、array_reduce(),array_map() のように array_ はじまる配列関数群があります。 これらは便利なのですが、配列とハッシュ兼用でJavaScriptやRubyなどから来ると面食らうことがあります。 また、配列の添字がcallback関数にわたらない仕様も移植のときに問題になります。 そこで、上記の問題をクリアした配列専用の関数群を作ってみました。 また、sum() は、カハンの加算アルゴリズムを使い誤差を最小にするようにしています。
- 実装
<?php // phpinfo(); declare(strict_types=1); function reduce(array $ary, callable $code, mixed $result = null): mixed { $i = 0; foreach ($ary as $el) { if ($i == 0 && !isset($result)) { $result = $el; } else { $result = $code($result, $el, $i); } $i++; } return $result; } echo 'reduce([1, 2, 3], fn($s, $x) => $s + $x) --> ', reduce([1, 2, 3], fn($s, $x) => $s + $x), PHP_EOL; echo 'reduce([1, 2, 3], fn($s, $x) => $s + $x, 10) --> ', reduce([1, 2, 3], fn($s, $x) => $s + $x, 10), PHP_EOL; echo 'reduce(["A", "B", "C"], fn($s, $x) => $s . $x) --> ', reduce(["A", "B", "C"], fn($s, $x) => $s . $x), PHP_EOL; echo 'reduce(["A", "B", "C"], fn($s, $x) => [ ...$s, "<$x>"], []) --> ', var_export(reduce(["A", "B", "C"], fn($s, $x) => [...$s , "<$x>"], []), true),PHP_EOL; echo PHP_EOL; function map(array $ary, callable $code): array { $result = []; $i = 0; foreach ($ary as $el) { $result[] = $code($el, $i++); } return $result; } $a1 = [2, 3, 5, 7, 11]; echo '$a1 = [2, 3, 5, 7, 11];', PHP_EOL; echo 'map($a1, fn($x) => $x * $x) --> ', var_export(map($a1, fn($x) => $x * $x), true), PHP_EOL; echo PHP_EOL; function each(array $ary, callable $code): void { $i = 0; foreach ($ary as $el) { $code($el, $i++); } } echo 'each($a1, fn($x, $i) => printf("%d[%d] ", $x, $i)); --> '; each($a1, fn($x, $i) => printf("%d[%d] ", $x, $i)); echo PHP_EOL; echo PHP_EOL; function filter(array $ary, callable $code): array { $result = []; $i = 0; foreach ($ary as $el) { if ($code($el, $i++)) { $result[] = $el; } } return $result; } echo 'filter($a1, fn($x) => $x % 2) --> ', var_export(filter($a1, fn($x) => $x % 2), true), PHP_EOL; echo 'filter($a1, fn($x) => $x < 6) --> ', var_export(filter($a1, fn($x) => $x < 6), true), PHP_EOL; echo PHP_EOL; function sum(array $ary): int|float|null { $sum = null; $i = 0; foreach ($ary as $delta) { if ($i == 0) { $sum = $delta; $c = $delta - $delta; } else { $y = $delta - $c; $t = $sum + $y; $c = ( $t - $sum ) - $y; $sum = $t; } $i++; } return $sum; } $nums = array_fill(0, 10, 0.1); echo '$nums = array_fill(0, 10, 0.1);', PHP_EOL; echo 'reduce($nums, fn($x, $y) => $x + $y) --> ', sprintf("%.53f", reduce($nums, fn($x, $y) => $x + $y)), PHP_EOL; echo 'array_sum($nums) --> ', sprintf("%.53f", array_sum($nums)), PHP_EOL; echo 'sum($nums) --> ', sprintf("%.53f", sum($nums)), PHP_EOL; echo 'sum(range(1, 100)) --> ', sprintf("%d", sum(range(1, 100))), PHP_EOL; echo 'sum([]) --> ',var_export(sum([]), true), PHP_EOL; echo PHP_EOL; function every(array $ary, callable $code): bool { $i = 0; foreach ($ary as $el) { if (!$code($el, $i++)) { return false; } } return true; } echo 'every($a1, fn($x) => $x != 1) --> ', var_export(every($a1, fn($x) => $x != 1), true), PHP_EOL; echo 'every($a1, fn($x) => $x == 2) --> ', var_export(every($a1, fn($x) => $x == 2), true), PHP_EOL; echo PHP_EOL; function some(array $ary, callable $code): bool { $i = 0; foreach ($ary as $el) { if ($code($el, $i++)) { return true; } } return false; } echo 'some($a1, fn($x) => $x != 1) --> ', var_export(some($a1, fn($x) => $x != 1), true), PHP_EOL; echo 'some($a1, fn($x) => $x == 2) --> ', var_export(some($a1, fn($x) => $x == 2), true), PHP_EOL; echo PHP_EOL;
- 実行結果
reduce([1, 2, 3], fn($s, $x) => $s + $x) --> 6 reduce([1, 2, 3], fn($s, $x) => $s + $x, 10) --> 16 reduce(["A", "B", "C"], fn($s, $x) => $s . $x) --> ABC reduce(["A", "B", "C"], fn($s, $x) => [ ...$s, "<$x>"], []) --> array ( 0 => '<A>', 1 => '<B>', 2 => '<C>', ) $a1 = [2, 3, 5, 7, 11]; map($a1, fn($x) => $x * $x) --> array ( 0 => 4, 1 => 9, 2 => 25, 3 => 49, 4 => 121, ) each($a1, fn($x, $i) => printf("%d[%d] ", $x, $i)); --> 2[0] 3[1] 5[2] 7[3] 11[4] filter($a1, fn($x) => $x % 2) --> array ( 0 => 3, 1 => 5, 2 => 7, 3 => 11, ) filter($a1, fn($x) => $x < 6) --> array ( 0 => 2, 1 => 3, 2 => 5, ) $nums = array_fill(0, 10, 0.1); reduce($nums, fn($x, $y) => $x + $y) --> 0.99999999999999988897769753748434595763683319091796875 array_sum($nums) --> 0.99999999999999988897769753748434595763683319091796875 sum($nums) --> 1.00000000000000000000000000000000000000000000000000000 sum(range(1, 100)) --> 5050 sum([]) --> NULL every($a1, fn($x) => $x != 1) --> true every($a1, fn($x) => $x == 2) --> false some($a1, fn($x) => $x != 1) --> true some($a1, fn($x) => $x == 2) --> true
- このコードは、PHPでArrayメソッドを実装したものです。主に、reduce、map、each、filter、sum、every関数を定義しています。
- reduce関数は、配列の要素を走査して、コールバック関数を適用し、最終的な単一の値を返します。また、第3引数に初期値を渡すこともできます。
- map関数は、配列の各要素に対してコールバック関数を適用し、新しい配列を作成して返します。
- each関数は、配列の各要素に対してコールバック関数を適用します。ただし、戻り値はありません。
- filter関数は、配列の要素を走査し、コールバック関数がtrueを返す要素を含む新しい配列を作成して返します。
- sum関数は、数値配列の合計値を返します。2つの浮動小数点数を正確に加算するために、内部で処理を行っています。
- アルゴリズムは、Kahanの加算アルゴリズムと呼ばれる方法を使っています。このアルゴリズムは、浮動小数点数の加算において、誤差を最小限に抑えるために考案されました。
- 具体的には、各要素を順番に加算する際に、現在の誤差を考慮して加算を行い、その誤差を記憶しておきます。次に、次の要素を加算する前に、前回の誤差を引いてから加算を行います。
- これにより、浮動小数点数の誤差を効果的に抑えることができます。
- この関数では、変数
$sum
に合計値を、変数$c
に現在の誤差を保持し、各要素を順番に加算していくことで、正確な合計値を得ています。
- every関数は、配列の各要素に対してコールバック関数を適用し、すべての要素がtrueを返した場合にtrueを返します。それ以外の場合は、falseを返します。
附録
[編集]チートシート
[編集]<?php // 関数定義 function 関数名 (引数1, 引数2, ...) { 処理; return 戻り値; } // 関数呼び出し $result = 関数名(引数1, 引数2, ...); // 引数のデフォルト値 function 関数名 (引数1 = 値1, 引数2 = 値2, ...) { 処理; } // 可変長引数 function 関数名 (...$args) { 処理; } // クロージャ $closure = function (引数1, 引数2, ...) { 処理; return 戻り値; }; // 無名関数の即時実行 $result = (function (引数1, 引数2, ...) { 処理; return 戻り値; })(値1, 値2, ...); // 再帰的呼び出し function 関数名 (引数) { if (条件) { return 値; } else { return 関数名(引数を更新); } } // スーパーグローバル変数 $GLOBALS $_SERVER $_GET $_POST $_FILES $_COOKIE $_SESSION $_REQUEST $_ENV // マジック定数 __LINE__ __FILE__ __DIR__ __FUNCTION__ __CLASS__ __TRAIT__ __METHOD__ __NAMESPACE__ // 名前空間 namespace 名前空間名; use 名前空間名\クラス名; クラス名::メソッド名(引数);
以上がPHPの関数に関するチートシートです。
用語集
[編集]- 関数 (function) : 名前を持ったプログラムの断片で、一連の処理をまとめて、その名前で呼び出すことができる。プログラムの構造化と再利用性を高める。
- 引数 (argument) : 関数に渡される値のこと。関数定義で宣言された引数と同じ数だけ渡す必要がある。
- 戻り値 (return value) : 関数から呼び出し元に返される値のこと。return文で指定された式の値が返り値として扱われる。
- 可変長引数 (variable-length argument) : 引数の数が可変長であることを示す機能。可変長引数を受け取る関数は、引数名の前に「...」を付ける。
- 引数のデフォルト値 (default value of argument) : 引数にデフォルト値を設定することができる。呼び出し時に引数が指定されなかった場合に、デフォルト値が使われる。
- キーワード引数 (keyword argument) : 引数を名前で指定する機能。引数名と値を「引数名 => 値」という形式で渡す。
- 型宣言 (type declaration) : 引数や戻り値の型を宣言することができる。PHP 7からサポートされた機能。
- スコープ (scope) : 変数が定義されている有効範囲のこと。スコープによって変数のアクセス可能範囲が決まる。
- ローカルスコープ (local scope) : 関数内で定義された変数の有効範囲のこと。関数の外からはアクセスできない。
- グローバルスコープ (global scope) : 関数の外で定義された変数の有効範囲のこと。どこからでもアクセスできる。
- 静的スコープ (static scope) : 関数内で定義された静的変数の有効範囲のこと。関数の呼び出し回数に依存せず、値が保持される。
- スーパーグローバル変数 (superglobal variable) : スクリプトのどこからでもアクセスできる特別な変数。$GLOBALS、$_SERVER、$_GET、$_POST、$_FILES、$_COOKIE、$_SESSION、$_REQUEST、$_ENVがある。
- 無名関数 (anonymous function): 名前を持たない関数のこと。変数に代入して使用することができる。
- クロージャ (closure): 関数が変数に代入された場合、その関数はそのまま変数として使用することができ、その変数を引数に取る関数のこと。
- 再帰的呼び出し (recursive call): 関数が自身を呼び出すこと。
- ジェネレーター関数 (generator function): 実行を一時停止し、値を返すことができる関数。
たとえば、以下のような HTML ファイルを作成し、myform.html というファイル名でドキュメントルートにアップロードします。
- myform.html
<form action="catchTest.php" method="post"> <label for="username">ユーザー名を登録:</label><input type="text" name="username"> <input type="submit" value="登録"> </form>
なお、このコードが表示するページは、
ユーザー名を登録: 登録
のようにレンダリングされます(ブラウザによって表示内容の細部は異なります)。
そして、http://localhost/formtest.html にウェブブラウザでアクセスすると、ウェブブラウザで見たときに「登録」と書いてあるボタン 登録 を押すと、ページが切り替わり、「catchTest.php」から作成された HTML ファイルのウェブページに切り替わります。
form
要素のaction
属性とmethod
属性にしたがって、処理がされているわけです。input
要素のtype
属性がtext
の場合、文字列入力欄で、上記のようなレンダリングがなされ、1 行の文字列が入力できます。input
要素のname
属性は、リクエストのインデックスになります。
しかし、これだけでは、まだ PHP ファイルがない状態です。
以下のような catchTest.php
ファイルを作成します。
- catchTest.php
<?php echo "あなたはユーザー名", $_REQUEST['username'], "で登録しました。" ; ?>
このように、$_REQUEST['名前']
で HTML フォームからの入力を受け取ることができます。もちろん、引数の名前は、HTML 側で input
要素の name
属性で定義したものでなければなりません。
先程の「登録」ボタンのあるページで、ボタンを押すと
あなたはユーザー名◯◯で登録しました。
というように、画面が PHP のページに遷移して、◯◯ の部分にインプットボックスにある文字列を入れた結果が出てきます。
※ もし遷移しない場合は、上記の操作のどれかを抜かしている可能性があります。
- localhost経由のアドレスでformtest.html にアクセスせず、直接ダブルクリックでHTMLファイルを起動してしまっていませんか?
なお、遷移先の PHP のページでブラウザのソース表示の機能を使用しても、
- (自動作成されたHTMLソースコード)
◯◯と入力されました。入力されたユーザー名を保存しています。
のように、サーバーによって生成された HTML が表示されるだけであり、元の PHP ソースコードはクライアント側には表示されない。
また、ブラウザで直接
にアクセスしても、
- (ウェブブラウザのメイン画面表示)
と入力されました。入力されたユーザー名を保存しています。
と表示されるだけです。
そのページのソースコードをウェブブラウザで見ても、
- (自動作成されたHTMLソースコード)
と入力されました。入力されたユーザー名を保存しています。
と表示されるだけです。
変数名などはブラウザ側では表示されないが、echo のテキスト文字列などは表示されてしまうので、機密情報を echo テキスト文字列に入れないように注意する必要があります。
念のため、サーバーアプリ作成後にはソースを見てみるのが安全です。
ファイル入出力
[編集]ファイルとHTMLフォーム
[編集]PHPには、(サーバー側にある)ファイルを読書きする機能があります。
ファイル読込み
[編集]- 例
<?php declare(strict_types=1); header("Content-Type: text/plain"); $fp = fopen("/etc/motd", "r"); if (!$fp) { die('Fail: fopen("/etc/motd", "r");'); } var_dump($fp); while ($line = fgets($fp)) { echo "> $line"; } fclose($fp);
- 実行結果(/etc/motdが開けた場合)
resource(5) of type (stream) > System Maintenance Notice > > This weekend, we will shut down at 3:00 p.m. on Saturday for system updates.
- 実行結果(/etc/motdが開けなかった場合)
PHP Warning: fopen(/etc/motd): Failed to open stream: No such file or directory in /workspace/Main.php on line 5 Fail: fopen("/etc/motd", "r");
- /etc/motd
System Maintenance Notice This weekend, we will shut down at 3:00 p.m. on Saturday for system updates.
- fopen()関数は、Cと同じ名前ですが大きく機能が拡張されており、ネットワーク上のリソースも開くことができます。
- ここではローカルファイルシステムの /etc/motd を読出し、内容を表示しようとしています。
- /etc/motd は、UNIXにログインした時に表示するメッセージが保存されているファイルです。
- fopen() に限らず、関数は戻値で成功/失敗を表しています。
- ここで失敗を無視すると、それ以後の $fp を使った処理はことごとくエラーとなり、開けなかったハンドルで fclose() するという醜態に至ります。
- var_dump()の結果から、fopen() が返す値は、リソース型だとわかります。
例外によるエラーハンドリング
[編集]- 例
<?php declare(strict_types=1); header("Content-Type: text/plain"); set_error_handler(function ($errno, $errstr, $errfile, $errline) { throw new ErrorException($errstr, 0, $errno, $errfile, $errline); }); try { $fp = fopen("/etc/motd", "r"); var_dump($fp); while ($readString = fgets($fp)) { echo "> $readString"; } fclose($fp); } catch (Exception $e) { echo sprintf("%s(%d): %s", $e->getFile(), $e->getLine(), $e->getMessage()), PHP_EOL; }
書込み
[編集]PHPをウェブサーバー上で動かしている場合のファイル書込みは、セキュリティ上の脅威に直結するので慎重になるべきです。
ここでは、ごく小さな永続オブジェクトが必要な、アクセスカウンターを例に取ります。
- 例
<?php header("Content-Type: text/plain"); $fp = fopen("counter.txt", "c+"); if (!$fp) { die('Fail: fopen("counter.txt", "c+");'); } if (flock($fp, LOCK_EX)) { $counter = (int) fgets($fp); $counter++; rewind($fp); if (fwrite($fp, $counter) === false) { print "ファイル書き込みに失敗しました"; } flock($fp, LOCK_UN); } else { echo "flock: error"; } fclose($fp); echo "COUNT: ", $counter;
- 実行結果(1)
COUNT: 1
- 実行結果(2)
COUNT: 2
- 実行結果(3)
COUNT: 3
- fopen()のモード "c+" はR/Wで開き、ファイルがなければ新規に作ります。
- flock()は、ファイルへのアクセスをアトミックにする関数です。
- ロックを掴んだあと、ファイルから1行読み出し、整数に変換しインクリメントしたあと先頭まで seek() しインクリメントした値を書込みます。
シンプルでダーティーなログインフォーム
[編集]- login.php
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="utf-8"> <title>シンプルでダーティーなログインフォーム</title> </head> <body> <?php ?> <?php if (!array_key_exists('username', $_POST)): ?> <!-- GET Method --> <form action="login.php" method="POST""> <div><label for="username">ユーザー名:</label><input type="text" name="username" id="username"></div> <div><label for="password">パスワード:</label><input type="password" name="password" id="password"></div> <input type="submit" value="ログイン"> </form> <?php elseif ($_POST['username'] == "UserID" && $_POST['password'] == "PassWord" ): ?> <!-- POST Method --> <p>ログイン成功</p> <?php else: ?> <!-- POST Method --> <p>ユーザー名 または パスワード が違います。<p> <?php endif; ?> </body> </html>
- このスクリプトは、ファーム入力とフォームの両方が含まれており
- 分岐コード
<?php if (!array_key_exists('username', $_POST)): ?>
- スーパーグローバル変数 $_POST が(間違っていないかではなく)あるのかをテストして、なければGETメソッドと判断しています。
- GETメソッドならばフォーム
<!-- GET Method --> <form action="index.php" method="POST""> <div><label for="username">ユーザー名:</label><input type="text" name="username" id="username"></div> <div><label for="password">パスワード:</label><input type="password" name="password" id="password"></div> <input type="submit" value="ログイン"> </form>
- を生成します。
- POSTメソッドならば
- ユーザー名とパスワードの両方が一致したら
<!-- POST Method --> <p>ログイン成功</p>
- を生成します。
- 片方でも一致しなければ
<!-- POST Method --> <p>ユーザー名 または パスワード が違います。<p>
- を生成します。
- (ここでどちらが不一致だったかを教えてはいけません。ブルートフォース攻撃が格段に易しくなります。)
- ファームのレンダリング例
どこがダーティーか?
[編集]このコードには以下のような欠点があり、アジャイルにはともかく実務には使えません。
- スクリプトに認証情報がハードコードされている
- サーバーに侵入されなければ、JavaScript のように丸見えにはなりませんが「サーバーに侵入されない」前提で認証関係のコードを書いてはいけません。
- メソッドを拠り所にフォームと認証を切り替えている
- 細工したユーザーエージェントからならば、メソッドを自由に切替えてチャレンジできます。
- チャレンジ回数に限界がない
- ブルートフォース攻撃に遭った時に、回数制限がないのは致命的な欠点です。
サーバーからのダウンロード
[編集]概要
[編集]PHPでダウンロードをブラウザに問いかけるには、下記のように header 関数というのを使って、ブラウザに問い掛けできます。
- コード例
<?php // 画像のパスとファイル名 (拡張子ごと) $fpath = "/var/www/html/phpgra2.png"; $fname = "phpgra2.png"; // ヘッダーの設定 header('Content-Type: application/octet-stream'); header('Content-Length: ' . filesize($fpath)); header('Content-Disposition: attachment; filename="' . $fname . '"'); // 環境によっては必要 ob_end_clean(); // 画像のダウンロード readfile($fpath); ?>
書式は
<?php $パス変数 = 'パスのアドレス'; $ファイル変数 = 'ファイル名'; header('Content-Type: application/octet-stream'); header('Content-Length: ' . filesize($パス変数)); header('Content-Disposition: attachment; filename="' . $ファイル変数 . '"'); ob_end_clean(); readfile($パス変数); ?>
です。
実験のさいには、あらかじめ画像データを作成しておいてください。
そして、ブラウザから、上記のPHPを実行します。(コマンドラインから実行しても、意味不明の文字列が表示されるだけです。)
成功すれば、ページ起動時に
- 「次のファイルを開こうとしています:」
と出て、「キャンセル」または「OK」のボタンが出てきます。
Content-Type: application/octet-stream の 「octet-stream 」は、種類を特定しないバイナリデータであることを宣言しています。画像データなどをダウンロードさせたい場合は画像ならバイナリ形式ですので、この 「octet-stream」を指定してもダウンロード可能です。
ダウンロードしたいファイルのファイル形式によっては、Content-Type で具体的に指定することもできます。
画像の場合、
- PNG画像なら image/png をつかって
Content-Type: image/png
と指定しても、かまいません。 - もしGIF画像なら image/gif で
Content-Type: image/gif
とも書けます。 - JPEG画像なら
Content-Type: image/jpeg
とも書けます。
画像以外でも、
- もしPDFをダウンロードさせるなら application/pdf のようになり、
Content-Type: application/pdf
とも書けます。 - あるいは、もしテキストファイルなら text/plain で、
Content-Type: text/plain
とも書けます。
さて、
header('Content-Disposition: attachment; filename="' . $fname . '"');
は、ダウンロードしたときのファイル名を上記コードでは $fname で指定しています。なので、ほかの名称でも構いません。たとえば
header('Content-Disposition: attachment; filename="' . "test" . '"');
とすれば、ダウンロードされたファイル名は「test」になります。
ダウンロード開始は readfile関数でなくても、 file_get_contents 関数でもダウンロード問い掛けを出来ます。
環境や、アップロードするファイルの種類によっては
ob_end_clean();
が必要です。
これがないと、ファイルにバッファ内の余計なデータがついたままブラウザに送信されてしまい、ダウンロード自体はできても、読込みエラーになってしまい、さっかくダウンロードした価値が無くなってしまいます。
実際の例
[編集]上記のコードだと、ページが表示される前にダウンロードが始まってしまう。そのため、とても見づらくなります。
上記のPHPコードにprintなどの命令を書いても、うまく動作しないです。(基本的に、ダウンロード用のリンクでは、画像表示や文字表示は、あまり機能しないです。)
実務的な方法としては、別のHTMLファイルで上記PHPコードにアクセスするリンクを配置し、
- コード例
<a href="dlTest.php">ダウンロード</a>
- (※ これはHTMLファイルです。PHPではありません)
のようにして、このHTMLファイルに先にリンクしてもらうようにするのが良い。
すると、先にこのHTMLだけが表示されます。
そして、「ダウンロード」リンクをクリックすると、ページはそのままで(このHTMLが表示されたままで)、ダウンロードのポップアップが出るので、あとはブラウザ側でユーザーにダウンロードしてもらえば済む。
PHPとデータベースとの連動
[編集]PHPは、Webアプリケーションを開発する上で欠かせないデータベースの扱いに非常に優れたプログラミング言語です。本章では、PHPと様々なRDBMSを連携させる方法について解説します。
PHPは、複数のRDBMSと連携することができます。そのための方法として、ベンダー固有モジュールと抽象化レイヤーの2つが存在します。
ベンダー固有モジュールは、ベンダーが提供する機能やデータ構造を直接PHPにマッピングすることで、RDBMSの機能を十分に活用できるという特徴があります。ただし、この方法では特定のRDBMSに依存してしまうため、別のRDBMSに移行する場合に手間がかかる可能性があります。
一方、抽象化レイヤーにはDBA、ODBC、PDOの3種類があります。これらは、RDBMSの共通機能を抽出し、機能やデータ構造を抽象化することで、移行時の手直しを最小限に抑えることができます。さらに、新しいRDBMSに対しても容易に対応できるというメリットがあります。
SQLite
[編集]SQLiteは、データベースエンジンであり、サーバーを必要とせずに利用できます。また、PHPとの相互運用性が高いため、広く利用されています。本書では、PHPとSQLiteを連携させる方法について説明します。なお、PHP5.4以降のバージョンには、SQLiteへのインタフェースが標準で付属しています。
SQLiteは、学習用途に適しており、以下のような特徴を持っています。
- MySQLのようなサーバークライアントモデルを採用していないため、アプリケーションプログラムにライブラリとしてリンクされます。
- MySQLのように、サーバーのセットアップが不要であり、ポートの開放や認証情報のセットアップ、パーミッションの整合性の維持などの手間が省けます。
- SQLiteは弱い型付けであり、MySQLのように強い型付けではありませんが、個々のフィールドに制約をかけることは可能です。
- sqlite3は、SQLite3のデータベースの保守管理を行うコマンドラインインターフェースであり、アプリケーションプログラムがリンクされるわけではありません。また、sqlite3自身も、SQLite3がリンクされたアプリケーションプログラムの一部と見なすことができます。
ベンダー固有モジュール
[編集]SQLite3
[編集]PHPは、SQLite3クラスでSQLite をサポートしています。
次のコードは、SQLite3クラスを使用して仮想メモリ上にSQLite3データベースを作成し、そのデータベースにテーブルを作成し、データを挿入し、そのデータを取得して表示するものです。
- 例
<?php // SQLite3クラスを使用したデータベースハンドリング header("Content-Type: text/plain"); $prefs = [ [01, "北海道", "Hokkaido"], [02, "青森県", "Aomori"], [03, "岩手県", "Iwate"], ]; $db = new SQLite3(":memory:"); $db->exec( "CREATE TABLE 都道府県コード一覧表 (都道府県コード INTEGER, 都道府県 STRING, prefectures STRING)" ); try { $db->enableExceptions(true); $insert = $db->prepare( "INSERT INTO 都道府県コード一覧表 VALUES (:n,:ja,:en)" ); foreach ($prefs as $pref) { $insert->bindParam(":n", $pref[0]); $insert->bindParam(":ja", $pref[1]); $insert->bindParam(":en", $pref[2]); $insert->execute(); } } catch (Exception $e) { echo sprintf("%s(%d): %s", $e->getFile(), $e->getLine(), $e->getMessage()), PHP_EOL; } $insert = null; $query = "SELECT * FROM 都道府県コード一覧表;"; echo "$query", PHP_EOL; $result = $db->query($query); while ($assoc = $result->fetchArray(SQLITE3_ASSOC)) { array_walk($assoc, function (&$v, $k) { $v = "$k:$v"; }); $v = null; echo implode(",", $assoc), PHP_EOL; } echo PHP_EOL;
- 実行結果
SELECT * FROM 都道府県コード一覧表; 都道府県コード:1, 都道府県:北海道, prefectures:Hokkaido 都道府県コード:2, 都道府県:青森県, prefectures:Aomori 都道府県コード:3, 都道府県:岩手県, prefectures:Iwate
$prefs
という都道府県の配列を定義します。それぞれの都道府県は、都道府県コード、日本語名、英語名の3つの要素を持っています。new SQLite3(":memory:")
という記述により、仮想メモリ上にSQLite3データベースを作成します。$db->exec()
メソッドを使用して、上記のデータベース内に都道府県コード一覧表
というテーブルを作成します。このテーブルは、都道府県コード、日本語名、英語名の3つのカラムを持っています。try-catch
文を使用して、例外処理を有効にします。これにより、後続の処理で発生した例外をキャッチし、エラーメッセージを出力することができます。$db->prepare()
メソッドを使用して、INSERT文を準備します。ここで、:n
、:ja
、:en
というプレースホルダを使用しています。プレースホルダは、後続のbindParam()
メソッドで値をバインドすることができます。foreach
文を使用して、$prefs
配列の要素を1つずつ取り出し、bindParam()
メソッドを使用して、プレースホルダに値をバインドします。そして、execute()
メソッドを使用して、SQL文を実行してテーブルにデータを挿入します。$query
という文字列変数に、SELECT * FROM 都道府県コード一覧表;
というSQL文を格納します。$db->query()
メソッドを使用して、上記のSQL文を実行し、その結果を$result
変数に格納します。while
ループを使用して、$result
から1行ずつデータを取り出し、$assoc
変数に連想配列形式で格納します。その後、array_walk()
関数を使用して、各要素の値の前にキーを付加します。implode()
関数を使用して、連想配列をカンマ区切りの文字列に変換し、それを表示します。最後に、改行文字を出力します。
- 機能的には不足はないのですが、次に示すPDOバージョンの方が、それを元に他のRDBMSにも対応できるので、より汎用的です。
- SQLite3クラスとPDOを比較してみてください。
抽象化レイヤー
[編集]PDO
[編集]PDO(PHP Data Objects)は、PHPの標準的なデータベースアクセスレイヤーであり、データベースサーバーに対して安全な、拡張性のある接続を提供します。 PDOは、MySQL、PostgreSQL、SQLite、Microsoft SQL Serverなどの主要なデータベースサーバーと互換性があります。 PDOは、オブジェクト指向のAPIを提供します(手続き指向のAPIはありません)。
- PDOのインストール
- PDOは、PHP 5.1以降で標準的にインストールされています。ただし、PDOを使用するには、適切なデータベースドライバーがインストールされている必要があります。たとえば、MySQLを使用する場合は、mysqlドライバーが必要です。通常、これらのドライバーはPHP拡張機能として提供されています。
次のコードは、PHPのPDO(PHP Data Objects)を使用してSQLiteデータベースを操作する方法を示しています。 具体的には、都道府県のコードと名前をSQLiteデータベースに格納し、格納されたデータを表示するためのものです。
- 例
<?php // PDOを使用したデータベースハンドリング header('Content-Type: text/plain'); $prefs = [ [01, "北海道", "Hokkaido"], [02, "青森県", "Aomori"], [03, "岩手県", "Iwate"], ]; $db = new PDO("sqlite::memory:"); $db->exec( "CREATE TABLE 都道府県コード一覧表 (都道府県コード INTEGER, 都道府県 STRING, prefectures STRING)" ); $insert = $db->prepare("INSERT INTO 都道府県コード一覧表 VALUES (:n,:ja,:en)"); $db->beginTransaction(); try { foreach ($prefs as $pref) { $insert->bindParam(":n", $pref[0]); $insert->bindParam(":ja", $pref[1]); $insert->bindParam(":en", $pref[2]); $insert->execute(); } $db->commit(); } catch (PDOException $e) { echo $e; $db->rollback(); throw $e; } $insert = null; foreach ( $db->query("SELECT * FROM 都道府県コード一覧表")->fetchAll(PDO::FETCH_ASSOC) as $assoc ) { echo implode( ", ", array_map( fn($k, $v) => "$k:$v", array_keys($assoc), array_values($assoc) ) ); echo PHP_EOL; } ?>
- 実行結果
都道府県コード:1, 都道府県:北海道, prefectures:Hokkaido 都道府県コード:2, 都道府県:青森県, prefectures:Aomori 都道府県コード:3, 都道府県:岩手県, prefectures:Iwate
$prefs
という配列を作成して、都道府県コード、日本語名、英語名の3つの要素を持つ都道府県のリストを格納しています。$db
というPDOインスタンスを作成して、sqlite::memory:
を引数に渡すことで、メモリ上にSQLiteデータベースを作成します。$db->exec()
メソッドを使用して、CREATE TABLE
ステートメントを実行して、都道府県情報を格納するためのテーブルを作成します。$insert
というPDOステートメントを作成して、INSERT INTO
ステートメントを準備します。このステートメントは、都道府県情報をテーブルに挿入するために使用されます。$db->beginTransaction()
メソッドを呼び出して、トランザクションを開始します。foreach
ループを使用して、配列$prefs
の各要素を順に取り出し、$insert
ステートメントを実行します。これにより、都道府県情報がテーブルに挿入されます。$db->commit()
メソッドを呼び出して、トランザクションをコミットして、変更を確定します。もしエラーが発生した場合は、$db->rollback()
メソッドを呼び出して、トランザクションをロールバックします。$insert
ステートメントを null に設定して、PDOステートメントを解放します。foreach
ループを使用して、SELECT
ステートメントを実行して、テーブルに格納された都道府県情報を取得します。取得した情報は、PDO::FETCH_ASSOC
モードで連想配列に変換されます。array_map()
関数を使用して、各連想配列の要素について、キーと値をペアにした文字列を生成します。implode()
関数を使用して、各連想配列の要素をカンマで区切って、1行に表示します。PHP_EOL
定数を使用して、各行の最後に改行を追加します。
- PDOを使い抽象化することで、SQLiteとMySQLの違いを吸収しています。
- queryメソッドではなく、prepareメソッドを使っているのは名前付きプレースホルダーを使いたかったためで、これはSQLインジェクションへの配慮です。
ウェブレンダリング
[編集]同内容で、HTMLに出力してみましょう。
- 例
<?php // PDOを使用したデータベースハンドリング // phpinfo(); declare(strict_types=1); header('Content-Type: text/html'); $prefs = [ [01, "北海道", "Hokkaido"], [02, "青森県", "Aomori"], [03, "岩手県", "Iwate"], ]; $db = new PDO("sqlite::memory:"); $db->exec( "CREATE TABLE 都道府県コード一覧表 (都道府県コード INTEGER, 都道府県 STRING, prefectures STRING)" ); $insert = $db->prepare("INSERT INTO 都道府県コード一覧表 VALUES (:n,:ja,:en)"); $db->beginTransaction(); try { foreach ($prefs as $pref) { $insert->bindParam(":n", $pref[0]); $insert->bindParam(":ja", $pref[1]); $insert->bindParam(":en", $pref[2]); $insert->execute(); } $db->commit(); } catch (PDOException $e) { echo $e; $db->rollback(); throw $e; } $insert = null; ?> <table class=wikitable> <caption>都道府県コード一覧表</caption> <tr><?php foreach ($db->query("PRAGMA table_info('都道府県コード一覧表')")->fetchAll(PDO::FETCH_ASSOC) as $assoc) : ?><th><?= $assoc['name'] ?><?php endforeach ?></tr> <?php foreach ($db->query("SELECT * FROM 都道府県コード一覧表")->fetchAll(PDO::FETCH_ASSOC) as $assoc) : ?> <tr><?php foreach (array_values($assoc) as $value) { ?><td><?= $value ?><?php } ?></tr> <?php endforeach ?> </table>
- 実行結果
<table class=wikitable> <caption>都道府県コード一覧表</caption> <tr><th>都道府県コード<th>都道府県<th>prefectures</tr> <tr><td>1<td>北海道<td>Hokkaido</tr> <tr><td>2<td>青森県<td>Aomori</tr> <tr><td>3<td>岩手県<td>Iwate</tr> </table>
- レンダリング結果
都道府県コード | 都道府県 | prefectures |
---|---|---|
1 | 北海道 | Hokkaido |
2 | 青森県 | Aomori |
3 | 岩手県 | Iwate |
- この例では、見出し語をそのままテーブルのフィールド名にしているのでスキーマーの情報をそのままレンダリングに使っていますが、フィールド名を英数字に限定している場合は、フィールド名⇒見出し語のテーブルを作り、その情報でイテレーションすると良いでしょう。
MySQL
[編集]PHPとMySQLを連携するには、MySQLのインストールとセットアップが必要です。
ベンダー固有モジュール
[編集]MySQLi
[編集]MySQLiは、PHPからMySQLデータベースにアクセスするための拡張モジュールです。MySQLiは、以前のMySQLに比べて、より高度なセキュリティ機能、トランザクションサポート、ストアドプロシージャのサポート、エラー処理機能などを提供しています。MySQLiは、MySQL 4.1以上で提供される機能を利用できるようにするために開発されました。
MySQLiは、PHP本体とは別にインストールする必要があるため、オペレーティングシステムやディストリビューションのパッケージ管理システムでパッケージの検索・インストールを行った後にセットアップを行います。
MySQLiには、オブジェクト指向のAPIと手続き指向のAPIの2つのAPIがあります。オブジェクト指向のAPIは、MySQLiオブジェクトを使用してデータベースにアクセスするための方法を提供します。一方、手続き指向のAPIは、MySQLi関数を使用してデータベースにアクセスするための方法を提供します。どちらのAPIを使用するかは、開発者の好みやプロジェクトのニーズに応じて決定されます。
MySQLデータベースからデータを取得して表示(オブジェクト指向)
[編集]- 例
<?php $mysqli = new mysqli("localhost", "root", "", "testmysql"); foreach ( $mysqli->query("SELECT * FROM testTable")->fetch_array(MYSQLI_ASSOC) as $key => $value ) { echo "$key:$value", PHP_EOL; } $mysqli->close(); $mysqli = null; ?>
- 実行結果
原子番号:1 元素名:Hydrogen 元素記号:H
- このPHPコードは、MySQLデータベースからデータを取得して、取得したデータをキーと値の形式で表示するものです。
- まず、mysqliオブジェクトを作成し、"localhost"というホスト名、"root"というユーザ名、""というパスワード、そして"testmysql"というデータベース名でMySQLデータベースに接続しています。
- 次に、query()メソッドを使用して、"SELECT * FROM testTable"というSQL文を実行し、その結果をfetch_array()メソッドで取得しています。fetch_array()メソッドは、取得したデータを配列で返します。また、MYSQLI_ASSOCを引数として渡すことで、連想配列として取得できます。
- その後、foreachループを使用して、取得した連想配列の各要素を順番に取り出し、キーと値を表示しています。最後に、mysqliオブジェクトをclose()メソッドでクローズし、nullを代入してメモリリークを防止しています。
- 全レコードの読取り
<?php $mysqli = new mysqli("localhost", "root", "", "testmysql"); foreach ( $mysqli->query("SELECT * FROM testTable")->fetch_all(MYSQLI_ASSOC) as $assoc ) { foreach ($assoc as $key => $value) { echo "$key:$value", PHP_EOL; } } $mysqli->close(); $mysqli = null; ?>
- 実行結果
原子番号:1 元素名:Hydrogen 元素記号:H 原子番号:2 元素名:Helium 元素記号:
MySQLデータベースからデータを取得して表示(手続き指向)
[編集]- 例
<?php $connect = mysqli_connect("localhost", "root", "", "testmysql"); $result = mysqli_query($connect, "SELECT * FROM testTable"); foreach (mysqli_fetch_array($result, MYSQLI_ASSOC) as $key => $value) { echo "$key:$value", PHP_EOL; } ?>
- 実行結果
原子番号:1 元素名:Hydrogen 元素記号:H
- オブジェクト指向版に比べて、手続き指向版のほうがメソッドチェインを使えない関係で中間変数が増えコードが長くなる傾向にあります。
- 全レコードの読取り
<?php $connect = mysqli_connect("localhost", "root", "", "testmysql"); $result = mysqli_query($connect, "SELECT * FROM testTable"); foreach (mysqli_fetch_all($result, MYSQLI_ASSOC) as $assoc) { foreach ($assoc as $key => $value) { echo "$key:$value", PHP_EOL; } } ?>
- 実行結果
原子番号:1 元素名:Hydrogen 元素記号:H 原子番号:2 元素名:Helium 元素記号:
抽象化レイヤー
[編集]PDO
[編集]- 例
<?php $pdo = new PDO( "mysql:host=localhost;dbname=testmysql;charset=utf8", "root", "" ); foreach ( $pdo->query("SELECT * FROM testTable")->fetchAll(PDO::FETCH_ASSOC) as $assoc ) { foreach ($assoc as $key => $value) { echo "$key:$value", PHP_EOL; } }
- 実行結果
原子番号:1 元素名:Hydrogen 元素記号:H 原子番号:2 元素名:Helium 元素記号:
- ベンダー固有モジュール版のMySQLiには、手続き指向版のAPIがありましたが、PDOにはオブジェクト指向版しかありません。
手続き指向APIは、関数と手続きの集合で構成され、一般的にグローバルスコープで使用されます。
一方、オブジェクト指向APIは、クラスとオブジェクトの概念に基づいて構築され、複数のインスタンスを持つことができます。
以下は、手続き指向APIとオブジェクト指向APIの主な比較です。
- 再利用性
- オブジェクト指向APIは再利用性が高いです。同じクラスの複数のインスタンスを作成できます。
- 一方、手続き指向APIは再利用性が低く、同じコードを複数回書く必要がある場合があります。
- 可読性
- オブジェクト指向APIは通常、より読みやすく、理解しやすいコードを生成します。クラスとメソッドの名前は意味を持ち、コードの構造は明確であるため、開発者がコードをより迅速に理解できます。
- 一方、手続き指向APIは、複数のグローバル関数を組み合わせた複雑なコードを生成することがあり、可読性が低い場合があります。
- 拡張性
- オブジェクト指向APIは、クラスを継承して新しいクラスを作成することができ、継承、多重継承、ポリモーフィズムなどの機能があります。これにより、コードをより簡単に拡張できます。
- 一方、手続き指向APIは、コードを機能ごとに分割して再利用することができますが、新しい関数を作成するためには手順を実行する必要がある場合があり、拡張性が低い場合があります。
- メンテナンス性
- オブジェクト指向APIは、変更を加える場合にも影響を受けないコードの部分が多く、メンテナンス性が高いと言えます。
- 一方、手続き指向APIは、グローバル変数を多用するため、変更によって予期しないエラーが発生する場合があります。
オブジェクト指向
[編集]本章では、PHPにおけるオブジェクト指向プログラミングの基礎について説明します。オブジェクト指向は、現代のプログラミング言語で広く採用されているパラダイムであり、PHPでも重要な概念の一つです。
PHPのオブジェクト指向は、バージョン5から導入され、現在ではPHP 8.xまで進化を遂げています。オブジェクト指向プログラミングには、クラス、オブジェクト、継承、ポリモーフィズムなどの概念があり、これらを理解することでより柔軟かつ効率的なプログラムを作成することができます。
本章では、オブジェクト指向の基本的な概念から始め、クラスの定義、オブジェクトの生成、メソッドの呼び出し、継承などを順に説明していきます。また、実際の例を交えて、具体的なプログラムの作成方法を解説します。
オブジェクト指向は、初心者にとっては難しいと感じるかもしれませんが、一度理解してしまえば、より高度なプログラミングを行うことができます。本章を通じて、オブジェクト指向プログラミングについてしっかりと理解し、PHPのプログラミング能力を向上させてください。
PHPのオブジェクト指向
[編集]PHPのオブジェクト指向の特徴
[編集]PHPのオブジェクト指向の主な特徴は以下の通りです。
- クラスとオブジェクトのサポート:PHPはクラスとオブジェクトをサポートしており、オブジェクト指向プログラミングが可能です。クラスを定義することで、オブジェクトを生成し、オブジェクトに対してメソッドを呼び出したりプロパティを設定したりできます。
- カプセル化:PHPでは、クラスのプロパティやメソッドをカプセル化することができます。つまり、クラスの内部で定義したメソッドやプロパティは、外部から直接アクセスできなくなります。代わりに、公開されたメソッドを介してアクセスする必要があります。これにより、コードの再利用性や保守性が向上します。
- 継承:PHPでは、継承をサポートしています。つまり、既存のクラスを拡張して、新しいクラスを作成することができます。継承を使うことで、重複するコードを避け、プログラムの保守性を向上させることができます。なお、多重継承はできません。
- 多態性:PHPでは、多態性をサポートしています。つまり、同じメソッド名で異なるクラスのオブジェクトに対して、異なる処理を行うことができます。これにより、コードの再利用性や柔軟性が向上します。
- 抽象クラスとインターフェース:PHPでは、抽象クラスやインターフェースをサポートしています。抽象クラスは、具象クラスの共通の機能をまとめるために使用されます。インターフェースは、実装すべきメソッドを定義するために使用され、異なるクラスで同じインターフェースを実装することができます。
- 名前空間:PHP 5.3 以降では、名前空間という機能が導入され、クラス名や関数名の重複を回避することができるようになりました。これにより、大規模なプログラムでも、クラスや関数の名前が重複することを回避できます。
PHPのオブジェクト指向の長所短所
[編集]PHPのオブジェクト指向プログラミングの長所と短所については以下の通りです。
【長所】
- 再利用性が高くなる: クラスのインスタンスを生成し、メソッドを呼び出すことで処理を行うため、同じような処理を複数回実装する必要がなくなり、コードの再利用性が高くなります。
- 保守性が高くなる: クラスという単位でコードがまとまるため、処理の追加・変更が容易になります。また、変更時にクラス内の変更が終われば、他のコードに影響を与えないようになります。
- 拡張性が高くなる: クラスに新たなメソッドやプロパティを追加することで、機能の追加が容易になります。また、新しいクラスを継承して新たなクラスを作成することで、既存のクラスを拡張することができます。
【短所】
- 性能に影響が出る: オブジェクト指向プログラミングは、クラスのインスタンス生成やメソッドの呼び出しなど、多くの処理が発生するため、その分性能に影響が出ることがあります。
学習コストが高くなる: オブジェクト指向プログラミングは、手続き型プログラミングに比べて、抽象的な概念が多く登場するため、学習コストが高くなることがあります。
- デバッグが困難になる: クラスの関係性が複雑になると、どこでバグが発生しているのかを特定するのが難しくなります。また、オブジェクト指向プログラミングの特徴である「継承」という仕組みがあるため、継承関係を把握しなければならない場合があります。
Hello World!:オブジェクト指向版
[編集]- hello.php
<?php class Hello { private string $who; public function __construct(string $who = "World") { $this->who = $who; } public function greet() : void { echo "Hello {$this->who}!", PHP_EOL; } } $hello = new Hello; $hello->greet(); $universe = new Hello("Universe"); $universe->greet(); ?>
- 実行結果
Hello World! Hello Universe!
クラス Hello
は、private
アクセス修飾子を持つ$who
プロパティを持ち、__construct()
メソッドで初期化されます。__construct()
メソッドは、クラスがインスタンス化されたときに呼び出され、引数 $who
が渡された場合は、それを$this->who
に設定します。引数が渡されない場合は、デフォルトで "World" を設定します。
greet()
メソッドは、インスタンスの $who
プロパティを使用して、 "Hello {who}!" というメッセージを表示します。PHP_EOL
は、プラットフォームに依存しない改行コードです。
次に、$hello
という名前の Hello
クラスのインスタンスを作成し、greet()
メソッドを呼び出します。これにより、「Hello World!」が表示されます。次に、$universe
という名前の別の Hello
クラスのインスタンスを作成し、greet()
メソッドを呼び出します。これにより、「Hello Universe!」が表示されます。
コンストラクターとアクセス修飾子とreadonlyプロパティ
[編集]コンストラクターとプロパティは、クラスにおいて重要な役割を果たします。コンストラクターは、インスタンス化されたオブジェクトの初期化を行うためのメソッドであり、プロパティはオブジェクトのデータを表すためのメンバー変数のようなものです。
アクセス修飾子は、コンストラクターとプロパティのアクセスレベルを定義するために使用されます。アクセス修飾子は、public、private、protectedの3つがあり、それぞれ以下のような意味を持ちます。
- public:どのクラスからでもアクセス可能
- private:同じクラス内からのみアクセス可能
- protected:同じクラス内およびその派生クラスからのみアクセス可能
また、プロパティの値を初期化後に変更できないようにするには、readonly修飾子を使用することができます。readonly修飾子は、一度設定された値を変更できないプロパティを定義する場合に便利です。
これらの修飾子を適切に使うことで、コードの保守性や安全性を高めることができます
- hello2.php
<?php class Hello2 { public function __construct( public readonly string $who = "World" ) {} public function greet() { echo "Hello {$this->who}!", PHP_EOL; } } $hello = new Hello2(); $hello->greet(); $universe = new Hello2("Universe"); $universe->greet(); echo '$hello->who --> ', $hello->who, PHP_EOL; echo '$universe->who --> ', $universe->who, PHP_EOL; // $universe->who = "Japon"; // PHP Fatal error: Uncaught Error: Cannot modify readonly property Hello::$who in /workspace/Main.php:17 ?>
- 実行結果
Hello World! Hello Universe! $hello->who --> World $universe->who --> Universe
このコードは、PHP 8から導入された、コンストラクター引数にアクセス修飾子を組み合わせてプロパティを定義することができる機能を使用しています。また、readonly修飾子を使用して、値を一度設定した後に変更できないようにプロパティを定義しています。
具体的には、Hello2クラスでは、コンストラクターの引数として、public修飾子とreadonly修飾子が指定されたwhoという名前のstring型のプロパティを定義しています。コンストラクターでは、このプロパティに初期値が設定されます。greetメソッドは、このプロパティの値を用いて、Helloというメッセージを出力するために使用されます。
このクラスのインスタンスを生成する際には、コンストラクターに引数を渡すことができます。Hello2のインスタンスを生成する際に引数を渡さない場合、whoプロパティの値は "World" に設定されます。引数を渡した場合は、その値がwhoプロパティに設定されます。
また、コードの最後の行では、インスタンスが生成された後にプロパティの値を取得し、出力しています。その後、whoプロパティを書き換えようとしている行がコメントアウトされています。これは、readonly修飾子を使用してwhoプロパティが一度設定された後に変更できないようにしているため、エラーが発生します。
デストラクター
[編集]デストラクターとは、オブジェクトが破棄される際に自動的に呼び出される特殊なメソッドです。デストラクターは、クラス定義内に __destruct()
という名前で定義されます。デストラクターは、インスタンスを参照する変数が1つもなくなると自動的に呼び出され、そのインスタンスに関連する資源を解放するために使用されます。資源は、メモリ、ファイル、データベース接続など様々です。
また、readonly
修飾子を使用して、初期値から変更できないプロパティを作成することもできます。以下は、readonly
修飾子を使った例です。
- destruct.php
<?php class MyClass { function __destruct() { echo __CLASS__, " done!", PHP_EOL; } } echo '$obj = new MyClass()', PHP_EOL; $obj = new MyClass(); echo '$o2 = $obj', PHP_EOL; $o2 = $obj; echo '$obj = null', PHP_EOL; $obj = null; echo '$o2 = null', PHP_EOL; $o2 = null; echo __FILE__, " done!", PHP_EOL; ?>
- 実行結果
$obj = new MyClass() $o2 = $obj $obj = null $o2 = null MyClass done! /workspace/Main.php done!
このコードでは、クラス MyClass
のインスタンスが作成され、参照が $obj
で保持されます。
その後、$obj
の複製が $o2
に作成され、参照がコピーされます。
次に、$obj
に null
が代入されることで、$obj
からの参照はなくなりますが、$o2
からの参照は残っています。
最後に、$o2
に null
が代入されることで、すべての参照がなくなります。
その結果、MyClass::__destruct()
が暗黙的に呼び出されます。
__destruct()
は、オブジェクトが破棄される際に自動的に呼び出される特殊なメソッドであり、この例ではクラス名と "done!" という文字列が出力されます。
動的プロパティの非推奨と __get と __set
[編集]PHPには、動的プロパティ(dynamic properties)という機能があります。これは、オブジェクトのプロパティを実行時に定義することができる機能です。しかし、動的プロパティは1つのタイポで意図しないプロパティを作ってしまい、非常に危険であり、プログラムのメンテナンス性を低下させる可能性があります。そのため、PHPでは動的プロパティの使用を非推奨としており、代わりに __get() と __set() メソッドを使用することを推奨しています。
__get() メソッドは、オブジェクトのプロパティにアクセスする際に、存在しないプロパティにアクセスした場合に呼び出されます。このメソッドを実装することで、存在しないプロパティにアクセスされた場合に特定の処理を行うことができます。
__set() メソッドは、オブジェクトのプロパティに値を設定する際に、存在しないプロパティに値を設定しようとした場合に呼び出されます。このメソッドを実装することで、存在しないプロパティに値を設定された場合に特定の処理を行うことができます。
これらのメソッドを使用することで、動的プロパティの使用を避け、プログラムのメンテナンス性を向上させることができます。
動的プロパティ
[編集]- 動的プロパティの例
<?php class Simple { public int $prop1 = 1; } $instance = new Simple(); echo "\$instance->prop1 --> {$instance->prop1}", PHP_EOL; var_dump(get_object_vars($instance)); echo PHP_EOL; $instance->prop42 = 42; echo "\$instance->prop42 --> {$instance->prop42}", PHP_EOL; var_dump(get_object_vars($instance)); ?>
- 実行結果
$instance->prop1 --> 1 array(1) { ["prop1"]=> int(1) } $instance->prop42 --> 42 array(2) { ["prop1"]=> int(1) ["prop42"]=> int(42) }
このコードは、クラス Simple を定義し、その中にプロパティ $prop1 を宣言しています。その後、インスタンス $instance を作成し、そのプロパティ $prop1 にアクセスして値を表示し、get_object_vars() 関数を使用して、オブジェクトのプロパティの一覧を表示しています。この結果、$instance->prop1 の値が 1 で、get_object_vars() 関数が $prop1 を返していることがわかります。
次に、$instance->prop42 というプロパティに値 42 を設定して、その値を表示し、再度 get_object_vars() 関数を使用して、オブジェクトのプロパティの一覧を表示しています。この結果、$instance->prop42 の値が 42 で、get_object_vars() 関数が $prop1 と $prop42 を返していることがわかります。
このように、PHPでは、既存のプロパティの他に、動的に新しいプロパティを追加することもできます。また、get_object_vars() 関数を使用することで、オブジェクトのプロパティを一覧できます。
__get と __set
[編集]__setと__getは動的プロパティへの代入と参照をインターセプトする特殊関数で、いわゆるアクセサです。
- __get と __set
<?php class Simple { public int $prop1 = 1; private $ary = array(); public function __set($name, $value) { echo __METHOD__, "($name, $value)" . PHP_EOL; $this->ary[$name] = $value; } public function __get($name) { $value = $this->ary[$name]; echo __METHOD__, "($name) = $value" . PHP_EOL; return $value; } } $instance = new Simple(); echo "\$instance->prop1 --> {$instance->prop1}", PHP_EOL; var_dump(get_object_vars($instance)); $instance->prop42 = 42; echo "\$instance->prop42 --> {$instance->prop42}", PHP_EOL; var_dump(get_object_vars($instance)); ?>
- 実行結果
$instance->prop1 --> 1 array(1) { ["prop1"]=> int(1) } Simple::__set(prop42, 42) Simple::__get(prop42) = 42 $instance->prop42 --> 42 array(1) { ["prop1"]=> int(1) }
- 動的プロパティとして振舞っているのは、array型(連想配列)のプロパティ ary のキーと値のペアで、いまは echo しているだけですが、受入検査を行ったり例外を上げたりするフックとして有用です。
このコードは、クラス Simple を定義し、その中で __get() メソッドと __set() メソッドを実装しています。
__get() メソッドは、オブジェクトのプロパティにアクセスする際に、存在しないプロパティにアクセスした場合に呼び出されます。このメソッドを実装することで、存在しないプロパティにアクセスされた場合に特定の処理を行うことができます。この例では、プロパティ名をキーとして配列 $ary から値を取得しています。
__set() メソッドは、オブジェクトのプロパティに値を設定する際に、存在しないプロパティに値を設定しようとした場合に呼び出されます。このメソッドを実装することで、存在しないプロパティに値を設定された場合に特定の処理を行うことができます。この例では、プロパティ名をキーとして配列 $ary に値を設定しています。
クラス Simple のインスタンスを作成し、そのプロパティにアクセスすると、__get() メソッドが呼び出されます。最初にアクセスしたプロパティは $prop1 であり、存在するプロパティであるため、その値が表示されます。また、get_object_vars() 関数を使用して、オブジェクトのプロパティを取得し、配列として表示しています。
次に、存在しないプロパティである $prop42 に値を設定します。このとき、__set() メソッドが呼び出され、配列 $ary に値が設定されます。その後、再び $prop42 にアクセスすると、__get() メソッドが呼び出され、配列 $ary から値が取得され、表示されます。また、get_object_vars() 関数を使用して、オブジェクトのプロパティを取得し、配列として表示しています。このとき、配列 $ary の内容も表示されます。
object と array の相互変換
[編集]object (の公開プロパティ)を array(連想配列)に変換したり、array(連想配列)を object に変換することができます。 これは、「オブジェクトの配列」を使いたい場合、簡素に表現することを可能にします。
- arrayのarrayからobjectのarrayへの変換
<?php const vect = [ ["name" => "Tom", "age" => 18], ["name" => "Joe", "age" => 16], ["name" => "Sam", "age" => 10], ]; echo "配列の要素のハッシュを全て表示", PHP_EOL; echo ' implode(", ", array_map(fn($key) => "$key:$hash[$key]", array_keys($hash)))', PHP_EOL; foreach (vect as $hash) { echo implode(", ", array_map(fn($key) => "$key:$hash[$key]", array_keys($hash))), PHP_EOL; } echo PHP_EOL; $objs = array_map(fn($hash): object => (object) $hash, vect); echo "配列の要素のオブジェクトを全て表示", PHP_EOL; echo ' "name:{$obj->name}, age:{$obj->age}"', PHP_EOL; foreach ($objs as $obj) { echo "name:{$obj->name}, age:{$obj->age}", PHP_EOL; } echo PHP_EOL; echo "配列の要素のオブジェクトを全てハッシュに再変換して表示", PHP_EOL; echo ' $hash = (array)$obj; implode(", ", array_map(fn($key) => "$key:$hash[$key]", array_keys($hash)))', PHP_EOL; foreach ($objs as $obj) { $hash = (array)$obj; echo implode(", ", array_map(fn($key) => "$key:$hash[$key]", array_keys($hash))), PHP_EOL; } ?>
- 実行結果
配列の要素のハッシュを全て表示 implode(", ", array_map(fn($key) => "$key:$hash[$key]", array_keys($hash))) name:Tom, age:18 name:Joe, age:16 name:Sam, age:10 配列の要素のオブジェクトを全て表示 "name:{$obj->name}, age:{$obj->age}" name:Tom, age:18 name:Joe, age:16 name:Sam, age:10 配列の要素のオブジェクトを全てハッシュに再変換して表示 $hash = (array)$obj; implode(", ", array_map(fn($key) => "$key:$hash[$key]", array_keys($hash))) name:Tom, age:18 name:Joe, age:16 name:Sam, age:10
このコードは、配列とオブジェクトの相互変換の例を示しています。
配列 vect
は、3つの要素を持ち、それぞれが name
と age
というキーを持つハッシュです。最初の foreach
ループでは、vect
の要素を一つずつ取り出して、array_map
関数を使って各ハッシュのキーと値を連結した文字列を作成し、それを implode
関数を使ってカンマで区切った一つの文字列にまとめて出力しています。
次に、array_map
関数と無名関数を使って、vect
の各要素をオブジェクトに変換しています。このとき、array_map
関数の第二引数に渡している無名関数では、(object)
キャストを使って、ハッシュをオブジェクトに変換しています。このようにすることで、各オブジェクトは name
プロパティと age
プロパティを持つようになります。
2つ目の foreach
ループでは、配列 $objs
の各要素を取り出して、name
プロパティと age
プロパティを連結した文字列を作成し、それを出力しています。最後に、各オブジェクトを array
キャストを使って再びハッシュに変換し、キーと値を連結して文字列にして出力しています。
object はイテレーション可能
[編集]PHPの object は array と同じくイテレーション可能です。
object は、foreach
で反復可能です。
クラスの継承に当たっては、可視性のコントロールに注意が必要です。
- object はイテレーション可能
<?php declare(strict_types=1); header("Content-Type: text/plain"); class Base { public $basePub1 = 'bPub 1'; protected $basePro1 = 'bPro 1'; private $basePri1 = 'bPri 1'; function showAll() { echo __METHOD__, PHP_EOL; foreach ($this as $key => $value) { echo "$key => $value", PHP_EOL; } } } $base = new Base(); foreach ($base as $key => $value) { echo "$key => $value", PHP_EOL; } echo PHP_EOL; $base->showAll(); class Sub extends Base { public $subPub1 = 'sPub 1'; protected $subPro1 = 'sPro 1'; private $subPri1 = 'sPri 1'; function subShowAll() { echo __METHOD__, PHP_EOL; foreach ($this as $key => $value) { echo "$key => $value", PHP_EOL; } } } echo PHP_EOL; echo '---- class Sub ----', PHP_EOL; $sub = new Sub(); foreach ($sub as $key => $value) { echo "$key => $value", PHP_EOL; } echo PHP_EOL; $sub->subShowAll(); echo PHP_EOL; $sub->showAll();
- 実行結果
basePub1 => bPub 1 Base::showAll basePub1 => bPub 1 basePro1 => bPro 1 basePri1 => bPri 1 ---- class Sub ---- basePub1 => bPub 1 subPub1 => sPub 1 Sub::subShowAll basePub1 => bPub 1 basePro1 => bPro 1 subPub1 => sPub 1 subPro1 => sPro 1 subPri1 => sPri 1 Base::showAll basePub1 => bPub 1 basePro1 => bPro 1 basePri1 => bPri 1 subPub1 => sPub 1 subPro1 => sPro 1
- クラスの外からは、public なプロパティしかみえません。
- クラスのメソッドからは、すべてのプロパティが見える。
- 派生クラスのメソッドから基底クラスのプロパティは、public に加え ptotected も見える。
- 派生クラスのメソッドから派生クラスのプロパティは、public だけが見える。
このコードは、クラスのプロパティにアクセスするために PHP の foreach ループを使用する方法について示しています。
また、public、protected、private プロパティへのアクセスについても説明しています。
クラス変数とクラス定数
[編集]プロパティ(インスタンス変数)は、それぞれのインスタンスを構成する要素ですが、クラスに属する変数=クラス変数、そしてクラスに属する定数=クラス定数を定義することができます。
- クラス変数とクラス定数
<?php declare(strict_types=1); header("Content-Type: text/plain"); class Math { public static int $var1 = 42; const pi = 3.14159265359; const e = 2.71828182846; public static function showVar1() { echo 'self::$var1 --> ', self::$var1, PHP_EOL; } public static function setVar1($value) { self::$var1 = $value; } public static function showPI() { echo "self::pi --> ", self::pi, PHP_EOL; } } echo "クラス変数の参照と更新", PHP_EOL; echo 'Math::$var1 --> ', Math::$var1, PHP_EOL; echo 'Math::$var1 = 4423;', PHP_EOL; Math::$var1 = 4423; echo 'Math::$var1 --> ', Math::$var1, PHP_EOL; echo "メソッド経由のクラス変数の参照と更新", PHP_EOL; echo "Math::showVar1():", PHP_EOL; Math::showVar1(); echo "Math::setVar1(123);", PHP_EOL; Math::setVar1(123); echo "Math::showVar1():", PHP_EOL; Math::showVar1(); echo "クラス定数の参照", PHP_EOL; echo "Math::pi --> ", Math::pi, PHP_EOL; echo "Math::e --> ", Math::e, PHP_EOL; echo "メソッド経由のクラス定数の参照と更新", PHP_EOL; echo "Math::showPI():", PHP_EOL; Math::showPI();
- 実行結果
クラス変数の参照と更新 Math::$var1 --> 42 Math::$var1 = 4423; Math::$var1 --> 4423 メソッド経由のクラス変数の参照と更新 Math::showVar1(): self::$var1 --> 4423 Math::setVar1(123); Math::showVar1(): self::$var1 --> 123 クラス定数の参照 Math::pi --> 3.14159265359 Math::e --> 2.71828182846 メソッド経由のクラス定数の参照と更新 Math::showPI(): self::pi --> 3.14159265359
- クラス変数
- プロパティ(インスタンス変数)と構文は似ていますが、キーワード
static
を伴っています。 - 同じクラスのメソッドから
self::$変数名
の構文で参照できます(代入の左辺にもできます)。 - クラス定義の外からは、
クラス名::$変数名
の構文で参照できます(代入の左辺にもできます)。 - クラス定数
- グローバルスコープや関数スコープの定数と構文は同じですが、class定義内で定義されていることが違います。
- static修飾子は不要です。
- 同じクラスのメソッドから
self::定数名
の構文で参照できます。 - 代入の左辺にはできません。
- クラス定義の外からは、
クラス名::定数名
の構文で参照できます。 - staticメソッド
- メソッドの定義が static 修飾子を伴っていると、それはstaticメソッドです。
- 通常のメソッドは、アロー構文
$インスタンス = new クラス(引数); $インスタンス->メソッド(引数)
を使って呼出しますが、staticメソッドはこれに加えクラス::メソッド(引数)
のインスタンスを経由しない呼出しもできます。 - staticメソッドは、
$this
を参照できません(インスタンス経由ではない…可能性があるので)。 - staticメソッドは、
self
で自分のクラスを参照できます。
このコードは、クラス定数とクラス変数についての基本的な使い方を示しています。
まず、クラス Math
が定義され、クラス変数 var1
とクラス定数 pi
と e
が定義されています。var1
は public static
で宣言されているため、クラスから直接アクセスできます。また、showVar1()
メソッドと setVar1()
メソッドがあり、それぞれクラス変数 var1
を参照して更新します。
まず、Math::$var1
で var1
の値を取得し、それを更新します。次に、showVar1()
メソッドを呼び出して、クラス変数 var1
の値を表示し、setVar1()
メソッドを使用して値を変更し、再度 showVar1()
メソッドを呼び出して、変更された値を表示します。
次に、クラス定数 pi
と e
の値を表示し、showPI()
メソッドを呼び出して、クラス定数 pi
の値を表示します。
最後に、self
キーワードを使用して、クラス変数とクラス定数にアクセスする方法を示しています。self::$var1
でクラス変数にアクセスし、self::pi
でクラス定数にアクセスします。これらの値を echo
ステートメントで出力します。
マジックメソッド
[編集]__ からはじまるメソッドは、マジックメソッドといい処理系が特定のタイミングで暗黙に呼出します。 このまでに紹介した、コンストラクターもデストラクターもマジックメソッドです。
他にも以下のようなマジックメソッドがあります。
- __get($name):プロパティにアクセスしようとしたときに呼び出される。
- __set($name, $value):プロパティに代入されたときに呼び出される。
- __isset($name):isset() 関数が、指定されたプロパティが存在するかどうかをチェックするときに呼び出される。
- __unset($name):unset() 関数が、指定されたプロパティを破棄するときに呼び出される。
- __call($name, $arguments):オブジェクトのメソッドが存在しない場合に呼び出される。
- __toString():オブジェクトを文字列に変換する場合に呼び出される。
- __invoke($arguments):オブジェクトを関数として呼び出す場合に呼び出される。
これらのマジックメソッドは、クラス内で実装することができます。マジックメソッドを使うことで、より柔軟なオブジェクト指向プログラミングを行うことができます。ただし、適切に使わないとコードが複雑になってしまう場合もあるので、注意が必要です。
__toString
[編集]__toStringは、文字列を必要とする文脈にオブジェクトがあると呼出されます。
- 文字列化メソッド
<?php declare(strict_types=1); header("Content-Type: text/plain"); class Hello3 { public function __construct( public readonly string $who = "World" ) {} public function __toString() { return "Hello {$this->who}!"; } } $hello = new Hello3(); echo $hello, PHP_EOL; $universe = new Hello3("Universe"); echo $universe, PHP_EOL;
- 実行結果
Hello World! Hello Universe!
__get() と __set()
[編集]__get()
と __set()
は、PHPにおけるマジックメソッドの一種で、オブジェクトが存在しないプロパティにアクセスした際に呼び出されます。
__get()
は、存在しないプロパティにアクセスした際に呼び出され、そのプロパティの値を返すために利用されます。以下は、__get()
を使ったプロパティへのアクセスの例です。
<?php declare(strict_types=1); header("Content-Type: text/plain"); class MyClass { private $data = array(); public function __get($name) { return $this->data[$name]; } } $obj = new MyClass(); $obj->name = 'John'; // プロパティ 'name' に 'John' を設定 echo $obj->name; // 'John' を出力
__set()
は、存在しないプロパティに値を設定しようとした際に呼び出され、そのプロパティの値を設定するために用いられます。以下は、__set()
を使ってプロパティに値を設定する例です。
<?php declare(strict_types=1); header("Content-Type: text/plain"); class MyClass { private $data = array(); public function __set($name, $value) { $this->data[$name] = $value; } } $obj = new MyClass(); $obj->name = 'John'; // プロパティ 'name' に 'John' を設定 echo $obj->name; // 'John' を出力
上記の例では、MyClass
クラスに $data
というプロパティが存在します。このプロパティは private
に宣言されているため、直接アクセスすることはできません。しかし、__get()
メソッドを利用することで、存在しないプロパティにアクセスした際に $data
プロパティの値を返すことが可能です。また、__set()
メソッドを使用することで、存在しないプロパティに値を設定し、その値を $data
プロパティに保存することができます。
__get()
と __set()
は、プロパティを動的に取得または設定するための強力な方法です。ただし、過度に使用するとコードの可読性が低下する恐れがあるため、注意が必要です。
継承
[編集]継承は、既存のクラスを拡張し、新しいクラスを作成するための機能です。継承により、新しいクラスは既存のクラスのプロパティやメソッドを引き継ぐことができます。
以下の例では、Person
クラスを継承したStudent
クラスを定義しています。
<?php declare(strict_types=1); header("Content-Type: text/plain"); class Person { protected $name; public function __construct($name) { $this->name = $name; } public function sayHello() { echo "Hello, my name is " . $this->name; } } class Student extends Person { private $studentID; public function __construct($name, $studentID) { parent::__construct($name); $this->studentID = $studentID; } public function getStudentID() { return $this->studentID; } }
Person
クラスは、$name
という保護されたプロパティと、__construct()
とsayHello()
という公開されたメソッドを持っています。__construct()
は、インスタンスが作成されたときに実行される特別なメソッドであり、$name
プロパティを初期化します。sayHello()
は、$name
プロパティの値を使用してメッセージを出力します。
Student
クラスは、Person
クラスを継承しています。Student
クラスには、$studentID
というプライベートなプロパティと、__construct()
とgetStudentID()
というパブリックなメソッドがあります。__construct()
は、Person
クラスの__construct()
メソッドを呼び出し、$name
プロパティを初期化し、$studentID
プロパティを設定します。getStudentID()
は、$studentID
プロパティの値を返します。
以下のコードを使用することで、Person
クラスとStudent
クラスを使用することができます。
<?php declare(strict_types=1); header("Content-Type: text/plain"); $person = new Person("John"); $person->sayHello(); $student = new Student("Jane", 12345); $student->sayHello(); echo "My student ID is " . $student->getStudentID();
- 上記のコードでは、
Person
クラスのインスタンスを作成してsayHello()
メソッドを呼び出しています。 - 次に、
Student
クラスのインスタンスを作成し、sayHello()
メソッドとgetStudentID()
メソッドを呼び出しています。 Student
クラスはPerson
クラスを継承しているため、sayHello()
メソッドを呼び出すことができます。- また、
Student
クラスは、Person
クラスを継承しているため、Person
クラスで定義されたプロパティやメソッドを使用することができます。 - 例えば、
Student
クラスのインスタンス$student
は、Person
クラスのプロパティ$name
を持っています。 - また、
Student
クラスのインスタンスは、Person
クラスのメソッドsayHello()を呼び出すこともできます。 - ただし、
Student
クラスで定義されたプロパティやメソッドは、Person
クラスのインスタンスでは使用することができません。 - 例えば、
$student
は$studentID
プロパティを持っていますが、$person
はそれを持っていません。
継承を使用すると、コードの再利用性が向上し、冗長なコードを減らすことができます。 また、継承を使用することで、プログラムの柔軟性が向上します。例えば、新しいクラスを作成する際に、既存のクラスを継承することで、新しいクラスが既存のクラスの機能を引き継ぐことができます。 ただし、継承を過剰に使用すると、コードの複雑さが増し、メンテナンスが困難になる場合があります。継承を使用する際には、適切に設計し、必要な機能だけを継承するように心がけることが重要です。
抽象クラス
[編集]抽象クラスとは、具体的な実装が不完全なクラスであり、継承することによってその実装を補完することを目的としたクラスです。抽象クラスは、abstractキーワードを使用して宣言します。抽象クラス自体はインスタンス化することができず、抽象クラスを継承する具象クラスで実装する必要があります。
以下は、PHPで抽象クラスを定義する例です。
<?php declare(strict_types=1); header("Content-Type: text/plain"); abstract class Animal { protected $name; protected $sound; public function __construct($name) { $this->name = $name; } abstract public function makeSound(); } class Dog extends Animal { public function __construct($name) { parent::__construct($name); $this->sound = "Woof!"; } public function makeSound() { echo $this->name . " says " . $this->sound; } } class Cat extends Animal { public function __construct($name) { parent::__construct($name); $this->sound = "Meow!"; } public function makeSound() { echo $this->name . " says " . $this->sound; } } $dog = new Dog("Fido"); $dog->makeSound(); // "Fido says Woof!" $cat = new Cat("Whiskers"); $cat->makeSound(); // "Whiskers says Meow!"
上記の例では、Animalクラスを抽象クラスとして定義し、makeSound()メソッドを抽象メソッドとして宣言しています。具象クラスであるDogクラスとCatクラスは、Animalクラスを継承しています。DogクラスとCatクラスでは、Animalクラスで宣言された抽象メソッドmakeSound()を具体的に実装しています。
makeSound()メソッドは、犬が「Woof!」、猫が「Meow!」と鳴くように、各動物の鳴き声を出力します。DogクラスとCatクラスがAnimalクラスを継承しているため、Animalクラスで定義されたコンストラクタも呼び出されます。
最後に、DogクラスとCatクラスのインスタンスを作成し、makeSound()メソッドを呼び出すことによって、それぞれの動物の鳴き声を出力しています。
抽象クラスは、多くの具象クラスで共通するプロパティやメソッドを抽出し、再利用可能なコードを作成するのに役立ちます。また、抽象クラスは、継承することで必ず実装しなければならないメソッドを定義することができるため、クラスの機能や振る舞いを制限することができます。これにより、コードの信頼性や保守性を高めることができます。
例えば、動物クラスを抽象クラスとして定義した場合、多くの動物が鳴き声を出すことができるという共通点を抽出することができます。しかし、どのような鳴き声を出すかは動物によって異なります。そのため、抽象クラスでmakeSound()メソッドを定義し、具象クラスで実際の鳴き声を定義する必要があります。
また、抽象クラスは、インターフェースと似た役割を果たすことができます。インターフェースは、抽象メソッドのみを持つクラスであり、複数のインターフェースを実装することができます。一方、抽象クラスは、具象メソッドを持つことができ、単一の抽象クラスしか継承することができません。
抽象クラスは、継承することによってコードの再利用性を高めることができます。具象クラスは、抽象クラスのすべてのプロパティやメソッドを継承するため、共通の機能を持つクラスを簡単に作成することができます。また、抽象クラスは、具象クラスの実装を規定することができるため、開発者が間違った実装を行うことを防ぐことができます。
抽象クラスは、多くの具象クラスで共通する機能を抽出し、再利用可能なコードを作成することができます。また、抽象クラスは、具象クラスが必ず実装しなければならないメソッドを定義することができ、コードの信頼性や保守性を高めることができます。
トレイト
[編集]トレイト (Trait) は、PHP5.4から導入された機能のひとつで、クラスで利用できるメソッドを、クラス継承の代わりに、再利用できるようにするための仕組みです。トレイトは、複数のクラスで共通する処理を定義することができます。
トレイトを定義するには、 trait
キーワードを使います。トレイトを実装するクラスは、 use
キーワードを使ってトレイトを指定します。トレイトに定義されたメソッドは、 use
されたクラスのインスタンスから呼び出すことができます。
例えば、与えられた座標に移動する機能をもつ Point
トレイトを定義しています。このトレイトには、コンストラクターで座標を設定する処理や、座標を文字列に変換する処理が含まれています。これらの処理は、 use
するクラスで再利用することができます。
トレイトを利用することで、コードの再利用性を向上させることができます。しかし、トレイトを多用しすぎると、コードが複雑になり、メンテナンスが困難になる場合があるため、注意が必要です。
- Traitの定義の例
<?php declare(strict_types=1); trait Point { public function __construct( private float $x = 0, private float $y = 0) {} public function __toString() : string { return "($this->x, $this->y)"; } public function moveTo(float $dx = 0, float $dy = 0) : void { $this->x += $dx; $this->y += $dy; } } class MyPoint { use Point; } $point = new MyPoint(1.0, 2.0); echo $point->__toString() . PHP_EOL; // Output: (1, 2) $point->moveTo(2.0, 3.0); echo $point->__toString() . PHP_EOL; // Output: (3, 5)
このコードは、トレイトを使用してクラスを構築する方法を示しています。
Point
トレイトは、2つのプロパティ $x
と $y
を持つ __construct
コンストラクターと、__toString
マジックメソッド、および moveTo
メソッドを定義しています。このトレイトは、他のクラスに追加することができ、そのクラスに Point
の機能を提供することができます。
この例では、MyPoint
クラスが定義され、Point
トレイトが使用されています。MyPoint
クラスには、自分で追加したメソッドやプロパティがなく、Point
トレイトが提供するメソッドやプロパティを使用することができます。
最後に、MyPoint
クラスのインスタンス $point
が作成され、初期化子によって x
と y
の値が設定されます。次に、__toString
メソッドが呼び出され、(1, 2)
という文字列が表示されます。moveTo
メソッドが呼び出され、x
と y
の値が変更されます。最後に、再度 __toString
メソッドが呼び出され、(3, 5)
という文字列が表示されます。
インターフェイス
[編集]インターフェイスは、クラスがどのようなメソッドを提供する必要があるかを規定するために使用されるコードの抽象化です。 インターフェイスは実装を持ちませんが、そのメソッドの名前、引数の型、戻り値の型などを指定します。
インターフェイスは、クラスの柔軟性を高めるために使用されます。複数のインターフェイスを実装することで、複数の機能を提供できます。また、複数のクラスが同じインターフェイスを実装することで、コードを共通化することができます。
- インターフェイスの定義の例
interface Areable { public function area() : float; }
このコードは、Areable
というインターフェイスを定義しています。インターフェイスは、クラスやトレイトが実装しなければならないメソッドを定義するためのものです。
Areable
インターフェイスは、area()
というメソッドを持っています。このメソッドは、戻り値がfloat
型であることを示しています。具体的には、面積を計算してその値を返すことが期待されています。
例えば、正方形や円など、面積を計算する必要がある様々な図形がある場合、それらの図形を表すクラスを定義するときに、Areable
インターフェイスを実装することで、各図形に対してarea()
メソッドを提供することができます。
トレイトとインターフェースを参照したクラス定義
[編集]前二節で解説したトレイトとインターフェースを参照したクラス定義の例です。
- 図形クラス
class Shape implements Areable { use Point { Point::__construct as pointConstructor; Point::__toString as pointToString; } public function __construct(float $x = 0, float $y = 0) { $this->pointConstructor($x, $y); } public function __toString() : string { return $this->pointToString(); } public function area() : float { return 0; } } $shape = new Shape(1, 2); echo '$shape --> ', $shape, PHP_EOL;
このコードは、 Shape
クラスが Areable
インターフェースを実装し、 Point
トレイトを使用していることを示しています。
Shape
クラスのコンストラクターは、 Point
トレイトで定義されたコンストラクターをオーバーライドしています。それにより、 Shape
クラスが Point
トレイトを使用しているときに、 Shape
クラスのコンストラクターが呼び出されるようになります。
また、Shape
クラスは __toString()
メソッドを定義しており、これは Point
トレイトで定義された同じメソッドをオーバーライドしています。これにより、 Shape
オブジェクトを文字列として表現する際には、Point
トレイトで定義された __toString()
メソッドではなく、Shape
クラスで定義された __toString()
メソッドが呼び出されます。
注目したいのは、use文の中でPoint::__constructとPoint::__toStringを別名として宣言しています。 これによって、Shapeクラスが自身で実装した__constructと__toStringメソッドを持つことができます。 __constructメソッドでは、親トレイトの__constructを呼び出しています。 __toStringメソッドでは、親トレイトの__toStringを呼び出して、点の座標を文字列として返します。
最後に、 $shape
オブジェクトを文字列として出力すると、 Shape
クラスで定義された __toString()
メソッドが呼び出され、 $shape --> (1, 2)
という文字列が出力されます。
Enum
[編集]Enumをクラスと一緒に解説するのは奇異に感じられるかもしれませんが、言語処理系としては、Enumはclassのインフラを使って実装しているので、メソッドやメソッドのアクセス記述子などの働きは同じです。
- Enumの例
<?php declare(strict_types=1); header("Content-Type: text/plain"); // 列挙型 Seasons の定義 enum Seasons { case Spring; case Summer; case Autumn; case Winter; // 漢字で名前を返すメソッド public function kanji() { return match ($this) { Seasons::Spring => "春", Seasons::Summer => "夏", Seasons::Autumn => "秋", Seasons::Winter => "冬", }; } } foreach (Seasons::cases() as $season) { echo $season->name, " --> ", $season->kanji(), PHP_EOL; }
このコードは、列挙型 (enum) Seasons を定義しています。
Seasons は4つの要素 Spring、Summer、Autumn、Winter を定義しています。この列挙型の各要素に対して、kanji メソッドが定義されています。kanji メソッドは、match 式を使用して、要素に対応する漢字名を返します。
もし match 式で、Seasons の列挙子を網羅していないと、 UnhandledMatchError がスローされます。これにより網羅性が担保されます。
このコードの最後では、Seasons 列挙型のすべての要素に対して、要素名と kanji メソッドの結果を出力しています。それぞれの要素名と漢字名の組み合わせが出力されます。
- class と Enum の違い
- Enum は、継承することもされることもできません。
- Enum は、原則的にマジカルメソッドを定義できません。
- Enum は、const も含めプロパティを持てません。
- 一見定義できても参照できません。
- Enumのインスタンスは、プロパティ name で case の識別子と同じ文字列を得ることができます(書込み不可)。
UnitEnum interface
[編集]今節で取り上げた case に値を与えない Enum を Pure Enum といいます。 Pure Enum は UnitEnum interface の Implement です[2]。
- interface UnitEnum
/** * @since 8.1 */ interface UnitEnum { public readonly string $name; /** * @return static[] */ #[Pure] public static function cases(): array; }
#[Pure]
属性は、純粋関数(副作用を起こさない関数)であることを示します。- 属性はPHP8で導入された、PHPDocのアノテーションにかわる修飾子です。
Backed Enum
[編集]前節で紹介したEnumのように case が特定のスカラー値と結びついていない Enum を Pure Enum といいます。 これに対して、これから紹介する case にスカラー型とスカラー値を明示した Enum を Backed Enum といいます。
- Backed Enumの例
<?php enum W3C16 : int { case Black = 0x000000; case Glay = 0x808080; case Silver = 0xC0C0C0; case Blue = 0x0000FF; case Navy = 0x000080; case Teal = 0x008080; case Green = 0x008000; case Lime = 0x00FF00; case Aqua = 0x00FFFF; case Yellow = 0xFFFF00; case Red = 0xFF0000; case Fuchsia = 0xFF00FF; case Olive = 0x808000; case Purple = 0x800080; case Maroon = 0x800000; public function hex() { return sprintf("#%06X", $this->value); } } foreach (W3C16::cases() as $colour) { echo $colour::class, "::$colour->name --> ", $colour->hex(), "($colour->value)", PHP_EOL; } echo PHP_EOL; var_export(W3C16::from(0xFF00FF)); echo PHP_EOL; # var_export(W3C16::from(0x606060)); echo PHP_EOL; # => PHP Fatal error: Uncaught ValueError: 6316128 is not a valid backing value for enum "W3C16" var_export(W3C16::tryFrom(0xFF00FF)); echo PHP_EOL; var_export(W3C16::tryFrom(0x606060)); echo PHP_EOL; ?>
- 実行結果
W3C16::Black --> #000000(0) W3C16::Glay --> #808080(8421504) W3C16::Silver --> #C0C0C0(12632256) W3C16::Blue --> #0000FF(255) W3C16::Navy --> #000080(128) W3C16::Teal --> #008080(32896) W3C16::Green --> #008000(32768) W3C16::Lime --> #00FF00(65280) W3C16::Aqua --> #00FFFF(65535) W3C16::Yellow --> #FFFF00(16776960) W3C16::Red --> #FF0000(16711680) W3C16::Fuchsia --> #FF00FF(16711935) W3C16::Olive --> #808000(8421376) W3C16::Purple --> #800080(8388736) W3C16::Maroon --> #800000(8388608) W3C16::Fuchsia W3C16::Fuchsia NULL
スカラー型を int と明示 この例では、W3C16 の値は int であり、明示的に int を指定しています。しかし、Enum では int 以外にも string が有効です。
異なる case に同じ値を割り当てることはできません(エラーになります)。 Enum では、異なる case に同じ値を割り当てることはできません。そのため、コード中で同じ値が割り当てられている case がある場合には、エラーが発生します。
Pure Enum の name に加え value もプロパティに持ちます。 Backed Enum では、各 case は value というプロパティを持ちます。このプロパティは、case の値を返します。また、name というプロパティも持ちます。
クラスメソッド from() は値から case を引きます。 Enum には、クラスメソッド from() があります。このメソッドを使うと、指定された値に対応する case を取得できます。ただし、無効な値を指定すると ValueError が発生します。
クラスメソッド tryFrom() は値から case を引きます。 Enum には、クラスメソッド tryFrom() もあります。このメソッドは、指定された値に対応する case を取得します。無効な値を指定すると、NULL が返されます。
Enum は、機能的には連想配列に似ていますが、Backed Enum では、各 case は一意であり、value も一意であることが保証されています。これにより、プログラマーは、例外処理などの面倒を省くことができます。一方、連想配列はキーが重複しないことが保証されますが、value が重複することがあるため、プログラマーが例外処理を実装する必要があります。
BackedEnum interface
[編集]Backed Enum は BackedEnum interface の Implement です[3]。
- interface BackedEnum
/** * @since 8.1 */ interface BackedEnum extends UnitEnum { public readonly int|string $value; /** * @param int|string $value * @return static */ #[Pure] public static function from(int|string $value): static; /** * @param int|string $value * @return static|null */ #[Pure] public static function tryFrom(int|string $value): ?static; }
コードキャラリー
[編集]まとまった規模で、実用的な目的に叶うコードを読まないと機能の存在理由などの言語設計に思い至りにくいので、コードの断片から少し踏み込んだプログラムを掲載します。
複素数クラス
[編集]PHPには複素数がないので、クラスとして実装しました。 ただ、8.1.13 の時点ではまだ演算子オーバーロードはないので、$x->add($y) のようにメソッドで実装しました。
- 複素数
<?php declare(strict_types=1); header("Content-Type: text/plain"); class Complex { public function __construct( private float $real = 0, private float $img = 0) {} public function __toString() { if ($this->img == 0) { return "$this->real"; } if ($this->real == 0) { return "{$this->img}i"; } if ($this->img < 0) { $neg = -$this->img; return "$this->real - {$neg}i"; } return "$this->real + {$this->img}i"; } private static function coerce($right) { switch (gettype($right)) { case "integer": case "double": $right = new self($right, 0); break; case "object": if (get_class($right) != get_class()) { throw new Exception("Type mismatch", 500); } break; default: throw new Exception("Type mismatch", 500); } return $right; } public function add($right) { $right = self::coerce($right); return new self($this->real + $right->real, $this->img + $right->img); } public function sub($right) { $right = self::coerce($right); return new self($this->real - $right->real, $this->img - $right->img); } public function abs() { return sqrt($this->real * $this->real + $this->img * $this->img); } } $x = new Complex(3, 4); echo "$x", PHP_EOL; echo "($x)->abs() --> {$x->abs()}", PHP_EOL; echo "$x + 10 --> {$x->add(10)}", PHP_EOL; echo "$x + 10.0 --> {$x->add(10.0)}", PHP_EOL; $y = new Complex(1, 9); echo "$x + $y --> {$x->add($y)}", PHP_EOL; echo "$x - $y --> {$x->sub($y)}", PHP_EOL;
- 実行結果
3 + 4i (3 + 4i)->abs() --> 5 3 + 4i + 10 --> 13 + 4i 3 + 4i + 10.0 --> 13 + 4i 3 + 4i + 1 + 9i --> 4 + 13i 3 + 4i - 1 + 9i --> 2 - 5i
このPHPスクリプトは、複素数を表現するComplexクラスを定義しています。複素数は、実数部分と虚数部分を持ちます。
Complexクラスのコンストラクタは、実数部分と虚数部分を初期化します。コンストラクタの引数には、float型のプロパティ$realと$imaginaryがあります。各プロパティは、引数のデフォルト値が0に設定されています。
__toString()メソッドは、Complexオブジェクトを文字列に変換するために使用されます。虚数部分が0の場合は、実数部分のみが表示されます。実数部分が0の場合は、虚数部分と文字"i"が表示されます。虚数部分が負数の場合は、負数を括弧で囲みます。それ以外の場合は、実数部分と虚数部分が+記号で連結され、文字"i"が追加されます。
coerce()メソッドは、$right引数をComplexオブジェクトに変換します。gettype()関数を使用して、$rightの型を確認します。integerまたはdoubleの場合、新しいComplexオブジェクトが作成され、$rightの値が実数部分に設定されます。objectの場合、get_class()関数を使用して、$rightがComplexオブジェクトであることを確認します。それ以外の場合、coerce()メソッドは例外をスローします。
add()メソッドとsub()メソッドは、複素数の加算と減算を行います。coerce()メソッドを使用して、引数をComplexオブジェクトに変換します。新しいComplexオブジェクトが作成され、$thisオブジェクトと$rightオブジェクトの実数部分と虚数部分が加算または減算されます。
abs()メソッドは、Complexオブジェクトの絶対値を計算します。絶対値は、複素数の実数部分の2乗と虚数部分の2乗の平方根です。
最後に、スクリプトは、Complexオブジェクトの作成と演算を実行して、結果を出力します。例えば、"$x + $y --> {$x->add($y)}"という行は、Complexオブジェクト$xと$yを加算し、結果を表示します。
図形と面積
[編集]図形クラス(Shape)から正方形(Square)や楕円形(Oval)などを派生しています。 Shape自身は、interface Areable の実装で、Areableは共通するメソッド area()を定義しており、area() を実装しないとShapeから派生できません。
- 図形と面積
<?php declare(strict_types=1); header("Content-Type: text/plain"); trait Point { public function __construct( private float $x = 0, private float $y = 0) {} public function __toString() : string { return "($this->x, $this->y)"; } public function moveTo(float $dx = 0, float $dy = 0) : void { $this->x += $dx; $this->y += $dy; } } interface Areable { public function area() : float; } class Shape implements Areable { use Point { Point::__construct as pointConstructor; Point::__toString as pointToString; } public function __construct(float $x = 0, float $y = 0) { $this->pointConstructor($x, $y); } public function __toString() : string { return $this->pointToString(); } public function area() : float { return 0; } } $shape = new Shape(1, 2); echo '$shape --> ', $shape, PHP_EOL; class Square extends Shape { public function __construct($x, $y, private float $wh = 0) { parent::__construct($x, $y); } public function __toString() : string { return "[$this->wh] + " . parent::__toString(); } public function area() : float { return $this->wh * $this->wh; } } $square = new Square(1, 2, 3); echo '$square --> ', $square, PHP_EOL; class Rectangle extends Shape { public function __construct($x, $y, private float $width = 0, private float $height = 0) { parent::__construct($x, $y); } public function __toString() : string { return "[$this->width x $this->height] + " . parent::__toString(); } public function area() : float { return $this->width * $this->height; } } $rectangle = new Rectangle(1, 2, 3, 4); echo '$rectangle --> ', $rectangle, PHP_EOL; class Circle extends Shape { public function __construct($x, $y, private float $radius = 0) { parent::__construct($x, $y); } public function __toString() :string { return "($this->radius) + " . parent::__toString(); } public function area() : float { return 3.14159265359 * $this->radius * $this->radius; } } $circle = new Circle(8, 9, 10); echo '$circle --> ', $circle, PHP_EOL; class Oval extends Shape { public function __construct($x, $y, private float $width = 0, private float $height = 0) { parent::__construct($x, $y); } public function __toString() : string { return "($this->width x $this->height) + " . parent::__toString(); } public function area() : float { return 3.14159265359 * $this->width * $this->height / 4; } } $oval = new Oval(1, 2, 3, 4); echo '$oval --> ', $oval, PHP_EOL; $oval->moveTo(10, 20); echo '$oval --> ', $oval, PHP_EOL; const shapes = array( new Square(0,1,2), new Rectangle(3,4,5,6), new Circle(8,9,10), new Oval(3,4,5,6) ); foreach(shapes as $shape) { echo get_class($shape),":", $shape, ",\tarea:", $shape->area(), PHP_EOL; }
- 実行結果
$shape --> (1, 2) $square --> [3] + (1, 2) $rectangle --> [3 x 4] + (1, 2) $circle --> (10) + (8, 9) $oval --> (3 x 4) + (1, 2) $oval --> (3 x 4) + (11, 22) Square:[2] + (0, 1), area:4 Rectangle:[5 x 6] + (3, 4), area:30 Circle:(10) + (8, 9), area:314.159265359 Oval:(5 x 6) + (3, 4), area:23.561944901925
このPHPコードは、幾何学的な形状(正方形、長方形、円、楕円)を表すために、トレイト(trait)を使用したオブジェクト指向プログラミングの例を示しています。
まず、Pointというトレイトを定義しています。このトレイトは、x座標とy座標を表すプロパティと、座標を移動するmoveToメソッドを持っています。
次に、Areableというインターフェイスを定義しています。このインターフェイスには、面積を返すareaメソッドが含まれています。
そして、Shapeクラスを定義しています。このクラスは、Pointトレイトを使用して、x座標とy座標を持っています。また、Areableインターフェイスを実装しており、areaメソッドをオーバーライドして0を返すようにしています。このクラスは、正方形、長方形、円、楕円の基底クラスとして使用されます。
Square、Rectangle、Circle、Ovalクラスは、Shapeクラスを拡張しています。それぞれが異なるプロパティを持っており、それぞれが面積を計算するためにareaメソッドをオーバーライドしています。
最後に、いくつかの形状のインスタンスを作成し、それらの面積を計算して表示するためのコードがあります。また、PointトレイトのmoveToメソッドを使用して、Ovalの座標を変更しています。
このプログラムは、オブジェクト指向プログラミングの基本的な概念を示しています。特に、クラスの継承、トレイトの使用、インターフェイスの実装などが重要な要素です。また、オブジェクトの状態を変更するメソッド(moveTo)を定義しています。
用語集
[編集]- オブジェクト指向プログラミング(OOP): ソフトウェア設計の一種で、プログラムを「オブジェクト」と呼ばれる単位に分割し、それぞれのオブジェクトがデータと処理を組み合わせた「クラス」という型で表現される方法。OOPにはカプセル化、継承、ポリモーフィズムの3つの重要な概念が含まれている。
- クラス: OOPで、オブジェクトの型を定義するためのテンプレート。クラスには、属性(メンバ変数)とメソッド(メンバ関数)が含まれる。
- オブジェクト: クラスのインスタンスであり、データと処理を組み合わせた単位。オブジェクトは、それぞれ独自の状態を持つことができ、同じクラスから作成された他のオブジェクトとは異なる動作をすることができる。
- 属性: クラスに定義されたメンバ変数のこと。オブジェクトには、そのクラスに定義された属性が含まれる。
- メソッド: クラスに定義されたメンバ関数のこと。オブジェクトには、そのクラスに定義されたメソッドが含まれる。
- カプセル化: クラスの属性とメソッドを一体化し、外部からのアクセスを制限する機能。カプセル化により、クラスの実装の詳細を隠蔽することができる。
- 継承: クラスの再利用性を高めるための機能で、既存のクラスを基にして新しいクラスを作成することができる。継承元のクラスを「スーパークラス」、継承したクラスを「サブクラス」と呼ぶ。
- ポリモーフィズム: 同じインターフェースを持つ異なるクラスのオブジェクトが同じメソッドを呼び出すことができる機能。ポリモーフィズムにより、クラスの階層構造を自然に表現することができる。
- インスタンス: クラスから生成されたオブジェクトのこと。同じクラスから生成されたオブジェクトでも、それぞれ異なる状態を持つ。
- アクセス修飾子: プロパティやメソッドに対して、外部からのアクセスの制限を設定するためのキーワード。public、private、protectedの3つがある。
- 抽象クラス: インスタンスを生成できないクラスのこと。抽象メソッドを持ち、継承して実装することを前提としている。
- 抽象メソッド: 実装されていないメソッドのこと。抽象クラスで定義され、サブクラスで実装することを期待されている。
- インターフェース: 抽象クラスと似た概念だが、抽象メソッドのみを持ち、実装を強制することができる。
- 多態性: 同じメソッドが、異なるクラスで異なる振る舞いをすることができる機能のこと。継承やインターフェースを用いて実現される。