Java経験者のためのPHP入門

こんにちは。ゆうせいです。

これまでJavaでプログラミングの世界を冒険してきた皆さん、ようこそPHPの世界へ!Javaという堅牢な鎧を身につけた皆さんにとって、PHPは少し違った、軽快でスピーディな乗り物のように感じられるかもしれません。

「JavaとPHPって、どっちもWebで使われる言語でしょ?何がそんなに違うの?」

そんな疑問を抱いている方も多いのではないでしょうか。大丈夫です!この記事を読めば、JavaとPHPの根本的な違いから、PHPを動かすための第一歩まで、スッキリと理解できますよ。

それでは早速、PHPの冒険に出発しましょう!

PHP Logo

1. PHPの概要とJavaとの違い

まずはPHPがどんな言語で、Javaとどこが違うのか、その全体像を掴んでいきましょう。

1.1 PHPの特徴と用途

PHPは、一言で言うと「WebサイトやWebアプリケーションを作ることに特化したプログラミング言語」です。皆さんが普段見ているブログやSNS、ECサイトの裏側で、PHPはとても活発に動いています。

PHPの最大の特徴は、サーバーサイドで動作する「スクリプト言語」であることです。

ここで、「サーバーサイド」と「スクリプト言語」という専門用語が出てきましたね。一つずつ解説します。

  • サーバーサイドWebサイトの裏側、つまり皆さんの目に見えない「サーバー」と呼ばれるコンピュータ上で動くプログラムのことです。ユーザーからのリクエスト(「このページが見たい!」という要求)に応じて、データベースから情報を取ってきたり、計算をしたりして、その結果をHTMLという形式でユーザーのブラウザに返します。例えるなら、レストランのキッチンです。お客さん(ユーザー)からの注文(リクエスト)を受け、厨房(サーバー)で料理人(PHP)が調理し、出来上がった料理(HTML)をホールスタッフが席まで運んでくれる、そんなイメージですね。
  • スクリプト言語プログラムを書いたら、事前の準備(コンパイル)なしに、すぐに実行できるタイプの言語を指します。これはJavaとの大きな違いの一つで、後ほど詳しく解説しますが、とにかく「手軽に・素早く」動かせるのが魅力です。粘土細工のように、こねながら形を整えていくような感覚で開発を進められるんですよ。

Javaがどんな建物でも建てられる万能な設計図だとしたら、PHPは「Webサイト」という特定の建物を、驚くほどスピーディに建てるための専門の設計図、と考えると分かりやすいかもしれません。

1.2 Javaとの主な違い

さて、ここが本題です。Java経験者の皆さんが最も知りたいであろう、具体的な違いを3つのポイントに絞って見ていきましょう!下の表にまとめてみました。

項目JavaPHP
型の扱い静的型付け動的型付け
実行前の準備コンパイルが必要コンパイル不要(インタプリタ型)
主な実行環境JVM(Java仮想マシン)Webサーバー (Apache, Nginxなど)

専門用語が並びましたね。でも安心してください、一つずつ噛み砕いて説明します!

違い①:静的型付け vs 動的型付け

これは、変数(データを入れておく箱)の扱い方に関する、とても重要な違いです。

  • 静的型付け (Static Typing)Javaでお馴染みの方法ですね。変数を宣言するときに、intやStringのように、その変数にどんな種類のデータを入れるか(= 型)を、あらかじめ厳密に決めなければなりません。例えるなら、「この箱は『整数専用』です」と最初にラベルを貼ってしまうようなものです。一度決めたら、整数以外のもの、例えば文字列を入れることはできません。プログラムを実行する前に間違いを発見しやすく、大規模で複雑なプログラムでも品質を保ちやすい、というメリットがあります。まさに堅牢なJavaらしい思想ですね。
  • 動的型付け (Dynamic Typing)一方、PHPはこちらの方式を採用しています。変数を宣言するときに、型を決める必要がありません。プログラムが実行されるときに、代入された値を見てPHPが自動的に「あ、これは整数だな」「これは文字列だな」と判断してくれるのです。例えるなら、「なんでも入る魔法の箱」のようなもの。最初は100という整数を入れていた箱に、後から"こんにちは"という文字列を入れてもPHPは怒りません。コードを柔軟かつスピーディに書けるのが最大の魅力ですが、予期せぬ型が入ってエラーを引き起こす可能性もあるため、注意が必要です。

違い②:コンパイルが必要 vs 不要

プログラムを動かすまでの手順にも、大きな違いがあります。

  • Javaの場合(コンパイルが必要)皆さんはJavaのコードを書いた後、javacというコマンドを使って.javaファイルを.classファイルに変換しましたよね。この作業を「コンパイル」と呼びます。コンパイルは、人間が書いたプログラム(ソースコード)を、コンピュータが理解できる言葉(バイトコード)に「翻訳」する作業です。この翻訳作業があるおかげで、一度翻訳さえすれば、どんなコンピュータ(OS)の上でも動くことができるのです。
  • PHPの場合(コンパイルが不要)PHPには、この「翻訳」のステップがありません。書いたコードをそのままサーバーに置けば、PHPの実行エンジン(インタプリタと呼ばれます)が、コードを一行ずつ解釈しながら実行していきます。例えるなら、Javaは日本語で書いた手紙を外国人の友人に送るために、一度プロの翻訳家に英語へ翻訳(コンパイル)してもらうイメージ。PHPは、日本語がわかる友人に、日本語で書いた手紙をそのまま渡すイメージです。翻訳の手間が省ける分、PHPの方が圧倒的に手軽でスピーディに結果を確認できるのです!

違い③:実行環境

プログラムが実際に動く「場所」も異なります。

  • Javaの場合 (JVM)Javaは「JVM(Java Virtual Machine)」という、Java専用の仮想的なコンピュータの上で動きます。このJVMさえあれば、WindowsでもMacでもLinuxでも、同じようにプログラムを動かせます。これが「Write once, run anywhere(一度書けば、どこでも動く)」というJavaの有名なスローガンですね。
  • PHPの場合 (Webサーバー)PHPは、主に「Apache(アパッチ)」や「Nginx(エンジンエックス)」といったWebサーバーソフトウェアに組み込まれて動作します。Webサーバーがユーザーからのリクエストを受け取ると、PHPのプログラムを起動し、その実行結果をブラウザに返す、という流れです。Webの世界に特化しているからこそ、その土俵(Webサーバー)の上で最も効率的に動くように設計されているのです。

1.3 実行環境とPHPファイルの実行方法

「じゃあ、PHPを自分のパソコンで動かすにはどうすればいいの?」と思いますよね。

JavaでいうJDK(Java Development Kit)のような開発環境を整える必要があります。

心配しないでください!今はとても便利なツールがあります。

  • XAMPP (ザンプ) / MAMP (マンプ)これらは、PHPを動かすのに必要なWebサーバー(Apache)、データベース(MySQL)、そしてPHP本体を、ワンクリックでまとめてインストールしてくれる夢のようなパッケージです。WindowsならXAMPP、MacならMAMPを使うのが一般的で、初心者のうちはまずこれをインストールすればOKです!
  • Docker (ドッカー)もう少し本格的な開発に挑戦したい方向けのツールです。アプリケーションの実行環境を「コンテナ」という箱に閉じ込めて、あなたのPC上でも、本番のサーバー上でも、全く同じ環境を再現できます。Java開発でも使われることがあるので、ご存知の方も多いかもしれませんね。

環境が整ったら、いよいよPHPファイルを実行してみましょう。

  1. テキストエディタでファイルを作成し、拡張子を.phpにします。(例: index.php
  2. ファイルの中身を <?php で始め、 ?> で閉じます。この間にPHPのコードを書いていきます。
  3. 作成したファイルを、XAMPPやMAMPが指定する特別なフォルダ(ドキュメントルートと呼ばれます)に保存します。
  4. Webブラウザを起動し、アドレスバーに http://localhost/index.php のように入力してアクセス!

すると、WebサーバーがPHPコードを実行し、その結果がブラウザに表示されます。簡単でしょう?

1.4 Javaのmainメソッド相当がない理由と実行フローの違い

Java経験者がPHPを触って、最初におそらく「あれ?」と戸惑うポイントがこれです。

「プログラムの入り口である main メソッドはどこ?」

結論から言うと、PHPにはJavaのpublic static void main(String[] args)のような、明確に定められたプログラムの開始地点が存在しません。

なぜなら、PHPのプログラムは「リクエスト駆動」で動くからです。

  • Javaの実行フローmainメソッドが呼ばれると、そこからプログラムがスタートし、一連の処理が順々に実行され、最後に終了します。まるで、上映開始時間になったら始まり、エンドロールが流れたら終わる一本の映画のようです。
  • PHPの実行フローPHPは、ユーザーがブラウザから特定のURLにアクセスする(=リクエストを送る)たびに、そのリクエストに対応するPHPファイルが「上から下へ」と実行されます。そして、処理が最後まで終わるとプログラムも終了し、次のリクエストを待ちます。例えるなら、自動販売機です。お客さん(ユーザー)が「Aのジュース」のボタンを押す(リクエスト)と、ガコンとAのジュースが出てくる(レスポンス)。次に「Bのコーヒー」のボタンが押されたら、またガコンとコーヒーが出てくる。リクエストがあるたびに、毎回完結した処理が一度だけ行われるのです。この仕組みのため、PHPには「ここが全体の始まりです」と宣言するmainメソッドが必要ない、というわけなんですね。アクセスされたファイルそのものが、実行の単位となるのです。

次に学ぶべきは、PHPの具体的な文法です。

Javaとの共通点や違いを探しながら学習を進めると、きっと面白く、効率的に知識を吸収できるはずです。恐れることはありません、さあ、次のステップへ進みましょう!

2. 基本文法の差異

JavaとPHPは、どちらもC言語の思想を受け継いでいるため、驚くほど似ている部分がたくさんあります。ですが、もちろん「ここが違う!」という重要なポイントも存在します。この章では、Javaの知識を活かしつつ、PHP特有のルールを効率よく学んでいきましょう!


2.1 セミコロンやブロック構造の共通点

まずは、Java経験者の皆さんにとって安心できるニュースからです。

プログラムの文の終わりを表す「セミコロン(;)」や、if文やfor文、メソッド(PHPでは関数と呼びます)の処理範囲をまとめる「ブロック({})」の使い方は、JavaとPHPで全く同じです!

Java

// Javaの例
int score = 80;
if (score >= 60) {
  System.out.println("合格です!");
}

上のJavaコード、見慣れた形ですよね。これがPHPになると、次のようになります。

PHP

// PHPの例
$score = 80;
if ($score >= 60) {
  echo "合格です!"; // echoは文字列などを出力する命令です
}

どうでしょうか?変数の書き方($score)や出力命令(echo)が少し違うだけで、骨格となる構造はそっくりですよね。

このように、処理の流れをコントロールする基本的な構造は、Javaの経験をそのまま活かすことができるのです。これは嬉しいポイントですね!


2.2 変数宣言の違い(型指定不要、$プレフィックス)

さて、ここからがPHPらしいルールの本番です。JavaとPHPで、変数の扱い方は大きく異なります。これは前の章で触れた「静的型付け」と「動的型付け」の違いから来ています。

思い出してください、Javaでは変数を「使う前に、どんな種類のデータを入れる箱なのか」を宣言する必要がありました。

Java

// Javaの例:型を先に決める
String name = "山田";
int age = 30;
boolean isStudent = false;

一方、PHPではこの「型宣言」が不要です。その代わり、すべての変数名の前に必ずドルマーク($)を付けなければならない、という絶対的なルールがあります。

PHP

// PHPの例:型宣言は不要!$を付けるだけ
$name = "山田";      // 文字列を代入したので、文字列型の変数になる
$age = 30;          // 整数を代入したので、整数型の変数になる
$isStudent = false; // 論理値を代入したので、論理型の変数になる

PHPは、代入された値を見て「なるほど、この変数$nameは文字列なんだな」と実行時に判断してくれます。

例えるなら、Javaの変数は「果物専用の箱」「野菜専用の箱」のように、中身をあらかじめ決めておくスタイル。対してPHPの変数は、どんなものでも入れられる「魔法の箱」です。ただし、その箱には必ず「$」というシールを貼っておく、というルールがあるのです。

この「$を付ける」というルールは、最初は少し奇妙に感じるかもしれませんが、すぐに慣れますよ。逆に、$が付いていればそれは変数だ、と一目でわかるので、コードが読みやすくなるというメリットもあるんです。


2.3 定数の宣言方法(define / const)

「一度決めたら、後から絶対に変えられない値」、つまり「定数」の宣言方法も見ていきましょう。Javaではfinalキーワードを使っていましたね。

PHPには、定数を宣言する方法が主に2つあります。define()関数を使う方法と、constキーワードを使う方法です。それぞれに少し特徴があります。

宣言方法define()const
書き方関数として定義 define("NAME", "value");キーワードで定義 const NAME = "value";
使える場所スクリプトのどこでも使えるスクリプトのトップレベルか、クラス内で使う
特徴if文の中など、条件によって定義できるJavaのfinalに感覚が近い。より高速に動作する

1. define()関数

これは昔からある、伝統的な定数の定義方法です。

PHP

// define() を使った定数の定義
define("TAX_RATE", 0.1);

$price = 1000;
$total = $price * (1 + TAX_RATE);
echo $total; // 1100 と出力される

注意してほしいのは、defineで定義した定数には、変数のときのような$は付かないことです。

2. constキーワード

こちらは、より新しく導入された方法で、Javaのfinalに近い感覚で使えます。クラスの定数を定義するときなどによく使われ、一般的にdefineよりも高速に動作すると言われています。

PHP

// const を使った定数の定義
const TAX_RATE = 0.1;

$price = 1000;
$total = $price * (1 + TAX_RATE);
echo $total; // 1100 と出力される

「じゃあ、どっちを使えばいいの?」と迷いますよね。

基本的には、よりモダンでJavaに似ているconstを使うことをお勧めします。ただし、if文の中で条件によって定数を変えたい、といった特殊なケースではdefine()を使う必要がある、と覚えておくと良いでしょう。


2.4 コメントの書き方の共通点・違い

プログラムの中にメモを残すためのコメント。これもJavaとほとんど同じなので安心してください!

1行コメント

// を使う方法は、Javaと全く同じです。それに加えて、PHPではシャープ記号(#)も1行コメントとして使えます。

// これはJavaと同じ1行コメントです
# こちらもPHPでは1行コメントとして扱われます
$user_id = 123;

複数行コメント

/*で始まり*/で終わるブロックコメントも、Javaと全く同じです。

/*
  ここは複数行にわたるコメントです。
  Javaでの書き方と完全に同じなので、
  迷うことはありませんね!
*/
$user_name = "鈴木";

ドキュメンテーションコメント

JavaでJavadocを書くときに使った /** ... */ という形式のコメント、ありますよね。

PHPにもPHPDocという同様の仕組みがあり、全く同じ記法が使われます。IDE(統合開発環境)がこのコメントを解釈して、関数の説明や引数の型などを補完表示してくれるので、積極的に活用していきましょう!

/*
  ここは複数行にわたるコメントです。
  Javaでの書き方と完全に同じなので、
  迷うことはありませんね!
*/
$user_name = "鈴木";

いかがでしたか?

変数の$には驚いたかもしれませんが、それ以外の基本的なルールは、Javaと非常によく似ていることがお分かりいただけたかと思います。この調子で、PHPの文法にどんどん慣れていきましょう!

3. データ型と型変換

プログラムはデータを扱うためにあります。そのデータの「種類」を定義するのが「データ型」です。JavaではintStringbooleanといった型を厳密に区別してきましたが、PHPではこのデータの扱い方がより柔軟になっています。

ここではPHPのデータ型を学び、Javaとの違いや、PHP特有の「型変換」という面白い(そして時に注意が必要な)振る舞いについて見ていきましょう!


3.1 PHPのデータ型(スカラー型・配列・オブジェクト)

PHPのデータ型は、大きく分けていくつかのカテゴリに分類できます。

  • スカラー型 (Scalar Types)これは「単一の」値を表すデータ型で、Javaのプリミティブ型に似ています。
    • bool: truefalseのどちらかを持つ、おなじみの論理型です。
    • int: 10-100などの整数を表します。
    • float: 3.14-0.05などの浮動小数点数(小数)を表します。
    • string: "こんにちは"のような文字列を表します。
  • 複合型 (Compound Types)こちらは、複数の値をまとめることができる、より複雑なデータ型です。
    • array: PHPを学ぶ上で最も重要と言っても過言ではないデータ型です。 詳細は後述しますが、Javaの配列、リスト、マップ(HashMap)の機能をすべて兼ね備えた、非常に強力で柔軟な「魔法の箱」だと考えてください!
    • object: クラスから生成されるインスタンスです。これはJavaのオブジェクトと全く同じ考え方で、皆さんのオブジェクト指向の知識をそのまま活かせます。
  • 特別な型
    • null: 「値が何もない」ことを示す特別な値です。これもJavaのnullと同じですね。

3.2 Javaとの比較(配列/リスト、Map、Wrapperクラス)

Java経験者がPHPのデータ型を学ぶ上で、特に注目すべき違いは3つです。

違い①:最強のデータ構造 array

Javaでは、用途に応じてデータ構造を使い分けていましたよね。

  • 固定長のデータ列なら、int[]のような配列
  • 可変長のデータ列なら、ArrayList<String>のようなリスト
  • キーと値のペアなら、HashMap<String, Integer>のようなマップ

PHPでは、驚くべきことに、これらの役割をすべてarray型一つでこなせてしまいます!

例えるなら、Javaが大工さんの道具箱だとしたら、配列は「金槌」、リストは「のこぎり」、マップは「ドライバー」と、それぞれの役割を持った専門の道具です。

一方でPHPのarrayは、これ一本で何でもできる「十徳ナイフ」のようなものです。

// 1. Javaの「リスト」のように振る舞う配列
$fruits = ["apple", "banana", "cherry"];
echo $fruits[0]; // "apple" と出力される

// 2. Javaの「マップ」のように振る舞う配列(連想配列と呼びます)
$user = [
    "name" => "Sato",
    "age" => 25,
    "city" => "Tokyo"
];
echo $user["name"]; // "Sato" と出力される

このように、キーに整数を使えばリストのように、文字列を使えばマップのように振る舞うのです。この柔軟性がPHPの開発生産性を高める大きな要因の一つとなっています。最初は戸惑うかもしれませんが、このarrayを使いこなせることがPHPマスターへの近道です!

違い②:Wrapperクラスは存在しない

Javaでは、プリミティブ型であるintをオブジェクトとして扱いたいとき、Integerというラッパークラスを使いました。

PHPには、このラッパークラスという概念がありません。なぜなら、PHPは動的型付け言語であり、必要に応じて数値を文字列として扱ったり、その逆を行ったりするのが得意だからです。intとIntegerのような区別はなく、すべてが一つの型としてシンプルに扱われます。


3.3 暗黙の型変換とvar_dumpの活用

ここがPHPの非常に面白い特徴であり、Java経験者が最も注意すべき点です。「暗黙の型変換(Type Juggling)」と呼ばれます。

これは、PHPが文脈を読んで、自動的にデータ型を変換してしまう振る舞いのことです。

例えば、次のコードを考えてみてください。

$num_string = "10"; // これは文字列です
$num_integer = 5;   // これは整数です

$result = $num_string + $num_integer;

Javaで Stringとintを+でつなぐと、"105"という文字列の結合になりますよね。

しかし、PHPの + 演算子は純粋に「足し算」の機能しか持ちません。そのため、PHPは「お、足し算をしようとしているな。じゃあ文字列の"10"を、数値の10に変換して計算してあげよう」と、気を利かせてくれるのです。

その結果、$resultには15という整数が代入されます。

この自動的な変換は便利な反面、「意図しない型に変わっていてバグの原因になる」こともあります。

そこで、絶対に覚えてほしい最強のデバッグツールがvar_dump()です!

var_dump()は、変数の「値」だけでなく、「型」や「長さ」まで、その変数のすべてを丸裸にして表示してくれます。

$num_string = "10";
$result = $num_string + 5;

echo $result;        // 画面には「15」とだけ表示される
var_dump($result);   // 画面には「int(15)」と表示される

echoが「箱の中に何が入ってる?」と聞いて「15だよ」と答えるだけなのに対し、var_dump()は「これは『整数型』で、値は『15』のデータだよ」と、その正体を詳しく教えてくれるX線スコープのようなものです。

動的型付け言語であるPHPを開発する上で、var_dump()はあなたの最高の相棒になります。絶対に使いこなしてください!


3.4 型キャスト(Javaとの記法比較)

PHPが自動で型変換してくれるのは便利ですが、時には「この変数は、絶対にこの型として扱ってほしい!」と、プログラマが明示的に型を変換したい場面もあります。これを「型キャスト」と呼びます。

朗報です!この型キャストの書き方は、Javaと全く同じです。

変数の前に (変換したい型) を書くだけです。

// Javaでの型キャスト
double d = 9.78;
int i = (int) d; // iには9が入る

これがPHPでも、全く同じように書けます。

// PHPでの型キャスト
$float_num = 9.78;
$int_num = (int) $float_num; // $int_num には 9 が入る

$num = 123;
$str_num = (string) $num;   // $str_num には "123" が入る

使える型キャストには、以下のようなものがあります。

  • (int): 整数へ
  • (bool): 論理値へ
  • (float): 浮動小数点数へ
  • (string): 文字列へ
  • (array): 配列へ
  • (object): オブジェクトへ

暗黙の型変換に頼るだけでなく、意図が明確になる場面では、このように明示的な型キャストを使うことで、より安全で読みやすいコードを書くことができますよ。

4. 演算子の違い

プログラムで計算や比較を行う際に使う「演算子」。ここにもJavaとPHPの思想の違いが表れていて、特に注意すべき点がいくつかあります。Javaの常識が通用しない場面もあるので、しっかり確認していきましょう!

でも、安心してください。多くの算術演算子(+, -, *, /, %など)はJavaと同じように使えますよ。


4.1 比較演算子と === / == の違い(型の比較有無)

これはJava経験者がPHPで最初にハマる、最大の罠と言っても過言ではありません。集中してください!

Javaでは、二つの値が等しいか比較するときに==を使いましたよね(プリミティブ型の場合)。PHPにも==はありますが、Javaのそれとは少し挙動が異なります。そして、PHPには===という、さらにもう一つの比較演算子が存在します。

演算子読み方比較方法10 == "10"0 == false
==等しい (Equal)値が等しいか比較する(型は自動変換する)truetrue
===全く同じ (Identical)値と型の両方が等しいか比較する(型変換しない)falsefalse

== (等しい / ルーズな比較)

==演算子は、二つの値が「だいたい同じ」かどうかをチェックします。もしデータ型が違っていても、PHPが「これは同じ値と見なせそうだな」と判断すれば、自動で型を変換(型ジャグリング)してから比較します。

そのため、上の表のように、整数10と文字列"10"を比較してもtrue(真)と判断されてしまうのです。

=== (全く同じ / 厳密な比較)

===演算子は、二つの値が「完全に同じ」かどうかを厳密にチェックします。値が同じであることに加え、データ型も全く同じでなければtrueにはなりません。型が一つでも違えば、問答無用でfalse(偽)を返します。

Javaの感覚に近いのは、こちらの===の方だと言えるでしょう。

例えるなら、==は「この1000円札と、この100円玉10枚は、価値が同じですか?」と聞いているようなものです。答えは「はい(true)、価値は同じです」ですよね。

一方、===は「この1000円札と、この100円玉10枚は、全く同じモノですか?」と聞いています。答えは「いいえ(false)、一方は紙幣で、もう一方は硬貨の集まりです」となります。

PHPでは、意図しない型の変換によるバグを防ぐため、比較を行う際は常に===を使うことが強く推奨されています。特別な理由がない限り、==は使わない、と心に決めておきましょう!これは命令です!


4.2 文字列結合(.演算子 vs +演算子)

Javaで文字列を連結するとき、+演算子を使っていましたね。

// Javaの例
String firstName = "Taro";
String lastName = "Yamada";
String fullName = firstName + " " + lastName; // "Taro Yamada"

この感覚でPHPのコードを書くと、予期せぬ結果になります。なぜなら、前の章でも触れたように、PHPの+演算子は純粋に「数値の足し算」しか行わないからです。

PHPで文字列を結合するには、プラス記号+ではなく、ピリオド.を使います。

// PHPの例
$firstName = "Taro";
$lastName = "Yamada";
$fullName = $firstName . " " . $lastName; // "Taro Yamada"

もしPHPで+を使って文字列を足そうとすると、PHPはそれらの文字列を何とか数値に変換しようと試みます。

$str1 = "10 apples";
$str2 = "20 oranges";
echo $str1 + $str2; // 30 と出力される

PHPは"10 apples"から数値の10を、"20 oranges"から数値の20を抽出し、それらを足し算してしまうのです。これは多くの場合、あなたの期待する結果ではないはずです。

「文字列をつなぐときはピリオド.!」と、しっかり覚えておきましょう。


4.3 論理演算子の共通点・注意点

複数の条件を組み合わせる論理演算子については、Javaと共通する点が多くあります。

  • && (AND): 二つの条件が両方ともtrueのときにtrueになる。
  • || (OR): 二つの条件のどちらか一方がtrueのときにtrueになる。
  • ! (NOT): 条件のtruefalseを反転させる。

これらの記号はJavaと全く同じように使えます。

ただし、PHPには&&の代わりにand||の代わりにorというキーワードも用意されています。

「え、じゃあどっちを使っても同じ?」と思うかもしれませんが、ここに小さな罠があります。

&&とand、||とorでは、演算子の「優先順位」が異なるのです。

特に代入演算子=と組み合わせたときに、挙動が変わることがあります。

// && の場合
$result = false && someFunction(); // $result は false になる。someFunction()は実行されない。

// 'and' の場合
$result = false and someFunction(); // ($result = false) and someFunction() と解釈される。
                                    // $result は false になる。someFunction()は実行されない。

// 挙動が変わる例
$result = true || false; // $result は true
$result = true or false; // ($result = true) or false と解釈され、$resultはtrue

// 罠の例
$result = file_exists("test.txt") or die("ファイルがありません!");
// ↑これは意図通りに動かないことがある
// ($result = file_exists("test.txt")) or die(...) と解釈されるため。

andやorは、=よりも優先順位が低いため、意図しない結果を招くことがあります。

結論として、Java経験者の皆さんには、混乱を避けるためにも、使い慣れた&&と||を一貫して使用することをお勧めします。その方がコードの意図が明確になり、バグも減らせますよ。

5. 制御構文の共通点と相違点

プログラムの流れをコントロールする「制御構文」。ifforswitchといったおなじみの命令ですね。JavaとPHPは同じC言語ファミリーに属するため、ここでも多くの共通点があります。しかし、PHPの柔軟なデータ型に起因する、いくつかの重要な違いも存在します。

さあ、あなたのJavaの知識がどれだけ通用するのか、腕試しをしてみましょう!


5.1 if / else / elseif

条件によって処理を分けるif文は、PHPでも全く同じ感覚で使えます。これは嬉しいニュースですね!

$score = 85;

if ($score >= 80) {
    echo "素晴らしい!";
} else if ($score >= 60) {
    echo "合格です";
} else {
    echo "もう少し頑張りましょう";
}

基本的な構造はJavaとそっくりです。

一つだけ、ちょっとした違いを挙げるとすれば、else ifの書き方です。Javaではelse ifとスペースを入れて書きますが、PHPではelseifと1語でつなげて書くのが公式なスタイルとされています。(実はPHPではelse ifと書いても動きますが、elseifが一般的です)。

些細な違いですが、覚えておくと「お、この人はPHPを知っているな」と思われるかもしれませんよ。


5.2 switch文(break必須は共通だが、型比較の挙動は異なる)

複数の値によって処理を分岐させるswitch文。これも基本的な構造や、各caseの終わりにbreakを書かないと次の処理に突き抜けてしまう(フォールスルー)というルールはJavaと共通です。

しかし、ここにもPHP特有の罠が潜んでいます。それは、caseの比較方法です。

PHPのswitch文は、比較に「厳密な比較 (===)」ではなく、「ルーズな比較 (==)」を使います。

思い出してください。==は、型が違っても値が同じと見なせればtrueを返してしまう比較でしたね。この仕様が、switch文で予期せぬ挙動を引き起こすことがあります。

次のコードを見てください。

$value = 0;

switch ($value) {
    case "a":
        echo "文字列のaです";
        break;
    case 0:
        echo "数値の0です";
        break;
    default:
        echo "その他です";
        break;
}

Javaの感覚なら、$valueは整数0なので、当然「数値の0です」が出力されると期待しますよね。

しかし、PHPでこのコードを実行すると、なんと「文字列のaです」が出力されてしまいます!

なぜなら、PHPは内部で"a" == 0という比較を行い、これがtrueと判定されてしまうからです。(アルファベットから始まる文字列を数値に変換しようとすると0になるため)。

このように、PHPのswitch文は異なる型の値を扱う際に非常に危険です。

もし、信頼性の高い分岐処理を書きたい場合は、switch文を使わずに、ifとelseifを===でつないで書く方がずっと安全です。この点はしっかり覚えておいてください。


5.3 foreach(Javaの拡張forとの違い)

配列やオブジェクトの要素を一つずつ取り出して処理するループは、Javaでは「拡張for文」と呼ばれていましたね。PHPにも同じ目的で使われるforeachという構文がありますが、こちらはJavaよりもさらにパワフルです!

まずはJavaの拡張for文に似た、シンプルな使い方から見てみましょう。

Java (拡張for文)PHP (foreach)
String[] fruits = {"apple", "banana"};
for (String fruit : fruits) { ... }
$fruits = ["apple", "banana"];
foreach ($fruits as $fruit) { ... }

:asに変わったような形で、コンセプトは非常に似ていますね。

PHPのforeachが真価を発揮するのは、キーと値のペアを同時に取り出せる点です。これはPHPのarrayがマップ(連想配列)の機能を持つからこその強みです。

$user = [
    "name" => "Sato",
    "age" => 25,
    "city" => "Tokyo"
];

// キーと値を同時に取り出す!
foreach ($user as $key => $value) {
    echo $key . " は " . $value . " です\n";
}

このコードを実行すると、以下のように出力されます。

name は Sato です
age は 25 です
city は Tokyo です

$array as $key => $valueという構文を使うことで、JavaのMap.Entryのようなことを、よりシンプルに実現できるのです。

例えるなら、Javaの拡張for文は「本のページを1枚ずつめくって、内容だけを読む」ようなものです。

PHPのforeachは、「ページをめくりながら、そのページ番号(キー)と内容(値)を同時に確認できる」ようなもの。この違いは非常に大きく、PHPでの配列操作を非常に快適にしてくれます。


5.4 while / do-whileの違いなし

最後に、嬉しいお知らせです。

条件がtrueである間、処理を繰り返すwhile文と、必ず一度は処理を実行してから条件を判定するdo-while文。

これら2つの制御構文については、PHPとJavaで文法も挙動も全く違いがありません!

// while文
$i = 0;
while ($i < 5) {
    echo $i;
    $i++;
}
// 出力: 01234

// do-while文
$j = 0;
do {
    echo $j;
    $j++;
} while ($j < 5);
// 出力: 01234

ここは完全にJavaの知識をそのまま流用できるので、何も心配することはありませんね!

6. 関数とメソッド

処理をひとまとめにした「関数」(クラスの中にある場合は「メソッド」)。プログラムを部品化し、再利用性を高めるための基本的な要素ですね。ここでもPHPは、Javaの厳格さとは一味違う、柔軟な書き方を提供してくれます。

Javaのラムダ式に似た機能も登場しますよ。早速見ていきましょう!


6.1 関数定義の書き方(戻り値・引数型指定は任意)

PHPで関数を定義するには、functionというキーワードを使います。Javaのように、publicprivateといったアクセス修飾子や、戻り値の型を先頭に書く必要はありません。

まずは、最もシンプルな昔ながらのPHPの書き方を見てみましょう。

// 型指定を一切しない、クラシックなPHPの関数
function add($a, $b) {
    return $a + $b;
}

echo add(5, 10);      // 15
echo add("5", "10");  // 15 (暗黙の型変換が起こる)

非常にシンプルですね。しかし、これだとどんな型の引数でも受け入れてしまうため、意図しない型変換でバグを生む可能性がありました。

そこで、現代のPHPでは、Javaのように引数と戻り値に「型」を指定することが強く推奨されています。これを「型宣言」または「タイプヒンティング」と呼びます。

// 型宣言を使った、モダンなPHPの関数
function add(int $a, int $b): int {
    return $a + $b;
}

echo add(5, 10);      // 15
// echo add("5", "10");  // ここでエラーが発生する!

add(int $a, int $b)のように引数の前に型を、関数の閉じカッコの後に: intのように戻り値の型を記述します。

この型宣言は任意ですが、コードの安全性を飛躍的に高めるため、特別な理由がない限り必ず書くようにしましょう!

例えるなら、型指定なしの関数は「なんでもいいから2つちょうだい。足して返すから」という口約束のようなもの。

型宣言ありの関数は「『整数』を2つ、ご提供ください。契約に基づき『整数』を1つお返しします」という、内容が明確な契約書のようなものです。契約書がある方が、ずっと安心ですよね。


6.2 可変長引数(...$args vs Javaの...)

引数の数が決まっていない「可変長引数」。JavaではString... argsのように書きましたね。

PHPでも、これとほぼ同じ機能が、ほぼ同じ記法で使えます!

// Javaの可変長引数
public int sum(int... numbers) {
    int total = 0;
    for (int n : numbers) {
        total += n;
    }
    return total;
}

これがPHPになると、次のようになります。

// PHPの可変長引数
function sum(int ...$numbers): int {
    $total = 0;
    foreach ($numbers as $n) {
        $total += $n;
    }
    return $total;
}

echo sum(1, 2, 3, 4, 5); // 15と出力される

...(スリー・ドット・オペレータやスプレッド演算子と呼ばれます)を引数の前に置く点は同じですね。受け取った引数は、$numbersという名前の配列として関数内で扱えます。

Javaの知識をそのまま活かせる、嬉しい共通点の一つです!


6.3 無名関数・クロージャ(Javaのラムダとの違い)

Java 8から導入された「ラムダ式」。その場で使い捨ての関数を定義できる便利な機能でしたね。

PHPにも、これによく似た「無名関数」または「クロージャ」と呼ばれる機能があります。

// PHPの無名関数
$add = function(int $a, int $b): int {
    return $a + $b;
}; // ← 式なのでセミコロンが必要

echo $add(5, 3); // 8

functionキーワードを使い、関数名を付けず、最後は変数への代入文として;で締めくくるのが特徴です。

Javaのラムダ式とPHPのクロージャ。目的は似ていますが、一つ決定的な違いがあります。それは、「外側のスコープにある変数へのアクセス方法」です。

Javaのラムダ式は、外側にあるfinalまたは実質的にfinalな変数を、何もせずとも「キャプチャ」して参照できました。

一方、PHPのクロージャが外側の変数を参照するには、useというキーワードを使って、どの変数を取り込むかを明示的に宣言しなければなりません。

$message = "こんにちは、";

// `use` を使って外側の変数 $message をクロージャ内に引き込む
$greeter = function(string $name) use ($message): string {
    return $message . $name;
};

echo $greeter("田中さん"); // こんにちは、田中さん

もしuse ($message)の部分がないと、クロージャの中から$message変数は見えず、エラーになってしまいます。

例えるなら、Javaのラムダ式は、親の姓を自動的に受け継ぐ子供のようなものです。

PHPのクロージャは、親の姓を名乗るために、役所で「この姓を使います(useします)」と、一手間かけて手続きをする養子のようなイメージです。このuseキーワードの存在は、PHPのクロージャを扱う上で非常に重要なポイントなので、必ず覚えてください!


6.4 グローバル変数とglobalキーワード

最後に、変数の有効範囲「スコープ」に関する、PHPの少し古い仕組みについて触れておきましょう。

基本的に、関数の中は独立した世界であり、関数の外で定義された変数を直接読み書きすることはできません。

$name = "山田";

function sayHello() {
    // echo $name; // このままでは$nameが見えず、エラーになる
}

この壁を越えて、関数の外にある変数(グローバル変数)を関数内で使うためのキーワードがglobalです。

$name = "山田";

function sayHello() {
    global $name; // グローバル変数$nameを、この関数内で使えるように宣言
    echo "こんにちは、" . $name . "さん";
}

sayHello(); // こんにちは、山田さん

global $name;と宣言することで、関数sayHelloはグローバルスコープにある$name変数を認識できるようになります。

しかし、はっきりと言います。現代のプログラミングにおいて、globalキーワードを使ってグローバル変数を安易に参照するのは、極力避けるべき「悪しき習慣」とされています。

なぜなら、どの関数がどのグローバル変数を書き換えているのか分からなくなり、プログラムが非常に追いにくく、バグの温床になるからです。これはJavaでpublic staticなフィールドを多用する危険性にも似ていますね。

では、どうすれば良いのでしょうか?

答えはシンプルです。「必要なものは、引数で渡す」のです。これを「依存性の注入(Dependency Injection)」と呼びます。

$name = "山田";

// 必要な$nameを引数で受け取る、クリーンな関数
function sayHello_good(string $targetName) {
    echo "こんにちは、" . $targetName . "さん";
}

sayHello_good($name); // こんにちは、山田さん

こちらの方が、関数が何に依存しているのか一目瞭然で、はるかに安全でテストもしやすいコードになります。

globalという機能は存在するものの、皆さんはJavaで培った良い設計思想を活かし、なるべく引数を使うクリーンなコーディングを心がけてくださいね!

7. 配列(Javaとの大きな違い)

さあ、JavaとPHPの最も大きな違いと言っても過言ではない、「配列」の世界へようこそ!

Javaではリスト、マップ、セットなど、用途に応じて様々なコレクションを使い分けてきました。PHPでは、そのほとんどの機能をたった一つのarray型で実現できてしまいます。

この章をマスターすれば、あなたのPHPプログラミングは一気に加速します。PHPの「配列」という名の、万能ツールナイフを使いこなしていきましょう!


7.1 PHP配列の特殊性(インデックス配列と連想配列が同じ構造)

PHPの配列がなぜ特別なのか?その答えは、「インデックス配列」と「連想配列」という2つの顔を、同じ一つの構造で持っているからです。

インデックス配列 (Indexed Array)

これはJavaのArrayListに似ており、0, 1, 2...という数値のキー(添字)と値がペアになった配列です。キーを省略して値だけを追加していくと、自動的に0から始まるキーが割り振られます。

// インデックス配列の例
$fruits = ["apple", "banana", "orange"];
// 上の行は、下のように書いたのと同じ意味です
// $fruits = [0 => "apple", 1 => "banana", 2 => "orange"];
連想配列 (Associative Array)

こちらはJavaのHashMapにそっくりで、文字列のキーと値がペアになった配列です。

// 連想配列の例
$user = [
    "name" => "Taro Yamada",
    "age" => 30,
    "email" => "taro@example.com"
];

重要なのは、PHPの世界ではこれらが別のデータ型ではなく、どちらも同じarray型だということです。JavaではArrayListHashMapは全くの別物でしたよね。

例えるなら、PHPの配列は一つの「魔法のファイリングキャビネット」です。

引き出しに0, 1, 2と番号シールを貼って使えば、それはインデックス配列。

引き出しに"顧客リスト", "請求書", "メモ"と名前シールを貼って使えば、それは連想配列。

なんと、一つのキャビネットの中で、番号の引き出しと名前の引き出しを混在させることさえ可能なのです!この柔軟性がPHPの強みです。


7.2 JavaのArrayListとHashMapに相当する使い方

それでは、Javaでの具体的なコレクション操作が、PHPの配列でどのように実現できるのかを見ていきましょう。

ArrayListのような使い方

JavaのArrayListのように、末尾に要素を追加したり、インデックスで値を取得したりする使い方です。

// 配列を初期化
$colors = ["red", "green"];

// 末尾に要素を追加する (Javaの add() に相当)
$colors[] = "blue"; // この [] という書き方が超便利!

// インデックスで値を取得する (Javaの get() に相当)
echo $colors[1]; // "green"

// 値を変更する (Javaの set() に相当)
$colors[0] = "aka";

// 現時点の$colorsの中身
// ["aka", "green", "blue"]
var_dump($colors);

HashMapのような使い方

JavaのHashMapのように、キーを指定して値を追加・取得する使い方です。

// 配列を初期化
$item = [
    "name" => "Desktop PC",
    "price" => 150000
];

// 新しいキーと値のペアを追加する (Javaの put() に相当)
$item["stock"] = 10;

// キーで値を取得する (Javaの get() に相当)
echo $item["name"]; // "Desktop PC"

// 値を変更する
$item["price"] = 148000;

// 現時点の$itemの中身
// ["name" => "Desktop PC", "price" => 148000, "stock" => 10]
var_dump($item);

このように、同じarray型でも、キーの扱い方によってJavaのArrayListHashMapの役割を自由に演じ分けることができるのです。


7.3 多次元配列の作成方法

配列の値にはどんなデータ型でも入れられる、ということは、配列の中にさらに配列を入れることも可能です。これを利用して「多次元配列」を簡単に作ることができます。

例えば、ユーザーのリストを考えてみましょう。各ユーザーは「名前」と「年齢」を持つ連想配列です。

$users = [
    [
        "name" => "Sato",
        "age" => 25
    ],
    [
        "name" => "Suzuki",
        "age" => 32
    ],
    [
        "name" => "Takahashi",
        "age" => 28
    ]
];

// 最初のユーザー(インデックス0)の名前を取得する
echo $users[0]["name"]; // "Sato"

// 3番目のユーザー(インデックス2)の年齢を取得する
echo $users[2]["age"]; // 28

インデックス配列の中に、連想配列が入っている形ですね。JavaでいうList<Map<String, Object>>のような構造が、PHPでは非常にシンプルに記述できます。[]を繋げていくだけで、深い階層のデータにも直感的にアクセスできるのが分かりますね。


7.4 配列操作関数(array_map, array_filter, array_merge)

PHPのもう一つの強みは、配列を操作するための便利な「組み込み関数」が、標準で山のように用意されていることです。Java 8以降のStream APIに慣れている方なら、きっと気に入るはずです!

ここでは、その中でも特に利用頻度の高い3つの関数を紹介します。

1. array_map

配列の各要素に、指定した関数(処理)を適用し、その結果からなる新しい配列を作成します。Javaのstream().map()に相当します。

$numbers = [1, 2, 3, 4, 5];

// 各要素を2倍にする
$doubled = array_map(function($n) {
    return $n * 2;
}, $numbers);

// $doubled の中身は [2, 4, 6, 8, 10]
var_dump($doubled);

2. array_filter

配列の各要素を、指定した関数(条件)でテストし、trueを返した要素だけを集めた新しい配列を作成します。Javaのstream().filter()に相当します。

$numbers = [1, 2, 3, 4, 5, 6];

// 偶数だけを抽出する
$evens = array_filter($numbers, function($n) {
    return $n % 2 === 0;
});

// $evens の中身は [2, 4, 6] (キーは元のものが維持されます)
// array_values()を使うとキーを0から振り直せます
var_dump(array_values($evens));

3. array_merge

一つ以上の配列を連結し、新しい一つの配列を作成します。

$array1 = ["red", "green"];
$array2 = ["blue", "yellow"];
$array3 = ["white"];

$merged = array_merge($array1, $array2, $array3);

// $merged の中身は ["red", "green", "blue", "yellow", "white"]
var_dump($merged);

ここで紹介したのは、ほんの氷山の一角です。PHPにはarray_で始まる便利な関数が、他にもたくさんあります。何か配列でやりたいことがあったら、まずは「php array 〇〇」のように検索してみてください。きっとあなたのやりたいことを実現してくれる関数が見つかりますよ!

8. 文字列処理

どんなアプリケーションでも、文字列の操作は避けて通れません。ユーザーの名前を表示したり、文章を整形したり、特定のキーワードを探したり。Javaにも豊富な文字列操作メソッドがありましたが、PHPにも強力かつユニークな機能がたくさん備わっています。

特に、引用符(クォーテーションマーク)の使い方一つで挙動が変わる、PHPならではの便利な機能は必見ですよ!


8.1 ダブルクォートでの変数展開(Javaにはない機能)

Javaで文字列と変数を結合するとき、+演算子で一つずつ繋げていましたよね。

String message = "こんにちは、" + name + "さん!";

PHPでもピリオド.演算子で同じことができますが、もっとスマートな方法があります。それが、ダブルクォート"を使った「変数展開」です!

PHPでは、文字列を囲む引用符にシングルクォート'とダブルクォート"の2種類があり、それぞれに明確な役割の違いがあります。

引用符の種類特徴例 ($name = "山田";)
シングルクォート '書かれた内容をそのまま、文字通りに表示する。変数展開は行わない。'こんにちは、$name さん'こんにちは、$name さん
ダブルクォート "文字列の中の変数を探し、その値に置き換えてから表示する(変数展開)。"こんにちは、$name さん"こんにちは、山田 さん

見てください!ダブルクォートで囲むだけで、文字列の中に書いた変数$nameが、その中身である「山田」という値に自動的に置き換えられました。いちいち.で連結する必要がないので、コードが非常にスッキリしますね。

例えるなら、シングルクォートは「書いた文字をそのままコピーする印刷機」。

ダブルクォートは「名簿データ(変数)を読み込んで、招待状の宛名を自動で差し込み印刷してくれる賢い印刷機」のようなものです。

変数が他の文字と隣接してしまい、どこまでが変数名か分からなくなってしまう場合は、変数を波括弧{}で囲むと明確になります。

$user = "Tanaka";
echo "{$user}'s pen."; // "Tanaka's pen."と正しく表示される

この変数展開は、JavaにはないPHPの非常に便利な機能なので、ぜひマスターしてください!


8.2 文字列連結とエスケープ

変数展開が便利とはいえ、もちろん従来通りの文字列連結も使います。前の章でも触れましたが、PHPではピリオド.演算子を使います。これはダブルクォートを使わない場面や、関数の戻り値などを連結するときに活躍します。

$greeting = "Hello, ";
$target = "World";
$message = $greeting . $target . "!"; // "Hello, World!"

次に、文字列の中で"や'といった特殊な意味を持つ文字そのものを表示したい場合の「エスケープ」です。

これはJavaと考え方は同じで、バックスラッシュ\を文字の前に置きます。

// ダブルクォートの中で " を表示したい
$str1 = "彼は \"PHPは簡単だ\" と言った。";

// シングルクォートの中で ' を表示したい
$str2 = 'It\'s a beautiful day.';

ただし、一つ注意点があります。改行を表す\nやタブを表す\tといった特殊なエスケープシーケンスは、変数展開と同じく、ダブルクォート"の中でしか機能しません。シングルクォート'の中では、ただの文字として表示されてしまいます。覚えておきましょう。


8.3 主な文字列関数(strlen, substr, str_replace)

PHPには、C言語のスタイルを受け継いだ、便利な文字列操作関数が多数用意されています。Javaのようにstring.method()というオブジェクト指向的な呼び出し方ではなく、function(string, ...)という手続き的な関数として呼び出すのが特徴です。

ここでは、代表的な3つの関数を紹介します。

1. strlen() - 文字列の長さを取得

JavaのmyString.length()に相当しますが、一つ大きな注意点があります。strlenは文字数ではなく、「バイト数」を返します。アルファベットだけなら問題ありませんが、日本語のようなマルチバイト文字を扱うと、意図しない結果になります。

$str_en = "abc";
echo strlen($str_en); // 3

$str_jp = "あいう";
echo strlen($str_jp); // 9 (UTF-8の場合、1文字3バイト × 3文字)

// 日本語の文字数を正しく数えたい場合
echo mb_strlen($str_jp); // 3

日本語を扱う際は、strlenではなく、マルチバイト対応版であるmb_strlenを必ず使うようにしてください!

2. substr() - 文字列の一部を切り出す

JavaのmyString.substring()に似ていますが、引数の意味が少し異なります。

$str = "Hello World";

// 3文字目から5文字分を切り出す
echo substr($str, 2, 5); // "llo W"

substr(対象文字列, 開始位置, 切り出す長さ)という指定の仕方をします。

3. str_replace() - 文字列を置換する

JavaのmyString.replace()に相当します。非常に強力で、検索文字列と置換文字列に配列を渡すことで、一度に複数の置換を行うこともできます。

// 単純な置換
$str = "今日の天気は晴れです。";
echo str_replace("晴れ", "雨", $str); // "今日の天気は雨です。"

// 複数の置換
$search = ["PHP", "難しい"];
$replace = ["Java", "面白い"];
$subject = "PHPは難しい言語です。";
echo str_replace($search, $replace, $subject); // "Javaは面白い言語です。"


8.4 正規表現(preg_match vs JavaのPattern/Matcher)

複雑なパターンの文字列を検索・抽出する「正規表現」。JavaではPatternクラスとMatcherクラスを組み合わせて、オブジェクト指向的に扱いました。

PHPでは、preg_という接頭辞を持つ、Perl互換の正規表現関数群を使うのが一般的です。中でも最もよく使うのがpreg_matchです。

JavaのPattern/Matcherが、パターンをコンパイルし、マッチャーオブジェクトを生成してからfind()group()で結果を取り出すという手順を踏んだのに対し、PHPのpreg_matchは1回の関数呼び出しでマッチングと結果の取得が完結します。

$subject = "私の郵便番号は123-4567です。";
$pattern = '/(\d{3})-(\d{4})/'; // パターンをスラッシュ / で囲むのがPHP流
$matches = [];

if (preg_match($pattern, $subject, $matches)) {
    // マッチした場合、$matchesに結果が格納される
    var_dump($matches);
}

実行すると、$matchesには以下のような配列が格納されます。

array(3) {
  [0]=> string(8) "123-4567" // パターン全体にマッチした部分
  [1]=> string(3) "123"      // 1番目のキャプチャグループ
  [2]=> string(4) "4567"      // 2番目のキャプチャグループ
}


preg_match(パターン, 対象文字列, 結果格納用配列)という形で呼び出し、マッチしたかどうかをif文で判定しつつ、マッチした場合は第三引数の$matchesに結果が自動で入る、という仕組みです。

例えるなら、JavaのPattern/Matcherは「捜査令状(Pattern)を取り、捜査官(Matcher)を派遣し、結果を一つずつ尋問(group)する」という手続き的な捜査。

PHPのpreg_matchは「探偵に写真(対象文字列)と人相書き(パターン)を渡したら、調査結果のレポート($matches配列)を丸ごと持ってきてくれる」という、より即物的なイメージです。

この手軽さは、PHPの大きな魅力の一つと言えるでしょう。

9. スーパーグローバル変数

Webアプリケーションを開発していると、ユーザーがフォームから送信したデータや、現在どのURLにアクセスしているか、ログインしているユーザーは誰か、といった情報が必ず必要になります。PHPでは、そういったプログラムのどこからでもアクセスできる、あらかじめ用意された特別な変数群があります。それが「スーパーグローバル変数」です。

Java ServletでHttpServletRequestHttpSessionオブジェクトから情報を取得していたように、PHPではこのスーパーグローバル変数を使って様々な情報を扱います。これらはすべて$_で始まり、連想配列として提供されます。さあ、Web開発の心臓部を探検しにいきましょう!


9.1 $_GET, $_POST, $_SERVERの役割

数あるスーパーグローバル変数の中でも、特に重要な3つを紹介します。

スーパーグローバル変数役割Javaでの相当機能
$_GETURLのクエリ文字列(?以降)で渡されたデータを格納する配列request.getParameter()(GETの場合)
$_POSTHTTP POSTメソッドで送信されたデータを格納する配列(フォームなど)request.getParameter()(POSTの場合)
$_SERVERサーバーやリクエストに関する情報を格納する配列requestオブジェクトの各種メソッド

$_GET

URLの後ろに付いている?name=Taro&age=30のような部分を「クエリ文字列」と呼びます。$_GETは、このクエリ文字列の情報を連想配列として保持しています。

// URLが http://example.com/index.php?name=Taro&age=30 の場合

echo $_GET['name']; // "Taro"
echo $_GET['age'];  // "30"

$_POST

HTMLのフォームでmethod="post"を指定して送信した場合、そのデータはURLには表示されません。そのようにして送信されたデータが、$_POST配列に格納されます。ログインフォームや、長文を送信する場合などに使われます。

<form action="process.php" method="post">
    <input type="text" name="comment">
    <button type="submit">送信</button>
</form>

// process.php の中身
echo $_POST['comment']; // フォームに入力された内容が表示される

$_SERVER

これはサーバー自身や、現在実行されているスクリプトの環境に関する情報が詰まった、宝箱のような配列です。例えば、どのIPアドレスからアクセスがあったか、どのURLがリクエストされたか、などの情報を取得できます。

// アクセスしてきたユーザーのIPアドレス
echo $_SERVER['REMOTE_ADDR'];

// リクエストされたURI(/index.phpなど)
echo $_SERVER['REQUEST_URI'];

// リクエストがGETかPOSTか
echo $_SERVER['REQUEST_METHOD'];

Javaでrequest.getRemoteAddr()request.getRequestURI()といったメソッドを呼び出していたのと似ていますね。


9.2 Java Servletのrequest.getParameter()との対応

JavaのServletでは、request.getParameter("name")という一つのメソッドを呼び出すだけで、データがGETで送られてきてもPOSTで送られてきても、よしなに取得してくれました。非常に便利でしたよね。

PHPでは、前述の通り$_GET$_POSTに分かれています。どちらでデータが送られてくるかによって、明示的に使い分ける必要があります。

「Javaみたいに、どっちでも受け取れる便利なものはないの?」

あります。それが$_REQUESTです。$_REQUESTは、$_GETと$_POST、そして$_COOKIE(クッキーの情報)を合わせた内容を持っています。

しかし、安易に$_REQUESTを使うことは一般的に推奨されません。なぜなら、そのデータがURLから来たのか(GET)、フォームから来たのか(POST)が曖昧になってしまい、セキュリティ上の問題を引き起こす可能性があるからです。

原則として、データの出所を明確にするためにも、$_GETと$_POSTをきちんと使い分けるようにしましょう。


9.3 $_SESSIONとJavaのHttpSessionの比較

複数のページにまたがってユーザーの情報を維持したい(例:ログイン状態、ショッピングカートの中身など)、そのために使うのが「セッション」です。この概念はJavaもPHPも全く同じです。

Javaでは、request.getSession()HttpSessionオブジェクトを取得し、session.setAttribute()session.getAttribute()といったメソッドで値を読み書きしていました。

PHPでは、これが驚くほどシンプルになります。

まず、セッションを利用したいPHPスクリプトの冒頭で、必ずsession_start()という関数を一度だけ呼び出します。これは「これからセッション機能を使いますよ」という開始の合図です。

<?php
session_start(); // 必ずページの処理の最初に書く!

// これ以降、$_SESSIONが使えるようになる

この合図さえすれば、あとは$_SESSIONというスーパーグローバル配列に、普通の連想配列と同じように値を入れたり、取り出したりするだけです!

// --- page1.php ---
session_start();
$_SESSION['username'] = "Taro Yamada";
$_SESSION['login_time'] = time(); // 現在のタイムスタンプを保存
echo "セッションに情報を保存しました。";

// --- page2.php ---
session_start();
if (isset($_SESSION['username'])) {
    echo "ようこそ、" . $_SESSION['username'] . "さん";
} else {
    echo "あなたはまだログインしていません。";
}

setAttributegetAttributeのようなメソッドは不要で、連想配列を操作するだけ。非常に直感的で分かりやすいですね。

ただし、セッションを使う全てのページでsession_start()を忘れずに書くこと。これを忘れると$_SESSIONはただの空っぽの配列になってしまいますので、注意してください!


9.4 $_FILESとファイルアップロード

ユーザーに画像やドキュメントをアップロードしてもらうのも、Webアプリケーションの一般的な機能です。

Javaでは@MultipartConfigアノテーションを付けたり、request.getPart()を使ったりと、少し準備が必要でした。

PHPでは、ファイルアップロード専用のスーパーグローバル変数$_FILESが用意されています。

まず、HTMLフォームでファイルを送信するには、enctype="multipart/form-data"というおまじないを<form>タグに付ける必要があります。

<form action="upload.php" method="post" enctype="multipart/form-data">
    <input type="file" name="user_avatar">
    <button type="submit">アップロード</button>
</form>

このフォームが送信されると、upload.php側では$_FILES変数にアップロードされたファイルの情報が格納されます。$_FILESもまた連想配列で、その構造は少し特殊です。

// upload.php の中身
if (isset($_FILES['user_avatar'])) {
    var_dump($_FILES['user_avatar']);
}

var_dumpすると、$_FILES['user_avatar']の中には、さらに以下のようなキーを持つ連想配列が入っていることが分かります。

  • 'name': アップロードされた元のファイル名(例: my_photo.jpg
  • 'type': ファイルのMIMEタイプ(例: image/jpeg
  • 'tmp_name': サーバー上に一時的に保存されているファイルのパス。これが最重要!
  • 'error': アップロード時のエラーコード。0なら成功。
  • 'size': ファイルサイズ(バイト単位)。

実際の処理では、まず'error'が0(成功)であることを確認します。

次に、'tmp_name'にある一時ファイルを、move_uploaded_file()という専用の関数を使って、サーバー上の安全で永続的な場所(例: uploads/ディレクトリなど)に移動させます。

$upload_dir = 'uploads/';
$tmp_path = $_FILES['user_avatar']['tmp_name'];
$upload_path = $upload_dir . $_FILES['user_avatar']['name'];

if (move_uploaded_file($tmp_path, $upload_path)) {
    echo "ファイルのアップロードに成功しました!";
} else {
    echo "ファイルのアップロードに失敗しました。";
}

一時ファイルはスクリプトの実行が終わると消えてしまうので、必ずmove_uploaded_file()で移動させる、という流れを覚えておきましょう!

10. フォーム処理

Webアプリケーションの花形、フォーム処理です!ユーザーからの入力を受け取り、それを検証し、データベースに保存したり、結果を表示したり。これまでの章で学んだスーパーグローバル変数、文字列処理、制御構文といった知識を総動員して、安全で使いやすいフォームを作成する方法を学びましょう。

Java Servletでの開発経験は、ここでも大いに役立ちます。概念は同じでも、PHPの実装がいかにシンプルで手軽か、きっと驚くはずですよ。


10.1 HTMLフォームとの連携

まずは、すべての基本となるHTMLフォームと、それを受け取るPHPスクリプトの連携を見てみましょう。これは、これまでの知識の総復習です。

まず、ユーザーが入力するためのHTMLファイルを用意します。

<!DOCTYPE html>
<html>
<head>
    <title>お問い合わせフォーム</title>
</head>
<body>
    <form action="process.php" method="post">
        お名前: <input type="text" name="username"><br>
        メールアドレス: <input type="email" name="email"><br>
        <button type="submit">送信</button>
    </form>
</body>
</html>

重要なのは2点です。

  • action="process.php": このフォームの送信先PHPファイルを指定します。
  • method="post": 送信方法をPOSTに指定しています。

次に、このフォームからのデータを受け取るprocess.phpを作成します。

// process.php
<?php
// POSTで送信されたデータをスーパーグローバル変数$_POSTで受け取る
$username = $_POST['username'];
$email = $_POST['email'];

echo "こんにちは、" . $username . "さん。<br>";
echo "あなたのメールアドレスは " . $email . " ですね。";
?>

index.htmlのフォームで入力し送信ボタンを押すと、process.phpがそのデータを受け取り、画面に表示します。$_POST['username']のusernameの部分は、HTMLの<input>タグのname属性と対応している点に注目してください。

このシンプルな連携が、PHPでのWebアプリケーション開発の基本となります。


10.2 GETとPOSTの違い(Servletと同様だが実装が簡潔)

GETとPOSTの使い分け。この概念はJava Servletで学んだことと全く同じです。

  • GET: サーバー上の情報を取得するだけで、状態を変更しないアクションに使います。例えば、キーワード検索や、特定のIDの商品ページを表示するなどです。データはURLに含まれ、誰にでも見えます。
  • POST: サーバー上の状態を変更するアクションに使います。ユーザー登録、コメントの投稿、商品の注文など、重要なデータを送信する場合です。データはリクエストの本体(ボディ)に含まれ、URLには表示されません。

JavaではdoGet()メソッドやdoPost()メソッドをオーバーライドして処理を分けていましたが、PHPではどちらのメソッドでリクエストが来たかに応じて、$_GET$_POSTのどちらかにデータが入っている、というだけの違いです。

// GETリクエストを処理する例 (search.php?keyword=PHP)
$keyword = $_GET['keyword'];
echo $keyword . "の検索結果...";

// POSTリクエストを処理する例
$username = $_POST['username'];
$password = $_POST['password'];
echo $username . "さん、ようこそ!";

HttpServletRequestのような複雑なオブジェクトを介さず、ただの連想配列にアクセスするだけ。この実装の簡潔さがPHPの魅力です。


10.3 サニタイズとエスケープ(htmlspecialchars)

ここからはWebセキュリティに関する、非常に重要な話です。絶対に覚えてください!

ユーザーが入力したデータを、そのまま画面に表示するのは非常に危険です。悪意のあるユーザーが、入力フォームにHTMLタグやJavaScriptコードを埋め込んでくる可能性があるからです。

例えば、名前の入力欄に<script>alert('Hacked!');</script>と入力されたとします。

もし、この入力を何の処理もせずにそのまま画面に表示してしまうと、そのページを見た他のユーザーのブラウザで、このJavaScriptが実行されてしまいます。これが「クロスサイトスクリプティング(XSS)」と呼ばれる、代表的な脆弱性の一つです。

このXSS攻撃を防ぐための、PHPの最も基本的な防御策がhtmlspecialchars()関数です。

この関数は、HTMLで特別な意味を持つ文字を、無害な「HTMLエンティティ」と呼ばれる文字列に変換してくれます。

  • <&lt;
  • >&gt;
  • "&quot;
  • &&amp;
// 危険な入力
$malicious_input = "<script>alert('Hacked!');</script>";

// 何も対策しない場合(危険!)
// echo $malicious_input;

// htmlspecialcharsでエスケープ処理を行う(安全!)
$safe_output = htmlspecialchars($malicious_input, ENT_QUOTES, 'UTF-8');
echo $safe_output;
// ブラウザには <script>alert('Hacked!');</script> という文字列がそのまま表示されるだけで、
// JavaScriptとして実行されることはない。

例えるなら、htmlspecialchars()は空港の保安検査官です。危険物である<script>タグを持ち込もうとしても、そのまま機内(Webページ)には持ち込ませません。代わりに、中身が見える安全なビニール袋(&lt;script&gt;)に入れてくれます。これなら、誰もが「これは危険物だったものだ」と認識できますが、もはや危険な機能は失われています。

鉄の掟です。ユーザーが入力したデータをWebページに表示する際は、必ずhtmlspecialchars()を通すこと!


10.4 バリデーションの実装

ユーザーからの入力が、こちらの期待する形式やルールに合っているかチェックする工程を「バリデーション」と呼びます。例えば、「メールアドレスの形式になっているか」「年齢は数字で入力されているか」「必須項目が空になっていないか」といったチェックです。

バリデーションは、データをデータベースに保存する前に行う、最後の砦です。

PHPでの基本的なバリデーションの流れは以下のようになります。

  1. エラーメッセージを格納するための配列を用意します。(例: $errors = [];
  2. 各入力項目に対して、チェックを行います。
    • 必須項目が空でないか? → empty()関数でチェック
    • メールアドレスの形式は正しいか? → filter_var($email, FILTER_VALIDATE_EMAIL)でチェック
    • 文字数は制限内か? → mb_strlen()でチェック
  3. チェックに引っかかったら、エラーメッセージを$errors配列に追加します。
  4. すべてのチェックが終わった後、$errors配列が空かどうかを調べます。
    • 空の場合 → バリデーション成功。データベースへの保存などの次の処理に進む。
    • 空でない場合 → バリデーション失敗。エラーメッセージと共に、もう一度入力フォームを表示する。
// process.php のバリデーションの例
$errors = [];
$email = $_POST['email'];

if (empty($email)) {
    $errors[] = "メールアドレスは必須です。";
} else if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
    $errors[] = "メールアドレスの形式が正しくありません。";
}

if (count($errors) === 0) {
    // 成功時の処理
    echo "ありがとうございます。処理を受け付けました。";
} else {
    // 失敗時の処理
    echo "入力にエラーがあります。<br>";
    foreach ($errors as $error) {
        echo $error . "<br>";
    }
}

この基本的な流れを身につければ、安全で堅牢なフォーム処理を実装することができます。

11. ファイル操作

アプリケーションを開発する上で、設定ファイルの読み込み、ログの書き出し、CSVデータのエクスポートなど、ファイルの操作は欠かせない処理です。JavaではFileInputStream/OutputStreamReader/Writerといった多くのクラスを組み合わせて、オブジェクト指向的にファイルを扱ってきましたね。

PHPでのファイル操作は、より手続き的で、驚くほど少ないコードで目的を達成できる関数が数多く用意されています。Javaの常識を良い意味で覆す、PHPの手軽さを体感してみましょう!


11.1 ファイル読み書き(fopen, file_get_contents)

PHPでファイルを扱う方法には、大きく分けて2つのアプローチがあります。一つは手軽な一括操作、もう一つはJavaのストリーム処理に似た、より細かい制御が可能な方法です。

とにかく手軽! file_get_contents / file_put_contents

もし、ファイルの内容を「丸ごと」一気に読み込んで文字列として扱いたいなら、file_get_contents関数が最適です。

// sample.txt の内容をすべて読み込んで変数に格納
$content = file_get_contents('data/sample.txt');

if ($content !== false) {
    echo $content;
} else {
    echo "ファイルの読み込みに失敗しました。";
}

たったこれだけです! JavaでFileReaderBufferedReaderを準備し、whileループで一行ずつ読み込んでStringBuilderに追記していく…といった定型的な処理を、PHPではこの一行がすべて肩代わりしてくれます。

逆に、ファイルに文字列を書き込みたい場合はfile_put_contentsを使います。

$log_data = "Error occurred at: " . date('Y-m-d H:i:s');

// $log_dataの内容をlog.txtに書き込む(ファイルがなければ新規作成、あれば上書き)
file_put_contents('logs/log.txt', $log_data);

この手軽さは、まさにスクリプト言語ならではの強みですね。📜

より細かく制御! C言語スタイルのファイル操作

大きなファイルを少しずつ処理したい場合や、より細かい制御が必要な場合は、Javaのストリーム処理に似た、C言語スタイルのアプローチを使います。

  1. fopen(): ファイルを開き、「ファイルポインタ(リソース)」を取得します。
  2. fgets()/fread(): ファイルからデータを読み込みます。
  3. fwrite(): ファイルにデータを書き込みます。
  4. fclose(): 開いたファイルを閉じます。
// ファイルを読み込みモードで開く
$file_handle = fopen('data/sample.txt', 'r');

if ($file_handle) {
    // ファイルの終わりまで一行ずつ読み込む
    while (($line = fgets($file_handle)) !== false) {
        echo $line;
    }

    // 必ずファイルを閉じる!
    fclose($file_handle);
}

fopenの第二引数「モード」で、ファイルをどのように開くかを指定します。

モード説明ファイルがない場合
r読み込み専用。ポインタはファイルの先頭。エラー
w書き込み専用。ポインタは先頭。ファイル内容は消去される新規作成
a追記書き込み用。ポインタはファイルの末尾。新規作成
r+読み書き用。ポインタは先頭。エラー
w+読み書き用。ポインタは先頭。ファイル内容は消去される新規作成
a+読み書き用。ポインタは末尾。新規作成

Javaでtry-with-resources構文やfinallyブロックでリソースを確実に閉じていたように、PHPでもfcloseでファイルポインタをしっかり閉じることが重要です。忘れると、サーバーに開かれたファイルが残り続け、問題を引き起こす可能性がありますよ。


11.2 CSVの読み込みと書き込み

業務アプリケーションでは、CSV(Comma-Separated Values)形式のデータを扱う機会が非常に多いですよね。Javaではライブラリを使うのが一般的でしたが、PHPにはCSVを扱うための専用関数が標準で組み込まれています。これも非常に便利なポイントです! 📊

CSVの読み込み: fgetcsv

fgetcsv関数は、ファイルポインタからCSV形式のデータを1行読み込み、自動的にカンマで分割して配列として返してくれます。

// user_data.csv
// 1,Taro Yamada,taro@example.com
// 2,Hanako Suzuki,hanako@example.com

$users = [];
if (($handle = fopen("data/user_data.csv", "r")) !== false) {
    // 1行ずつCSVとして読み込み、配列に変換
    while (($data = fgetcsv($handle)) !== false) {
        $users[] = $data;
    }
    fclose($handle);
}
var_dump($users);
// 出力結果:
// array(2) {
//   [0]=> array(3) { [0]=> string(1) "1", [1]=> string(11) "Taro Yamada", [2]=> string(16) "taro@example.com" }
//   [1]=> array(3) { [0]=> string(1) "2", [1]=> string(13) "Hanako Suzuki", [2]=> string(18) "hanako@example.com" }
// }

CSVの書き込み: fputcsv

fputcsv関数は、配列データをCSVの1行としてフォーマットし、ファイルに書き込んでくれます。

$new_user = [3, 'Jiro Sato', 'jiro@example.com'];

// 追記モードでファイルを開く
$handle = fopen("data/user_data.csv", "a");

// 配列をCSV形式で書き込む
fputcsv($handle, $new_user);

fclose($handle);

これらの関数のおかげで、面倒なカンマの処理やダブルクォートでの囲みなどを気にすることなく、直感的にCSVデータを扱うことができます。


11.3 アップロード処理

このトピックは「9. スーパーグローバル変数」の章でも扱いましたが、ファイル操作という文脈で再度確認しておきましょう。これは非常に重要な処理です。

HTMLフォームから送信されたファイルは、PHPではまずサーバー上の一時的な場所に保存されます。そのファイルの情報は$_FILESスーパーグローバル変数に格納されますね。

開発者の仕事は、その一時ファイルを検証し、安全な永続的保管場所に移動させることです。そのための専用関数がmove_uploaded_fileでした。

// HTMLの <input type="file" name="my_file"> から送信されたとする

// アップロード先のディレクトリ
$upload_dir = 'uploads/';
// エラーがないかチェック (UPLOAD_ERR_OKは定数で、値は0)
if (isset($_FILES['my_file']) && $_FILES['my_file']['error'] === UPLOAD_ERR_OK) {
    // 一時ファイルのパス
    $tmp_name = $_FILES['my_file']['tmp_name'];
    
    // 保存するファイル名(元の名前をそのまま使うのはセキュリティ上リスクがある場合も)
    $name = basename($_FILES['my_file']['name']);
    
    // 一時ファイルを永続的な場所に移動する
    if (move_uploaded_file($tmp_name, $upload_dir . $name)) {
        echo "ファイルが正常にアップロードされました。";
    } else {
        echo "ファイルの移動に失敗しました。";
    }
} else {
    echo "ファイルのアップロードに失敗しました。エラーコード: " . $_FILES['my_file']['error'];
}

この一連の流れは、PHPでWebアプリケーションを構築する上で頻出するパターンです。$_FILESの構造とmove_uploaded_file関数の使い方を、しっかりマスターしておきましょう!

12. オブジェクト指向PHP(OOP)

Java開発者の皆さん、お待たせしました!いよいよ、あなたのホームグラウンドであるオブジェクト指向プログラミング(OOP)の世界です。PHPは単なる手続き的なスクリプト言語ではありません。Javaに引けを取らない、成熟した強力なOOPの機能を備えています。

クラス、継承、インターフェース...。あなたがJavaで培ってきた設計思想は、PHPでもそのまま活かすことができます。この章では、Javaとの構文の違いに焦点を当てながら、PHPでのOOPの書き方をマスターしていきましょう。ここは、あなたの知識が最も輝く場所です!✨


12.1 クラス定義(Javaとの記法比較)

まずは、すべての基本となるクラスの定義方法です。Javaのクラス定義と並べて、違いを一目で確認してみましょう。

機能JavaPHP
プロパティ(メンバ変数)private String name;private string $name;
メソッドpublic void sayHello() { ... }public function sayHello(): void { ... }
インスタンス化User user = new User();$user = new User();
メンバへのアクセスuser.name, user.sayHello()$user->name, $user->sayHello()
自身のインスタンスthis$this

Javaのクラス

Java

class User {
    private String name;

    public void setName(String name) {
        this.name = name;
    }

    public String getName() {
        return this.name;
    }
}

PHPのクラス

PHP

class User
{
    private string $name;

    public function setName(string $name): void
    {
        $this->name = $name;
    }

    public function getName(): string
    {
        return $this->name;
    }
}

どうでしょうか?非常によく似ていますよね。注意すべきPHP特有のポイントは以下の通りです。

  • プロパティ(変数)には $ が付く: クラスのメンバ変数にも $ が必要です。
  • メソッド定義には function キーワードが付く: publicなどの修飾子の後に function を書きます。
  • メンバへのアクセスは ->(アロー演算子): Javaの .(ドット)の代わりに、-> を使ってインスタンスのプロパティやメソッドにアクセスします。これはPHPのOOPで最も重要な記号です!

12.2 コンストラクタとデストラクタ(__construct, __destruct)

オブジェクトが生成・破棄される際の特別な処理を定義するメソッドです。

コンストラクタ: __construct

Javaではクラス名と同じ名前のメソッドがコンストラクタでしたが、PHPでは__constructという決まった名前の「マジックメソッド」を使います。先頭にアンダースコアが2つ付くメソッドは、特定のタイミングでPHPが自動的に呼び出してくれる特別なメソッドです。

class Person
{
    public string $name;

    public function __construct(string $name)
    {
        echo "Personオブジェクトが生成されました。\n";
        $this->name = $name;
    }
}

$person = new Person("Yamada"); // この瞬間に__constructが呼ばれる
echo $person->name; // "Yamada"

さらに、PHP 8からは「コンストラクタプロパティプロモーション」という、Javaにはない非常に便利な機能が追加されました。

// コンストラクタプロパティプロモーションを使った書き方
class Person
{
    public function __construct(public string $name)
    {
        echo "Personオブジェクトが生成されました。\n";
    }
}

$person = new Person("Sato");
echo $person->name; // "Sato"

コンストラクタの引数にアクセス修飾子を付けるだけで、プロパティの宣言と初期化を同時に行ってくれます。驚くほどコードが簡潔になりますね!

デストラクタ: __destruct

オブジェクトへの参照がすべてなくなり、破棄される直前に自動的に呼び出されるのが__destructです。Javaのfinalizeメソッドに似ていますが、PHPのリクエスト・レスポンスモデルの中では、スクリプトの終了時などに呼ばれるため、実行タイミングは比較的予測しやすいです。

class Database
{
    public function __construct() { echo "DB接続\n"; }
    public function __destruct() { echo "DB切断\n"; }
}

$db = new Database(); // "DB接続"
// ...スクリプトの処理...
// スクリプト終了時に$dbオブジェクトが破棄され、"DB切断"と表示される


12.3 アクセス修飾子(public/private/protected)

これはJavaと全く同じです!あなたの知識をそのまま使えます。

  • public: どこからでもアクセス可能。
  • protected: そのクラス自身と、そのクラスを継承した子クラス内からのみアクセス可能。
  • private: そのクラス自身からのみアクセス可能。

Javaと同様、カプセル化の原則に従い、プロパティは基本的にprivateにし、publicなメソッド(ゲッターやセッター)経由でアクセスするのが良い設計です。


12.4 継承(extends、多重継承不可は共通)

クラスの機能を再利用するための継承。これもJavaと全く同じで、extendsキーワードを使います。そして、Javaと同様にPHPもクラスの多重継承はサポートしていません

親クラスのメソッドを呼び出すには、Javaのsuperに相当するものとして、parent::を使います。

class Animal
{
    public function cry(): void
    {
        echo "......";
    }
}

class Dog extends Animal
{
    // メソッドのオーバーライド
    public function cry(): void
    {
        echo "ワン!";
    }
    
    public function parentCry(): void
    {
        // 親クラスのメソッドを呼び出す
        parent::cry();
    }
}

$dog = new Dog();
$dog->cry();       // "ワン!"
$dog->parentCry(); // "......"


12.5 インターフェースと抽象クラス

こちらもJavaの概念とほぼ同じです。

インターフェース

実装すべきメソッドのシグネチャ(名前、引数、戻り値の型)だけを定義した「契約書」です。

interfaceキーワードで定義し、implementsキーワードでクラスに実装します。

interface Drawable
{
    public function draw(): void;
}

class Circle implements Drawable
{
    public function draw(): void
    {
        echo "円を描画します。";
    }
}

抽象クラス

具体的な実装を持つメソッドと、実装を持たない抽象メソッドの両方を持つことができるクラスです。

abstractキーワードを使って定義します。

abstract class Shape
{
    // 具象メソッド
    public function getDescription(): string
    {
        return "これは図形です。";
    }

    // 抽象メソッド(子クラスでの実装を強制)
    abstract public function getArea(): float;
}

class Rectangle extends Shape
{
    public function __construct(private float $width, private float $height) {}

    public function getArea(): float
    {
        return $this->width * $this->height;
    }
}


12.6 staticメンバと名前空間

staticメンバ

インスタンスではなく、クラス自身に属するプロパティやメソッドです。Javaと全く同じ概念で、staticキーワードを使います。

ただし、アクセス方法が異なります。->ではなく、::(スコープ解決演算子)を使います。

class MathUtil
{
    public static float $pi = 3.14;

    public static function circleArea(float $radius): float
    {
        // staticプロパティには self:: でアクセス
        return $radius * $radius * self::$pi;
    }
}

// インスタンス化せずに直接アクセス!
echo MathUtil::$pi; // 3.14
echo MathUtil::circleArea(10); // 314

名前空間

Javaのパッケージに相当する機能です。クラス名が他のライブラリなどと衝突するのを防ぎます。

ファイルの先頭でnamespaceキーワードを使って空間を宣言し、他の名前空間のクラスを使う際にはuseキーワードでインポートします。Javaのimportと全く同じ役割ですね。

// App/Models/User.php
namespace App\Models;

class User
{
    // ...
}

// index.php
namespace App;

// Javaの import App.Models.User; と同じ
use App\Models\User;

$user = new User();

いかがでしたか?$->::といった記号の違いこそあれ、OOPの根幹をなす概念はJavaとPHPで驚くほど共通していることがお分かりいただけたかと思います。この知識があれば、PHPでも大規模で堅牢なアプリケーションを設計・構築することが可能です!

13. データベース連携

Webアプリケーションの核となる、データベースとの連携です。Javaでは**JDBC(Java Database Connectivity)**を使い、ドライバを読み込み、ConnectionPreparedStatementResultSetといったオブジェクトを駆使してデータベースを操作してきましたね。

PHPでは、このJDBCに相当する、モダンで汎用的な仕組みとして**PDO(PHP Data Objects)**が用意されています。PDOをマスターすれば、MySQL、PostgreSQL、SQLiteなど、様々なデータベースを同じ作法で扱うことができます。Javaで培ったデータベースの知識を、PHPの世界で再現してみましょう!


13.1 PDOによる接続(JavaのJDBCとの比較)

PDOは、異なるデータベース間の差異を吸収してくれる、優れた抽象化レイヤーです。JavaのJDBCが、MySQL用のConnector/JやPostgreSQL用のJDBC Driverといった異なるドライバを同じAPIで扱えるようにしてくれたのと、全く同じコンセプトです。

データベースに接続するには、まず**DSN(Data Source Name)**と呼ばれる接続文字列を作成します。DSNには、どのデータベースを使うのか、ホスト名、データベース名といった接続情報を含めます。これは、JavaのJDBC URL (jdbc:mysql://hostname:3306/dbname) に相当するものです。

機能Java (JDBC)PHP (PDO)
APIJDBC APIPDO
ドライバcom.mysql.cj.jdbc.Drivermysql (PDOドライバ)
接続文字列jdbc:mysql://...DSN (mysql:host=...)
接続DriverManager.getConnection(...)new PDO(...)
例外SQLExceptionPDOException

実際にPHPでMySQLデータベースに接続するコードを見てみましょう。

// --- 接続情報 ---
// DSN (Data Source Name)
$dsn = 'mysql:host=localhost;dbname=test_db;charset=utf8mb4';
// ユーザー名
$user = 'db_user';
// パスワード
$password = 'db_password';

try {
    // データベースに接続し、PDOオブジェクトを生成
    $pdo = new PDO($dsn, $user, $password);

    // エラー発生時に例外をスローするように設定
    $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

    echo "データベースへの接続に成功しました。";

} catch (PDOException $e) {
    // 接続失敗時にエラーメッセージを表示
    echo "データベースへの接続に失敗しました: " . $e->getMessage();
    exit; // スクリプトを終了
}

Javaでtry-catchを使ってSQLExceptionを捕捉していたように、PHPでもtry-catchPDOExceptionを捕捉するのが定石です。これにより、パスワード間違いなどの接続エラーに安全に対処できます。


13.2 SQL実行とデータ取得

接続が確立したら、いよいよSQLを実行します。基本的な流れは**「準備(prepare) → 実行(execute) → 結果取得(fetch)」**となり、JavaのPreparedStatementの流れと非常によく似ています。

全件取得: fetchAll

まずは、usersテーブルから全てのユーザーを取得する例を見てみましょう。

// 実行したいSQL文
$sql = 'SELECT id, name, email FROM users';

// SQL文を準備する (PreparedStatementの作成に相当)
$stmt = $pdo->prepare($sql);

// SQL文を実行する
$stmt->execute();

// 結果をすべて取得し、連想配列の配列として受け取る
$users = $stmt->fetchAll(PDO::FETCH_ASSOC);

// 結果を表示
foreach ($users as $user) {
    echo "ID: " . $user['id'] . ", Name: " . $user['name'] . "<br>";
}

ここで重要なのがfetchAll(PDO::FETCH_ASSOC)です。fetchAllは結果セットの全行を一度に取得する便利なメソッドで、引数のPDO::FETCH_ASSOCは、各行を**「カラム名をキーとした連想配列」**として取得するためのモード指定です。これにより、$user['name']のように直感的にデータにアクセスできます。

1件ずつ取得: fetch

メモリを節約したい場合など、一度に1行ずつ結果を取得するにはfetchメソッドをwhileループの中で使います。これはJavaでresultSet.next()をループさせるのと全く同じ感覚です。

$stmt = $pdo->prepare($sql);
$stmt->execute();

// 1行ずつ連想配列として取得し、結果がなくなるまでループ
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
    echo "ID: " . $row['id'] . ", Name: " . $row['name'] . "<br>";
}


13.3 プレースホルダでのSQLインジェクション対策

セキュリティに関する最重要項目です!

ユーザーからの入力を直接SQL文に連結してはいけません。悪意のある文字列(例: ' OR '1'='1)を注入され、データベースを不正に操作されてしまう「SQLインジェクション」という脆弱性を生むからです。

この絶対的な対策が、JavaのPreparedStatementでも使ったプレースホルダです。PDOでは、SQL文の中に「値の仮置き場」としてプレースホルダを記述し、後から安全に値を束縛(バインド)します。

🛡️ アナロジー: プレースホルダを使ったSQLは、安全な「公式書類」のようなものです。SQL文という「書類のフォーマット(様式)」を先にデータベースに提出します。この様式には「氏名: ___」のような**空欄(プレースホルダ)**があります。その後、ユーザーが入力したデータは、あくまで「空欄に記入する文字」として扱われ、書類の様式自体を書き換えることは絶対にできません。これにより、安全が保たれるのです。

PDOのプレースホルダには2種類ありますが、名前付きプレースホルダ(例: :name)が読みやすいため推奨されます。

// ユーザーIDを指定して、1件のユーザー情報を取得する
$sql = 'SELECT * FROM users WHERE id = :id';

$stmt = $pdo->prepare($sql);

// プレースホルダに値をバインドし、SQLを実行
// executeの引数に、キーがプレースホルダ名、値が実際の値となる連想配列を渡す
$stmt->execute([':id' => 1]);

// 結果を1件だけ取得
$user = $stmt->fetch(PDO::FETCH_ASSOC);

if ($user) {
    var_dump($user);
} else {
    echo "ユーザーが見つかりませんでした。";
}

INSERTUPDATEでも同様です。

$sql = 'INSERT INTO users (name, email) VALUES (:name, :email)';
$stmt = $pdo->prepare($sql);
$stmt->execute([
    ':name' => 'Jiro Sato',
    ':email' => 'jiro@example.com'
]);

JavaのpreparedStatement.setInt(1, id)のように、一つずつ型を指定してバインドするより、配列で一括して渡せるため非常に簡潔ですね。

ユーザーからの入力値を含むSQLでは、必ずプレースホルダを使いましょう!


13.4 トランザクション管理

複数のSQL処理を「すべて成功」か「すべて失敗」のどちらかにまとめたい。銀行の振り込み処理などで使われる、このトランザクションの概念も、Javaと全く同じように扱えます。

  1. beginTransaction(): トランザクションを開始します。(Javaのconnection.setAutoCommit(false)に相当)
  2. commit(): すべての処理が成功した場合に、変更を確定させます。
  3. rollBack(): 処理の途中で問題が発生した場合に、beginTransaction以降のすべての変更を取り消します。

これらの処理も、try-catchブロックで囲むのが定石です。

try {
    // 1. トランザクション開始
    $pdo->beginTransaction();

    // 2. Aさんの残高を減らす
    $stmt1 = $pdo->prepare('UPDATE accounts SET balance = balance - 1000 WHERE name = :name');
    $stmt1->execute([':name' => 'Aさん']);

    // 3. Bさんの残高を増やす
    $stmt2 = $pdo->prepare('UPDATE accounts SET balance = balance + 1000 WHERE name = :name');
    $stmt2->execute([':name' => 'Bさん']);
    
    // 4. すべて成功したらコミット
    $pdo->commit();

    echo "振り込みが完了しました。";

} catch (PDOException $e) {
    // 5. エラーが発生したらロールバック
    $pdo->rollBack();
    
    echo "エラーが発生したため、処理を中断しました: " . $e->getMessage();
}

PDOを使えば、Javaで培ったデータベースプログラミングの知識と設計パターンを、PHPの世界でも安全かつ効率的に活かすことができます。

14. エラーと例外処理

どんなに注意深くプログラミングしても、予期せぬ問題は発生します。ファイルが見つからなかったり、データベース接続に失敗したり、不正なデータが入力されたり。このような「異常事態」に備え、プログラムの安定性を保つ仕組みがエラーと例外処理です。

Javaでは、コンパイラがチェックしてくれる**検査例外(Checked Exceptions)**という厳格な仕組みがあり、try-catchでの対応を強制される場面も多かったですよね。PHPの歴史は少し異なり、伝統的な「エラー」と、Javaによく似たモダンな「例外」が共存しています。この違いと共通点を理解し、PHPで堅牢なコードを書く方法を学びましょう。


14.1 エラーの種類(Javaの例外階層との違い)

PHPのエラーハンドリングを理解するには、まず「伝統的なエラー」と「モダンな例外」の2つを知る必要があります。

伝統的なPHPエラー

初期のPHPでは、問題が発生すると、その深刻度に応じて以下のようなエラーが報告されていました。

  • E_NOTICE (通知): 「未定義の変数を使っているよ」など、すぐには問題にならないかもしれないが、バグの可能性がある箇所を教えてくれる軽い通知。スクリプトの実行は継続します。
  • E_WARNING (警告): 「存在しないファイルをincludeしようとしたよ」など、明らかな問題があるが、処理の続行は試みる警告。スクリプトの実行は継続します。
  • E_ERROR (致命的エラー): 「定義されていない関数を呼び出したよ」など、これ以上処理を続けられない致命的な問題。スクリプトの実行は即座に停止します。
モダンな例外階層: Throwable

現在のPHP (PHP 7以降) では、Javaと非常によく似た例外処理の仕組みが導入されました。すべてのエラーと例外は、**Throwable**という共通のインターフェースを実装しています。これはJavaのThrowableクラスに相当します。

そして、PHPのThrowableは、主に2つの系統に分かれています。

系統説明Javaでの相当機能
Error従来の致命的エラーに相当する、PHP内部の深刻な問題。型エラーや文法ミスなど。Errorクラス (OutOfMemoryErrorなど)
Exceptionアプリケーションコード内で発生し、対処が可能な異常事態。Exceptionクラス (IOExceptionなど)

Javaとの決定的な違い: 検査例外の不在

ここが最も重要なポイントです。PHPには、Javaの検査例外(Checked Exceptions)がありません。

Javaでは、IOExceptionのように、try-catchで捕捉するか、メソッドにthrowsを付けて呼び出し元に処理を委譲するかをコンパイラが強制しました。

PHPの例外は、すべてJavaの非検査例外(Unchecked Exceptions)、つまりRuntimeExceptionのサブクラスのように振る舞います。try-catchで捕捉するかどうかは、完全にプログラマの任意です。この哲学の違いは、PHPの柔軟性と、Javaの厳格さを象徴していると言えるでしょう。


14.2 try-catchの使い方

例外を捕捉するためのtry-catch-finally構文は、Javaと全く同じです!これは嬉しいですね。

try {
    // 例外が発生する可能性のあるコード
    $pdo = new PDO('mysql:host=invalid_host;dbname=test', 'user', 'pass');

} catch (PDOException $e) {
    // PDOException(データベース関連の例外)を捕捉する
    echo "データベースエラー: " . $e->getMessage();

} catch (Exception $e) {
    // その他の一般的な例外を捕捉する
    echo "予期せぬ例外が発生しました: " . $e->getMessage();

} finally {
    // 例外の発生有無にかかわらず、必ず実行される処理
    echo "\n処理を終了します。";
}

Javaと同様に、catchブロックを複数記述して、例外の種類ごとに異なる処理を行えます。その際、より具体的な例外クラス(PDOException)を先に、より一般的な例外クラス(Exception)を後に書くのがセオリーです。


14.3 ユーザー定義例外

アプリケーション固有の異常事態を、より明確に表現したい場合があります。例えば「バリデーションエラー」や「認証エラー」などです。そのために、自分だけのオリジナルな例外クラスを作成することができます。これをユーザー定義例外と呼びます。

作り方はJavaと全く同じ。PHP標準の**Exceptionクラスを継承する**だけです。

📢 アナロジー: 標準のExceptionを投げるのは、何か問題が起きたときにただ「助けて!」と叫ぶようなものです。これに対し、ValidationExceptionのようなカスタム例外を作るのは、「メールアドレスの形式が不正です!」と、問題の具体的な内容を叫ぶようなものです。どちらが、その後の対処(catchブロックの処理)をしやすいかは一目瞭然ですね。

バリデーションエラーを表すValidationExceptionを作成し、実際に使ってみましょう。

1. カスタム例外クラスの定義

class ValidationException extends Exception
{
    // 必要であれば、独自のプロパティやメソッドを追加できる
}

2. カスタム例外を投げる (throw)

throwキーワードを使って、例外のインスタンスを投げます。これもJavaと全く同じです。

function registerUser(string $email)
{
    if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
        // バリデーションに失敗したら、自作の例外を投げる!
        throw new ValidationException("無効なメールアドレスです。");
    }
    
    // ... 成功時のユーザー登録処理 ...
    echo "ユーザー登録が完了しました。";
}

3. カスタム例外を捕捉する (catch)

try {
    registerUser("invalid-email");
    
} catch (ValidationException $e) {
    // ValidationExceptionだけを専門に捕捉する
    echo "入力エラー: " . $e->getMessage();
    
} catch (Exception $e) {
    // その他の予期せぬエラー
    echo "システムエラーが発生しました。";
}

このように、アプリケーションの文脈に合わせた独自の例外を定義し、throw / catchすることで、コードの意図が明確になり、より構造化されたエラー処理が可能になります。

PHPのモダンな例外処理は、Javaで培ったあなたの優れた設計スキルを活かすための強力なツールなのです。

15. セッション管理と認証

Webの基本プロトコルであるHTTPはステートレスです。つまり、リクエスト一つ一つが独立しており、サーバーは前のリクエストを覚えていません。しかし、私たちは「ログイン状態」や「ショッピングカートの中身」のように、ユーザーごとの状態を維持したいですよね。そのための魔法がセッション管理です。

JavaではHttpSessionオブジェクトを使ってセッションを扱いましたが、PHPでも全く同じ概念が、よりシンプルな形で実装されています。この章では、PHPのセッション管理をマスターし、Webアプリケーションの根幹である認証機能を実装する方法を学びましょう。


15.1 セッション開始・破棄(JavaのSession APIとの対応)

スーパーグローバルの章でも少し触れましたが、PHPのセッション管理は**session_start()関数と$_SESSION**スーパーグローバル配列が中心です。

session_start()は、セッションを開始、または既存のセッションを再開するための合図です。この関数が呼ばれると、PHPはブラウザから送られてきたクッキー(後述)を元に、サーバー上のセッションデータを特定し、$_SESSION配列に復元します。この関数は、セッションを扱う全てのスクリプトの一番最初に(HTMLなどの出力よりも前に)呼び出す必要があります。

アナロジー: ユーザーがWebサイトを訪れるのは、大きなロッカー室に来るようなものです。session_start()は、そのユーザー専用のロッカーの鍵を開ける行為です。一度鍵が開けば、$_SESSIONというロッカーの中に、自由に荷物(データ)を入れたり、取り出したりできます。🔑

機能Java (HttpSession)PHP
セッションの開始/取得request.getSession()session_start()
データの保存session.setAttribute("key", value)$_SESSION["key"] = value;
データの取得session.getAttribute("key")$_SESSION["key"]
セッションの破棄session.invalidate()session_destroy()

セッションを完全に破棄する(ログアウトする)には、Javaのsession.invalidate()に相当する処理として、いくつかの手順を踏むのが定石です。

// ログアウト処理の例
session_start();

// 1. セッション変数の中身をすべて空にする
$_SESSION = [];

// 2. セッションIDを保持しているクッキーを無効化する
if (isset($_COOKIE[session_name()])) {
    setcookie(session_name(), '', time() - 42000, '/');
}

// 3. サーバー上のセッションファイルを破棄する
session_destroy();

セキュリティの要: session_regenerate_id

ログイン成功時など、ユーザーの権限レベルが変わるタイミングでは、現在のセッションIDを破棄して新しいIDを割り当てるべきです。これは、ログイン前のセッションIDが何らかの方法で盗まれていた場合に、ログイン後のセッションを乗っ取られてしまう「セッションハイジャック」攻撃を防ぐためです。

このために、session_regenerate_id(true)という非常に重要な関数を使います。


15.2 クッキーの扱い

セッションの仕組みは、実はクッキー(Cookie)という技術に支えられています。

session_start()が呼ばれると、サーバーは「セッションID」というユニークな識別子を生成し、それをクッキーに含めてブラウザに送り返します。ブラウザは次回以降のリクエストで、そのクッキー(セッションID)を自動的にサーバーに送ります。サーバーはそのIDを見て、「ああ、さっきのユーザーだな」と認識し、対応するセッションデータを復元するのです。

PHPでは、セッションとは別に、独自のクッキーを扱うこともできます。そのための関数がsetcookie()です。

// 'username'という名前で、'Taro'という値を持つクッキーを設定
// 有効期限は1時間(3600秒)
$expires = time() + 3600;
setcookie('username', 'Taro', $expires, '/');

setcookie()関数もsession_start()と同様に、HTMLなどの出力よりも前に呼び出す必要があります。

ブラウザから送られてきたクッキーの値は、**$_COOKIE**スーパーグローバル配列から読み取ることができます。

if (isset($_COOKIE['username'])) {
    echo "クッキーに保存されたユーザー名: " . $_COOKIE['username'];
}


15.3 ログイン機能の実装例

それでは、これまでの知識を総動員して、シンプルなログイン機能を実装してみましょう。

1. ログインフォーム (login.html)

まずは、ユーザー名とパスワードを入力するフォームです。

<form action="login_process.php" method="post">
    ユーザー名: <input type="text" name="username"><br>
    パスワード: <input type="password" name="password"><br>
    <button type="submit">ログイン</button>
</form>

2. ログイン処理 (login_process.php)

フォームからのPOSTデータを受け取り、認証処理を行います。

<?php
session_start();

$username_from_form = $_POST['username'];
$password_from_form = $_POST['password'];

// 本来はデータベースと照合する
// ここでは簡易的にハードコード
$correct_username = 'user';
$correct_password = 'password123';

if ($username_from_form === $correct_username && $password_from_form === $correct_password) {
    // 認証成功
    
    // セキュリティ対策: 新しいセッションIDを発行
    session_regenerate_id(true);

    // ログイン情報をセッションに保存
    $_SESSION['user_id'] = 1;
    $_SESSION['username'] = $correct_username;

    // メンバーページへリダイレクト
    header('Location: member_page.php');
    exit;

} else {
    // 認証失敗
    echo "ユーザー名またはパスワードが違います。";
}

3. メンバー専用ページ (member_page.php)

ログインしているユーザーだけが閲覧できるページです。

<?php
session_start();

// ログイン状態をチェック
if (!isset($_SESSION['user_id'])) {
    // ログインしていなければ、ログインページにリダイレクト
    header('Location: login.html');
    exit;
}
?>

<!DOCTYPE html>
<html>
<head>
    <title>メンバーページ</title>
</head>
<body>
    <h1>ようこそ、<?php echo htmlspecialchars($_SESSION['username'], ENT_QUOTES, 'UTF-8'); ?> さん!</h1>
    <p>ここはメンバー専用のページです。</p>
    <a href="logout.php">ログアウト</a>
</body>
</html>

4. ログアウト処理 (logout.php)

セッションを破棄して、ログイン状態を解除します。

<?php
session_start();

// セッション変数を空にする
$_SESSION = [];

// セッションクッキーを削除
if (isset($_COOKIE[session_name()])) {
    setcookie(session_name(), '', time() - 42000, '/');
}

// セッションを破棄
session_destroy();

// ログインページにリダイレクト
header('Location: login.html');
exit;

この一連の流れが、Webアプリケーションにおける認証の基本形です。JavaのServletで実装したときよりも、コードが直感的でシンプルになっていることを感じていただけたのではないでしょうか。

16. セキュリティ

アプリケーションを構築する上で、セキュリティは機能の一つではなく、すべての土台となる絶対的な要件です。どんなに便利な機能も、脆弱性があれば一瞬にしてユーザーを危険に晒す凶器と化します。Javaの世界でもセキュリティは最重要課題でしたが、PHPでも考え方は全く同じです。

これまでの章で断片的に触れてきた対策を整理し、新たな脅威への防御策も学びます。ここで紹介する内容は、Webアプリケーション開発者としての「義務」です。必ず、一つ残らず実践してください! 🛡️


16.1 XSS / CSRF / SQLインジェクション対策

Webアプリケーションにおける「三大脆弱性」と呼ばれる攻撃手法と、その対策を再確認・整理します。

XSS(クロスサイトスクリプティング)対策
  • 脅威: 悪意のあるユーザーが入力フォームなどにJavaScriptを埋め込み、他のユーザーのブラウザ上で不正なスクリプトを実行させる攻撃。
  • 対策の鉄則: 出力のエスケープ。ユーザーからの入力値をWebページに出力する際は、必ずhtmlspecialchars()関数を通すこと。これはPHPにおけるセキュリティの基本中の基本です。
// ユーザーからの入力を受け取る
$comment = $_POST['comment'];

// 必ずエスケープしてから出力する!
echo htmlspecialchars($comment, ENT_QUOTES, 'UTF-8');

SQLインジェクション対策
  • 脅威: データベースに発行するSQL文に、不正なSQLの断片を注入し、データを不正に盗んだり、改ざん・削除したりする攻撃。
  • 対策の鉄則: プレースホルダを利用したプリペアドステートメントの使用。PDOを使ってデータベースを操作する際は、ユーザーからの入力値を直接SQL文に連結せず、必ずプレースホルダ(:nameなど)を使うこと
// :id がプレースホルダ
$sql = 'SELECT * FROM users WHERE id = :id';
$stmt = $pdo->prepare($sql);

// execute() の引数として安全に値を渡す
$stmt->execute([':id' => $_GET['id']]);

CSRF(クロスサイトリクエストフォージェリ)対策
  • 脅威: ログイン済みのユーザーを騙し、本人が意図しないリクエスト(例: 商品の購入、退会処理)を、別の悪意のあるサイトから強制的に送信させる攻撃。
  • アナロジー: あなたが銀行のサイトにログイン中だとします。その状態で、あなたが悪意のある罠サイトを訪れると、そのサイトはあなたの知らないうちに、あなたの銀行に対して「送金しろ」という偽のリクエストを送信します。銀行側は、あなたのブラウザから送られてきた正規のセッション情報が付いているため、本物のリクエストだと信じてしまい、送金処理を実行してしまいます。😱
  • 対策の鉄則: トークンによるリクエストの検証(シンクロナイザートークンパターン)

CSRFへの標準的な対策は、以下の手順で行います。

  1. フォームを表示する際に、ランダムで推測困難な文字列(トークン)を生成します。
  2. 生成したトークンを、サーバー側のセッションに保存します。
  3. 同じトークンを、HTMLフォームの中に隠しフィールド(<input type="hidden">)として埋め込みます。
  4. フォームが送信されたら、サーバー側で「送信されてきた隠しフィールドのトークン」と「セッションに保存しておいたトークン」が一致するかを比較します。
  5. 一致すれば正規のリクエスト、一致しなければ不正なリクエストとして処理を拒否します。

実装例

// --- フォーム表示側 (form.php) ---
session_start();
// トークンを生成
$csrf_token = bin2hex(random_bytes(32));
// トークンをセッションに保存
$_SESSION['csrf_token'] = $csrf_token;
?>
<form action="process.php" method="post">
    <input type="hidden" name="csrf_token" value="<?php echo htmlspecialchars($csrf_token, ENT_QUOTES, 'UTF-8'); ?>">
    <button type="submit">重要な処理を実行</button>
</form>

// --- 処理側 (process.php) ---
session_start();
// 送信されたトークンとセッションのトークンを比較
if (!isset($_POST['csrf_token']) || $_POST['csrf_token'] !== $_SESSION['csrf_token']) {
    // トークンが一致しない場合は不正なリクエストとして処理を中断
    die('不正なリクエストです。');
}
// トークンが一致したので、正規のリクエストとして処理を続行
echo "正常に処理が完了しました。";


16.2 パスワードのハッシュ化(password_hash / password_verify)

絶対に、パスワードを平文(そのままの文字列)でデータベースに保存してはいけません。

これはWebセキュリティにおける絶対の掟です。古いmd5()やsha1()といったハッシュ関数も、現在ではレインボーテーブル攻撃などにより簡単に破られてしまうため、安全ではありません。

PHPには、安全なパスワードハッシュを扱うための、モダンで非常に優れた標準関数が用意されています。パスワードを扱う際は、必ず以下の関数を使ってください

password_hash(): パスワードのハッシュ化

この関数は、パスワードを安全なハッシュ値に変換します。内部で自動的に以下の処理を行ってくれるため、開発者は複雑なことを考える必要がありません。

  • 現在推奨されている、強力なハッシュ化アルゴリズム(デフォルトはBCRYPT)を使用します。
  • ハッシュ化ごとに、安全な**ソルト(Salt)**を自動で生成し、ハッシュ値に含めます。
$password_from_form = 'password123';

// パスワードをハッシュ化
$hashed_password = password_hash($password_from_form, PASSWORD_DEFAULT);

// この$hashed_passwordをデータベースに保存する
// 例: $2y$10$m6O/N3gI9E3L8sU5a4h3e.o.wR7iW...
echo $hashed_password;

password_verify(): ハッシュの検証

ユーザーがログイン時に入力したパスワードが、データベースに保存されているハッシュ値と一致するかどうかを検証します。

$input_password = 'password123';
$hash_from_db = '$2y$10$m6O/N3gI9E3L8sU5a4h3e.o.wR7iW...'; // DBから取得したハッシュ

// パスワードとハッシュが一致するかを検証
if (password_verify($input_password, $hash_from_db)) {
    echo "パスワードが一致しました。ログイン成功!";
} else {
    echo "パスワードが一致しません。";
}

この関数は、ハッシュ値から自動的にソルトを抽出し、同じアルゴリズムで入力パスワードをハッシュ化して、タイミング攻撃にも強い安全な方法で比較してくれます。この2つの関数を使えば、パスワード管理に関するセキュリティは飛躍的に向上します。🔑


16.3 HTTPS通信

これまでの対策は、サーバー上のデータや、ブラウザでの挙動を守るものでした。しかし、ユーザーのブラウザとサーバー間の通信経路そのものが盗聴されていたら、元も子もありません。この通信を暗号化するのがHTTPSです。

HTTPS(HTTP over SSL/TLS)は、WebサーバーにSSL/TLS証明書を設定することで実現します。Let's Encryptなどのサービスを利用すれば、無料で証明書を取得することも可能です。

開発者がPHPコードで意識すべきことは、特にログインページや決済情報入力ページなど、全ての機密情報を扱うページが必ずHTTPSで提供されるようにすることです。

また、セッションIDなどの重要な情報をクッキーに保存する際は、HTTPS通信の時のみクッキーを送信するようにsecureフラグを、JavaScriptからアクセスできないようにhttponlyフラグを有効にすることが強く推奨されます。

// セッションクッキーの設定例(php.iniで設定するのが一般的)
session_set_cookie_params([
    'lifetime' => 3600,
    'path' => '/',
    'domain' => 'example.com',
    'secure' => true,   // HTTPSの場合のみクッキーを送信
    'httponly' => true, // JavaScriptからのアクセスを禁止
    'samesite' => 'Lax'
]);
session_start();

XSS対策、SQLインジェクション対策、CSRF対策、パスワードのハッシュ化、そしてHTTPS通信。この5つは、現代のWebアプリケーションを開発する上での「三種の神器」ならぬ「五種の神器」です。一つも欠かすことなく、あなたのアプリケーションに実装してください!🔒

17. JavaScriptとの連携

これまでの章で、PHPを使ってサーバーサイドの処理を構築する方法を学んできました。しかし、現代のWebアプリケーションは、ユーザーの操作に俊敏に反応する、リッチでインタラクティブなUI(ユーザーインターフェース)が不可欠です。その主役こそが、ユーザーのブラウザ上で動作するJavaScriptです。

PHPが「静的な骨格」を組み立て、JavaScriptがそれに「動的な命」を吹き込む。この二つの言語が連携することで、初めてモダンなWebアプリケーションは完成します。この章では、PHPとJavaScriptがどのように協力し合うのか、その具体的な方法を探っていきましょう。


17.1 PHPとJavaScriptの役割分担(サーバーサイド vs クライアントサイド)

まず、両者の役割分担を明確に理解することが重要です。

PHP (サーバーサイド) サーバーで動くJavaScript (クライアントサイド) ブラウザで動く
主な役割データベースとの連携、認証、ビジネスロジック、HTMLの初期生成ユーザー操作への応答、UIの動的な変更、サーバーとの非同期通信
実行場所Webサーバー上ユーザーのWebブラウザ上
タイミングページがリクエストされたときページが読み込まれた後、ユーザーが操作したとき

レストランでのアナロジー 🍽️

  • PHPは、厨房にいるシェフです 👨‍🍳。注文(リクエスト)を受け、レシピ(ビジネスロジック)に従い、データベースという食材庫から材料を取り出し、料理の本体(HTML)を調理します。
  • JavaScriptは、ダイニングホールにいるウェイターです。シェフが作った料理をお客さんのテーブルに運び、見た目を整えたり、お客さんからの「お水ください」といった追加の要望(ユーザー操作)に応えたりします。

シェフとウェイターが協力して初めて、素晴らしい食事体験が提供されるのです。


17.2 HTMLフォームでの連携(PHPで生成→JavaScriptで動的操作)

最も古典的で分かりやすい連携は、HTMLフォームを介したものです。

  1. PHPの役割: サーバーサイドで、フォームを含むHTMLページ全体を生成し、ブラウザに送ります。
  2. JavaScriptの役割: ブラウザ上でそのフォームを受け取り、ユーザーの入力を補助する動的な機能を追加します。

例:パスワード確認

ユーザー登録フォームで、パスワードを2回入力させる欄を考えます。

PHPは、二つの<input type="password">フィールドを持つHTMLを生成します。

JavaScriptは、ユーザーがキーボードをタイプするたびに二つのフィールドの値を比較し、「パスワードが一致しました」「一致しません」といったメッセージをリアルタイムで表示します。

このようなJavaScriptによる補助は、ユーザー体験を大幅に向上させます。


17.3 Ajax通信(XMLHttpRequest / fetch)による非同期データ取得

現代的なWebアプリケーションの核となる技術が**Ajax(Asynchronous JavaScript and XML)**です。

Ajaxとは、ページ全体をリロードすることなく、JavaScriptがバックグラウンドでサーバー(PHP)と通信し、必要なデータだけを取得・送信する技術です。

アナロジー 🚀

  • 従来の通信: レストランで「新しいコース料理」を注文するようなものです。ページ全体のリロードが発生し、一からすべてが運ばれてくるのを待つ必要があります。
  • Ajax通信: 食事の途中でウェイターに「すみません、お塩をもらえますか?」と頼むようなものです。ウェイターは厨房へ走り、塩(データ)だけを持ってきてくれます。食事(ページの表示)が中断されることはありません。

現在では、XMLの代わりにJSON(後述)がデータ形式の主流であり、通信にはJavaScriptの**fetch API**を使うのが一般的です。

// JavaScript側: ユーザーID 1 の情報をリクエストする
fetch('api/get_user.php?id=1')
    .then(response => response.json()) // レスポンスをJSONとして解釈
    .then(data => {
        // 受け取ったデータを使ってUIを更新する
        console.log(data.name); // PHPから返されたユーザー名
    });


17.4 JSON形式でのデータ送受信(json_encode / json_decode)

Ajax通信でPHPとJavaScriptがデータをやり取りする際、共通言語として最も広く使われているのが**JSON(JavaScript Object Notation)**です。JSONは軽量で人間にも読みやすく、PHPの連想配列とJavaScriptのオブジェクトとの間で非常に親和性が高いデータ形式です。

PHPには、JSONを扱うための強力な標準関数が用意されています。

json_encode(): PHP配列 → JSON文字列

PHPの配列(特に連想配列)を、JavaScriptが解釈できるJSON形式の文字列に変換します。

json_decode(): JSON文字列 → PHP配列/オブジェクト

JavaScriptから送られてきたJSON形式の文字列を、PHPで扱えるオブジェクトや連想配列に変換します。

PHP APIの実装例 (api/get_user.php)

<?php
// 本来はデータベースから取得する
$user_data = [
    'id' => 1,
    'name' => 'Taro Yamada',
    'email' => 'taro@example.com'
];

// Content-TypeヘッダーをJSONに設定
header('Content-Type: application/json');

// PHPの連想配列をJSON文字列に変換して出力
echo json_encode($user_data);

このPHPスクリプトにJavaScriptからfetchでアクセスすると、{"id":1,"name":"Taro Yamada","email":"taro@example.com"}というJSON文字列が返され、JavaScript側でデータとして利用できるのです。


17.5 クライアント+サーバーのバリデーション併用

フォーム入力の正当性をチェックするバリデーションは、クライアントサイド(JavaScript)とサーバーサイド(PHP)の両方で実装する必要があります。

  • クライアントサイド(JS)の役割: ユーザー体験(UX)の向上が目的です。「必須項目です」「メールアドレスの形式が違います」といったエラーを即座に表示し、ユーザーの入力ミスを素早く修正させます。
  • サーバーサイド(PHP)の役割: セキュリティとデータの整合性の担保が目的です。悪意のあるユーザーはJavaScriptを無効にしたり、直接リクエストを送信したりしてクライアントサイドのチェックを簡単に迂回できます。したがって、サーバーサイドは決してクライアントを信用せず、受け取ったすべてのデータを再度検証する最後の砦です。

どちらか片方だけでは不十分です。必ず両方で実装してください!


17.6 PHP変数をJavaScriptに渡す方法

ページが最初に読み込まれる際に、PHPが持っているデータをJavaScript側で初期値として使いたい場合があります。例えば、ログインしているユーザー名や、設定情報などです。

最も安全で確実な方法は、PHPのjson_encode()関数を使って、データをJSON形式でHTML内の<script>タグに埋め込むことです。

<?php
// PHP側で渡したいデータを用意
$current_user = [
    'id' => 123,
    'name' => 'Taro',
    'is_admin' => false
];
?>
<!DOCTYPE html>
<html>
<body>
    <script>
        // PHPのデータをjson_encodeで安全にJavaScriptの変数に渡す
        const currentUserData = <?php echo json_encode($current_user); ?>;

        // これでJavaScriptからPHPのデータを使える!
        console.log(currentUserData.name); // "Taro"
        if (currentUserData.is_admin) {
            console.log("管理者です");
        }
    </script>
</body>
</html>

json_encode()を使うことで、文字列の引用符などが自動でエスケープ処理され、JavaScriptの文法エラーやセキュリティ上の問題を未然に防ぐことができます。

18. 実践ミニプロジェクト

さあ、いよいよ最後の章です!これまでの旅で、あなたはPHPの文法、配列、オブジェクト指向、データベース連携、セキュリティ、そしてJavaScriptとの連携といった、強力な武器をいくつも手に入れました。この章では、それらの武器を実際に振るい、理論を「動く形」に変えるための実践的なミニプロジェクトに挑戦します。

知識を組み合わせて一つのアプリケーションを完成させることで、「ああ、あの機能はこのためにあったのか!」という深い理解が得られるはずです。Java Servletでの開発経験と比較しながら、PHPでのWebアプリケーション構築のフローをその手で確かめてみましょう!


18.1 ワンチャンス数当てゲーム(JavaのServlet版との比較) 🎮

まずは、フォームとセッション管理の基本を確かめるための、シンプルなゲームから始めましょう。

  • ルール: サーバーが1から10までの数字をランダムに一つ思い浮かべます。プレイヤーは一度だけ、その数字を推測できます。正解か不正解か、結果が表示されます。
  • 使う技術: HTMLフォーム、$_POST$_SESSION、基本的な制御構文。
PHPでの実装イメージ (game.php)
<?php
session_start();

// まだ答えがセッションになければ、新しい答えを生成
if (!isset($_SESSION['answer'])) {
    $_SESSION['answer'] = rand(1, 10);
}

$message = '';
// フォームが送信された(推測が送られてきた)場合の処理
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $guess = (int)$_POST['guess'];
    $answer = $_SESSION['answer'];

    if ($guess === $answer) {
        $message = "正解!答えは " . $answer . " でした!";
    } else {
        $message = "残念、不正解!正解は " . $answer . " でした。";
    }

    // ゲームが終わったので、セッションの答えを破棄
    unset($_SESSION['answer']);
}
?>

<!DOCTYPE html>
<html>
<head><title>数当てゲーム</title></head>
<body>
    <h1>数当てゲーム!</h1>
    <?php if ($message): ?>
        <p><?php echo htmlspecialchars($message, ENT_QUOTES, 'UTF-8'); ?></p>
        <a href="game.php">もう一度プレイ</a>
    <?php else: ?>
        <p>1から10までの数字を当ててください。</p>
        <form action="game.php" method="post">
            <input type="number" name="guess" min="1" max="10" required>
            <button type="submit">推測!</button>
        </form>
    <?php endif; ?>
</body>
</html>

Java Servlet版との比較

このシンプルなアプリケーションでも、アーキテクチャの思想の違いが明確に表れます。

観点Java ServletシンプルなPHPスクリプト
エントリーポイントdoGet/doPostメソッドスクリプトファイル自体(上から下へ)
状態管理HttpSessionオブジェクト$_SESSIONスーパーグローバル配列
リクエストデータrequest.getParameter()メソッド$_POST/$_GETスーパーグローバル配列
ルーティング設定ファイルやアノテーションでURLとクラスを紐付けファイルシステム(URLが直接ファイルに対応)
定型コードクラス定義など、比較的多い非常に少ない(1ファイルで完結可能)

Javaが、リクエストを専門の受付(サーブレットコンテナ)が受け、規律正しく各部署(サーブレットクラス)に振り分ける大企業だとすれば、このようなPHPスクリプトは、依頼(リクエスト)が来たらその場で製品(レスポンス)を作って渡す、フットワークの軽い町工場のようです。PHPのこの手軽さは、小さなツールやプロトタイプを素早く作る際に絶大な力を発揮します。

Java Servletでは、ロジックを記述する「サーブレット」と、表示を担当する「JSP (JavaServer Pages)」の2つにファイルを分けるのが一般的です。PHPの1ファイルで完結する手軽さと比較して、役割が明確に分離されているのが特徴です。


1. ゲームロジック (GameServlet.java)

ユーザーからのリクエストを受け取り、ゲームの進行管理やセッション操作を行うコントローラー部分です。

import java.io.IOException;
import java.util.Random;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;

// "/game"というURLへのリクエストをこのサーブレットが処理する
@WebServlet("/game")
public class GameServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;

    /**
     * GETリクエストの処理(最初のページ表示や「もう一度プレイ」)
     */
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) 
            throws ServletException, IOException {
        
        HttpSession session = request.getSession();

        // セッションに答えがなければ、新しい答えを生成する
        if (session.getAttribute("answer") == null) {
            session.setAttribute("answer", new Random().nextInt(10) + 1);
        }

        // 表示用のJSPファイルに処理を転送(フォワード)する
        request.getRequestDispatcher("/WEB-INF/game.jsp").forward(request, response);
    }

    /**
     * POSTリクエストの処理(フォームから推測が送信されたとき)
     */
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) 
            throws ServletException, IOException {
        
        HttpSession session = request.getSession();
        String message;

        // セッションから答えを取得
        Integer answer = (Integer) session.getAttribute("answer");

        if (answer == null) {
            // ページを再読み込みされるなどしてセッションが切れた場合
            response.sendRedirect("game"); // GETリクエストとしてやり直し
            return;
        }

        try {
            // フォームから送信された値を取得
            int guess = Integer.parseInt(request.getParameter("guess"));

            if (guess == answer) {
                message = "正解!答えは " + answer + " でした!";
            } else {
                message = "残念、不正解!正解は " + answer + " でした。";
            }
            
            // ゲームが終わったので、セッションの答えを破棄
            session.removeAttribute("answer");

        } catch (NumberFormatException e) {
            message = "1から10の数字を入力してください。";
        }

        // 結果メッセージをJSPに渡すためにリクエスト属性にセット
        request.setAttribute("message", message);
        
        // 表示用のJSPファイルに処理を転送する
        request.getRequestDispatcher("/WEB-INF/game.jsp").forward(request, response);
    }
}


2. 表示(View) (game.jsp)

サーブレットから渡された情報(結果メッセージ)を元に、HTMLを生成するビュー部分です。JSTL(JSP Standard Tag Library)という拡張ライブラリを使い、HTML内にJavaコードを書かずに条件分岐を実現しています。

webapp/WEB-INF/ディレクトリ内に配置します。

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>数当てゲーム</title>
</head>
<body>
    <h1>数当てゲーム!</h1>

    <%-- JSTLを使って、message属性が存在するかどうかで表示を切り替える --%>
    <c:choose>
        <%-- messageが存在する場合(ゲーム結果表示) --%>
        <c:when test="${not empty message}">
            <p><c:out value="${message}" /></p>
            <a href="game">もう一度プレイ</a>
        </c:when>
        
        <%-- messageが存在しない場合(ゲーム開始時) --%>
        <c:otherwise>
            <p>1から10までの数字を当ててください。</p>
            <form action="game" method="post">
                <input type="number" name="guess" min="1" max="10" required>
                <button type="submit">推測!</button>
            </form>
        </c:otherwise>
    </c:choose>
    
</body>
</html>

PHP版との主な違い

  • 関心の分離: PHP版が1ファイルにロジックとHTMLを混在させているのに対し、Java版はロジック(Servlet)と表示(JSP)を明確に分離しています。
  • 状態管理: PHPの$_SESSION連想配列に対し、JavaではHttpSessionオブジェクトのsetAttribute/getAttributeメソッドを使います。
  • リクエスト処理: PHPが$_SERVER['REQUEST_METHOD']で判定するのに対し、JavaではdoGet()doPost()という専用メソッドで処理を分けます。
  • テンプレートエンジン: PHPがHTML内に直接<?php ... ?>を埋め込むのに対し、JavaではJSTLのような専用のタグライブラリを使って、より宣言的にビューを記述するのが一般的です。

18.2 TODOアプリ ✅

次に、データベース連携の基本である**CRUD(Create, Read, Update, Delete)**を網羅した、定番のTODOアプリを構築します。

  • 機能: タスクの一覧表示、新規タスクの追加、タスクの完了/未完了の切り替え、タスクの削除。
  • 使う技術: PDOによるデータベース操作、フォーム処理、CRUDの概念。

実装のポイント

  1. データベース設計: tasksテーブル(id, title, is_completed, created_atなど)を準備します。
  2. 接続: PDOでデータベースに接続します。
  3. 処理の分岐: $_POST['action']のような隠しフィールドの値を見て、「追加」「更新」「削除」のどの処理かを判断します。
  4. 表示 (Read): SELECT * FROM tasksで全タスクを取得し、foreachで一覧表示します。
  5. 追加 (Create): INSERT INTO tasks ...で新しいタスクをデータベースに保存します。
  6. 更新 (Update): タスクの完了チェックボックスが押されたら、UPDATE tasks SET is_completed = ...で状態を更新します。
  7. 削除 (Delete): 削除ボタンが押されたら、DELETE FROM tasks WHERE id = ...で該当タスクを削除します。

全ての処理で、SQLインジェクション対策としてプレースホルダを使うことを忘れないでください!

はい、承知いたしました。指定された要件に基づき、データベースと連携する基本的なTODOアプリを単一のPHPファイルで作成します。

事前準備:データベースとテーブルの作成

まず、データベースにtasksテーブルを作成してください。以下はMySQL用のSQLクエリ例です。

CREATE DATABASE IF NOT EXISTS php_todo_app;
USE php_todo_app;

CREATE TABLE IF NOT EXISTS `tasks` (
  `id` INT AUTO_INCREMENT PRIMARY KEY,
  `title` VARCHAR(255) NOT NULL,
  `is_completed` BOOLEAN NOT NULL DEFAULT FALSE,
  `created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- ダミーデータの挿入(任意)
INSERT INTO `tasks` (title) VALUES ('PHPの勉強をする'), ('牛乳を買う');


TODOアプリのコード (todo.php)

以下のコードをtodo.phpという名前のファイルとして保存してください。データベース接続情報$dsn, $user, $password)は、ご自身の環境に合わせて書き換える必要があります。

<?php

// --------------------------------
// 1. データベース接続
// --------------------------------
// DSN (Data Source Name)
$dsn = 'mysql:host=localhost;dbname=php_todo_app;charset=utf8mb4';
// ユーザー名
$user = 'root'; // あなたのユーザー名に変更してください
// パスワード
$password = ''; // あなたのパスワードに変更してください

try {
    // PDOインスタンスを生成
    $pdo = new PDO($dsn, $user, $password, [
        // エラー発生時に例外をスローする設定
        PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
        // フェッチモードのデフォルトを連想配列に設定
        PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
    ]);
} catch (PDOException $e) {
    // 接続エラー時の処理
    header('Content-Type: text/plain; charset=UTF-8', true, 500);
    exit('データベースへの接続に失敗しました: ' . $e->getMessage());
}

// --------------------------------
// 2. POSTリクエストの処理(追加・更新・削除)
// --------------------------------
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    if (isset($_POST['action'])) {
        $action = $_POST['action'];

        // --- 追加 (Create) ---
        if ($action === 'add' && !empty($_POST['title'])) {
            $title = $_POST['title'];
            $stmt = $pdo->prepare("INSERT INTO tasks (title) VALUES (:title)");
            $stmt->execute([':title' => $title]);
        }
        // --- 更新 (Update) ---
        elseif ($action === 'toggle' && isset($_POST['id'])) {
            $id = $_POST['id'];
            // is_completedの値を反転させる (0 -> 1, 1 -> 0)
            $stmt = $pdo->prepare("UPDATE tasks SET is_completed = NOT is_completed WHERE id = :id");
            $stmt->execute([':id' => $id]);
        }
        // --- 削除 (Delete) ---
        elseif ($action === 'delete' && isset($_POST['id'])) {
            $id = $_POST['id'];
            $stmt = $pdo->prepare("DELETE FROM tasks WHERE id = :id");
            $stmt->execute([':id' => $id]);
        }
    }
    // PRG (Post/Redirect/Get) パターン: 二重送信を防止
    header('Location: ' . $_SERVER['PHP_SELF']);
    exit;
}

// --------------------------------
// 3. データ取得 (Read)
// --------------------------------
$stmt = $pdo->query("SELECT * FROM tasks ORDER BY created_at DESC");
$tasks = $stmt->fetchAll();

?>
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>TODOアプリ</title>
    <style>
        body { font-family: sans-serif; container: inline-size; }
        .container { max-width: 600px; margin: 20px auto; padding: 20px; border: 1px solid #ddd; border-radius: 8px; }
        h1 { text-align: center; }
        .task-form { display: flex; gap: 10px; margin-bottom: 20px; }
        .task-form input[type="text"] { flex-grow: 1; padding: 8px; border: 1px solid #ccc; border-radius: 4px; }
        .task-form button { padding: 8px 15px; border: none; background-color: #007bff; color: white; border-radius: 4px; cursor: pointer; }
        .task-list { list-style-type: none; padding: 0; }
        .task-list li { display: flex; align-items: center; gap: 10px; padding: 10px; border-bottom: 1px solid #eee; }
        .task-list li:last-child { border-bottom: none; }
        .task-title { flex-grow: 1; }
        .completed { text-decoration: line-through; color: #888; }
        .delete-btn { padding: 5px 10px; border: none; background-color: #dc3545; color: white; border-radius: 4px; cursor: pointer; }
    </style>
</head>
<body>

<div class="container">
    <h1>TODOアプリ ✅</h1>

    <form action="" method="post" class="task-form">
        <input type="hidden" name="action" value="add">
        <input type="text" name="title" placeholder="新しいタスクを入力" required autocomplete="off">
        <button type="submit">追加</button>
    </form>

    <ul class="task-list">
        <?php foreach ($tasks as $task): ?>
            <li>
                <form action="" method="post" style="display: contents;">
                    <input type="hidden" name="action" value="toggle">
                    <input type="hidden" name="id" value="<?= $task['id'] ?>">
                    <input type="checkbox" <?= $task['is_completed'] ? 'checked' : '' ?> onchange="this.form.submit()">
                </form>

                <span class="task-title <?= $task['is_completed'] ? 'completed' : '' ?>">
                    <?= htmlspecialchars($task['title'], ENT_QUOTES, 'UTF-8') ?>
                </span>
                
                <form action="" method="post" style="display: contents;">
                    <input type="hidden" name="action" value="delete">
                    <input type="hidden" name="id" value="<?= $task['id'] ?>">
                    <button type="submit" class="delete-btn">削除</button>
                </form>
            </li>
        <?php endforeach; ?>
    </ul>
</div>

</body>
</html>

使い方

  1. 上記のSQLを実行してデータベースとテーブルを準備します。
  2. todo.phpファイルのデータベース接続情報を、あなたの環境に合わせて編集します。
  3. todo.phpをWebサーバーの公開ディレクトリに配置し、ブラウザでアクセスします。

コードのポイント

  • 単一ファイル構成: ロジックとビューを1つのファイルにまとめることで、PHPの基本的な動作を理解しやすくしています。
  • PDOとプリペアドステートメント: prepareexecuteを使い、SQLインジェクション対策を施しています。
  • PRGパターン: POSTリクエスト処理後に自身にリダイレクトすることで、ブラウザの「更新」ボタンによるフォームの二重送信を防いでいます。
  • onchange="this.form.submit()": チェックボックスの状態が変わると、即座にフォームが送信され、タスクの完了状態が更新されます。これにより、JavaScriptでAjax通信を実装しなくても、動的な使い心地を実現しています。
  • htmlspecialchars: ユーザーが入力したタスクタイトルを表示する際に必ず使用し、XSS(クロスサイトスクリプティング)を防いでいます。

18.3 会員管理システム 👥

認証機能とCRUDを組み合わせた、より実践的なアプリケーションです。

  • 機能: ユーザーの新規登録、ログイン・ログアウト機能、登録ユーザーの一覧表示。
  • 使う技術: これまでの総まとめ!セッション管理、password_hash()/password_verify()、PDO、セキュリティ対策全般。

実装のポイント

  • 新規登録: password_hash()でパスワードを安全にハッシュ化してからINSERTします。
  • ログイン: 入力されたパスワードをpassword_verify()で検証し、成功したらsession_regenerate_id(true)を実行してセッションにユーザー情報を保存します。
  • 一覧表示ページ: ページの冒頭でセッションをチェックし、ログインしていなければログインページにリダイレクトさせる「認証ガード」を実装します。

このシステムを構築できれば、一般的なWebアプリケーションの基本的な構造をマスターしたと言えるでしょう。


18.4 画像アップロードギャラリー 🖼️

最後に、ファイル操作とデータベース連携を組み合わせたアプリケーションです。

  • 機能: 画像のアップロードフォーム、アップロードされた画像の一覧表示ギャラリー。
  • 使う技術: $_FILESmove_uploaded_file()、ファイルバリデーション、PDO。

実装のポイント

  1. データベース設計: imagesテーブル(id, file_name, caption, uploaded_atなど)を準備します。ファイルそのものではなく、ファイルの情報を保存するのがポイントです。
  2. アップロード処理:
    • $_FILESで受け取ったファイルのエラー、サイズ、MIMEタイプを検証します。
    • ファイル名の重複を避けるため、uniqid()関数などを使ってユニークなファイル名を生成します。
    • move_uploaded_file()で、一時ファイルをuploads/のような公開ディレクトリに移動させます。
    • データベースのimagesテーブルに、生成したファイル名やキャプションなどの**メタデータをINSERT**します。
  3. ギャラリー表示:
    • imagesテーブルから全レコードをSELECTします。
    • foreachでループさせ、各レコードのfile_nameを元に<img>タグのsrc属性を組み立てて表示します。

旅の終わりに 🎉

「Java経験者のためのPHP入門」、これにて完結です!長い旅路、お疲れ様でした。

Javaという堅牢な言語の知識を土台に、PHPの動的な性質やWebに特化した思想を学ぶことで、あなたの技術的な視野は大きく広がったはずです。文法の違いに戸惑ったことも、PHPの手軽さに感動したこともあったことでしょう。

あなたは今、PHPという強力なツールを使って、頭の中のアイデアを形にするための十分な知識と技術を身につけました。しかし、ご存知の通り、学びの旅に終わりはありません。

次なるステップとして、LaravelSymfonyといったモダンなPHPフレームワークの世界を覗いてみてください。Javaの世界におけるSpringのように、フレームワークは、より大規模で、より堅牢で、より保守しやすいアプリケーションを構築するための素晴らしい指針と道具を提供してくれます。

千のWebアプリの道も、一行のコードから。

あなたのこれからの開発ライフが、創造性と喜びに満ちたものであることを心から願っています。

セイ・コンサルティング・グループの新人エンジニア研修のメニューへのリンク

投稿者プロフィール

山崎講師
山崎講師代表取締役
セイ・コンサルティング・グループ株式会社代表取締役。
岐阜県出身。
2000年創業、2004年会社設立。
IT企業向け人材育成研修歴業界歴20年以上。
すべての無駄を省いた費用対効果の高い「筋肉質」な研修を提供します!
この記事に間違い等ありましたらぜひお知らせください。