面接でいちばんよく出るPHPの質問を、PHP 8.3で正しい短い回答と、コードが要点を示す場面ではコード付きでまとめました。Runを押す前に各ブロックの出力を予想してください。面接官が試すのはまさにそこです。
初級のPHP面接質問
PHPとは何で、どう動くのですか?
PHPはサーバーサイドのスクリプト言語です。Webサーバー(NginxやApache)がリクエストをPHP(たいていPHP-FPM)に渡し、PHPがスクリプトを先頭から実行し、出力したものがレスポンス(たいていHTML)になります。各リクエストはまっさらな状態で始まり、セッション、データベース、キャッシュに保存しない限り、変数はリクエストをまたいで残りません。PHPはコマンドラインでもスクリプトを実行します(php script.php)。
==と===の違いは何ですか?
==は型の自動変換のあとで比較し、===は変換せずに値と型を比較します。上のウォームアップは==の動きを示しています。数値の文字列は数値として比較されるので、'10' == '1e1'はtrueです。PHP 8以降、数値と数値でない文字列は文字列として比較されるので、0 == 'a'はfalseです(PHP 7ではtrueでした)。セルをタップして2つの演算子を比べてみてください。
| $a == $b | true | false | 0 | 1 | "0" | "1" | "" | null | [] | "a" | "10" | "1e1" |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| true | ||||||||||||
| false | ||||||||||||
| 0 | ||||||||||||
| 1 | ||||||||||||
| "0" | ||||||||||||
| "1" | ||||||||||||
| "" | ||||||||||||
| null | ||||||||||||
| [] | ||||||||||||
| "a" | ||||||||||||
| "10" | ||||||||||||
| "1e1" |
デフォルトでは===を使いましょう。in_array()とarray_search()は、3つ目の引数にtrueを渡さない限り==を使います。
echoとprintの違いは何ですか?
どちらも文字列を出力します。echoは複数の引数を受け取り(echo $a, $b;)何も返しません。printは1つだけ受け取って1を返すので、式の中で使えます。どちらも関数ではないので、括弧は任意です。実際にはみんなechoを使います。
シングルクォートとダブルクォートの違いは?
ダブルクォートの文字列は変数とエスケープシーケンスを展開します:"Hi $name\n"。シングルクォートの文字列は\'と\\を除いて書いたとおりです:'Hi $name\n'はそのとおりの文字を出力します。速度の差はごくわずかなので、選ぶ基準は読みやすさです。展開するならダブルクォート、しないならシングルクォートです。
PHPのデータ型には何がありますか?
4つのスカラー型(int、float、string、bool)、2つの複合型(arrayとobject)、2つの特別な型(nullとresource)です。callable、iterable、mixed、voidは型宣言としてだけ存在し、値の型になることはありません。gettype()とget_debug_type()は実行時に値の型を教え、var_dump()は値と一緒に型を出力します。
isset、empty、is_nullの違いは何ですか?
isset($x)は、変数が存在してnullでなければtrueです。empty($x)は、変数が存在しないか、falseと評価される値(0、'0'、''、[]、false、null)ならtrueです。is_null($x)はnullに対してtrueですが、未定義の変数には警告を出します。issetとemptyは決して警告を出さないので、配列のキーに使われます。
| $x | isset | empty | is_null |
|---|---|---|---|
| null | |||
| false | |||
| 0 | |||
| "0" | |||
| "" | |||
| " " | |||
| [] | |||
| "a" | |||
| 0.0 |
よく間違えられるのは"0"の行です。文字列のゼロは空ですが、" "(空白1つ)は空ではありません。完全な表はissetのページにあります。
includeとrequireの違いは何ですか?
どちらもほかのファイルを挿入して実行します。ファイルがないと、includeは警告を出してスクリプトを続け、requireはError(「Failed opening required」)を投げ、何かがキャッチしない限りスクリプトが止まります。_onceの形(include_once、require_once)はすでに読み込んだファイルを飛ばすので、「cannot redeclare function」のエラーを防げます。スクリプトがそれなしでは動かないファイル、つまりほとんどすべてにはrequire(またはrequire_once)を使います。Composerのオートローダーのおかげで、手動のincludeの大部分は不要になっています。
GETとPOSTの違いは何ですか?
GETはURLのクエリ文字列($_GET)でデータを送るので、見え、ブックマークでき、長さに制限があります。検索やページ送りのような読み取りに使います。POSTはリクエストの本文($_POST)でデータを送ります。ログイン、保存、決済のように状態を変えるものすべてに使います。リンクは先読みされたりクロールされたりするので、GETリクエストで決してデータを変えてはいけません。
null合体演算子とは何ですか?
$a ?? $bは、$aが存在してnullでなければ$aを、そうでなければ$bを、未定義の変数やキーに対する警告なしで返します:$page = $_GET['page'] ?? 1;。??=は左辺がnullか存在しないときだけ代入します。falseと評価される値なら何でも代わりの値を使う?:とは違い、0 ?: 5は5ですが0 ?? 5は0です。
中級のPHP面接質問
foreachで参照を使ってループすると何が起きますか?
foreach ($arr as &$v)で要素をその場で変更できますが、ループのあとも$vは最後の要素への参照のままです。2つ目のループで同じ名前を使い回すと、その要素を上書きしてしまいます。
最後の要素は30ではなく20です。2つ目のループの間、$v(今も$arr[2])には10、次に20、最後に自分自身が代入されるからです。unset($v)の前の//を外して、もう一度実行してみてください。参照についてはforeachのページで詳しく扱っています。
抽象クラスとインターフェースの違いは何ですか?また、トレイトとは何ですか?
インターフェースは約束で、実装のないメソッドのシグネチャ(と定数)を持ち、クラスはいくつでも実装できます。抽象クラスは、プロパティ、コンストラクタ、完成したメソッドに加え、子が書かなければならない抽象メソッドを持て、クラスが継承できるのは1つだけです。トレイトはuseでクラスにコピーされるメソッドの集まりで、親を共有しないクラスの間でコードを再利用するためのものです。
面接官に好まれる短い答えはこうです。何ができるかを示すにはインターフェースを、それをどうやるかを共有するには抽象クラスを、関係のないクラスが同じいくつかのメソッドを必要とするときはトレイトを使います。
selfとstaticの違いは何ですか?
selfはコードが書かれているクラスを指します。staticは実行時に実際に呼ばれたクラスを指し、これを遅延静的束縛と呼びます。「この種類のオブジェクト」を作ったり説明したりする親のメソッドで重要になります。
User::createStatic()はUserを作ります。User::createSelf()はModelを作り、staticの戻り値の型に違反するので、PHPはTypeErrorを投げます。ファクトリーメソッドがnew static()を使うのはこのためです。
マジックメソッドとは何ですか?
PHPが代わりに呼んでくれるメソッドで、どれもアンダースコア2つで始まります。よく使われるのは、オブジェクトが作られるときの__construct()、破棄されるときの__destruct()、文字列として使われるときの__toString()、アクセスできないプロパティに対する__get()と__set()、存在しないメソッドに対する__call()と__callStatic()、オブジェクトが関数のように呼ばれるときの__invoke()、cloneのあとの__clone()です。
array_merge()と+演算子の違いは何ですか?
array_merge()は追加します。数値キーは振り直され、あとの文字列キーが前のものを上書きします。+は和集合で、左の配列を保ち、足りないキーだけを追加するので、キーが重複すれば最初の値が優先されます。
既定値には+が正しい道具です:$options + ['timeout' => 30]は呼び出し側が渡したものを保ち、残りを埋めます。
declare(strict_types=1)は何をしますか?
これがないと、PHPは可能ならスカラーの引数を宣言された引数の型に変換します。intの引数に"5"を渡すと5になります。ファイルの最初の文としてdeclare(strict_types=1);を書くと、そのファイルからの呼び出しは正確な型を渡さなければならず、そうでなければTypeErrorが投げられます(floatの引数にintを渡すのだけは許される拡大です)。
declareの行を消してもう一度実行すると、2つ目の呼び出しは例外を投げずに10を返します。
セッションとCookieの違いは何ですか?
Cookieはブラウザに保存され、リクエストのたびに送られるので、訪問者が読んだり変えたりできます。セッションはデータをサーバーに保存し、Cookie(PHPSESSID)にはランダムなIDだけを保持します。ログイン中のユーザーIDのように信頼できなければならないものは$_SESSIONに置き、ログイン後にはsession_regenerate_id(true)を呼びます。コードはセッションとCookieのページにあります。
SQLインジェクションはどう防ぎますか?
入力を連結してSQLを組み立ててはいけません。値がクエリとは別に送られるよう、プリペアドステートメントを使います。
<?php
$stmt = $pdo->prepare('SELECT * FROM users WHERE email = ?');
$stmt->execute([$email]);
$user = $stmt->fetch();
プレースホルダーが使えるのは値だけです。テーブル名、カラム名、ASC/DESCは、SQLに入れる前に許可リストと照合しなければなりません。PDOを参照してください。
XSSはどう防ぎますか?
入力ではなく出力をエスケープします。HTMLに出力するすべての値をhtmlspecialchars($value, ENT_QUOTES, 'UTF-8')で包むか、自動でそれをするテンプレートエンジン(Twig、Blade)を使います。<script>の中では、JSON_HEX_TAG付きのjson_encode()を使います。2つ目の防御層としてContent-Security-Policyヘッダーを加えます。
パスワードはどう保存すべきですか?
password_hash($password, PASSWORD_DEFAULT)で保存し、password_verify($password, $hash)で確認します。これらはハッシュの中に保存されるランダムなソルトと意図的なコストを持つbcrypt(またはArgon2id)を使います。md5()とsha1()は高速でソルトがないので、パスワードには不適切です。古いハッシュを更新するには、ログイン時にpassword_needs_rehash()を使います。
上級のPHP面接質問
ジェネレータとは何で、どんなときに使いますか?
yieldを含む関数は、求められたときにだけ値を1つずつ作るGeneratorを返します。メモリに配列を作らずに、大きな、または終わりのない並びをループできます。
典型的な用途は、大きなファイルを1行ずつ読むこと、データベースの行のストリーミング、遅延パイプラインです。ジェネレータはキー(yield $key => $value)、委譲(yield from)、send()による値の受け取りにも対応しています。
クロージャとは何で、useは何をしますか?
無名関数(function () { ... })はClosureオブジェクトです。useに並べない限り外側の変数は見えず、useはクロージャが作られた瞬間の値をコピーします。use (&$x)は代わりに参照でキャプチャします。アロー関数(fn ($x) => $x * $factor)は外側の変数を自動的に値でキャプチャし、1つの式だけを持ちます。
abstract、final、readonlyの違いは何ですか?
abstractはインスタンス化できないクラス、または子が実装しなければならないメソッドを示します。finalはメソッドのオーバーライドやクラスの継承を禁止します。readonly(PHP 8.1)はプロパティを、クラスの中から(たいていコンストラクタで)1回だけ書き込めるようにします。PHP 8.2では、すべてのプロパティがreadonlyになるreadonlyクラスが追加されました。
Composerとは何で、PSR-4とは何ですか?
ComposerはPHPの依存関係マネージャーです。composer.jsonにパッケージを並べ、composer.lockが正確なバージョンを固定し、vendor/autoload.phpがすべてを読み込みます。PSR-4は名前空間の接頭辞をフォルダーに対応させるオートロードの標準で、App\Mail\Mailerはsrc/Mail/Mailer.phpに置かれ、最初に使われたときに読み込まれます。install(ロックファイルから)とupdate(新しいバージョン、新しいロックファイル)の違いを説明しましょう。
PHP 8で何が変わりましたか?
主な機能は次のとおりです。8.0では名前付き引数、match、nullsafe演算子?->、union型、コンストラクタでのプロパティの昇格、アトリビュート、str_contains()、よりまともになった文字列と数値の比較。8.1ではenum、readonlyプロパティ、ファーストクラスcallable構文(strlen(...))、ファイバー。8.2ではreadonlyクラスと単独のnull/true/false型。8.3では型付きクラス定数、#[\Override]アトリビュート、json_validate()。8.4ではプロパティフック、非対称な可視性(public private(set))、array_find()、括弧で包まないnew Foo()->method()です。また多くの関数が、警告付きでfalseを返す代わりにTypeErrorやValueErrorを投げるようになりました。
PHPはメモリをどう管理していますか?
すべての値は参照カウントを持ち、それがゼロになるとメモリはすぐに解放されます。互いに参照し合うオブジェクトの循環は決してゼロにならないので、循環の候補を入れるバッファがいっぱいになる(デフォルトで10,000件)たびに循環コレクターが動いてそれらを解放します(gc_collect_cycles()で必要なときに実行できます)。配列と文字列はコピーオンライトで、$b = $aと代入しても、どちらかが変更されるまでデータを共有します。各リクエストはきれいに始まって終わるので、メモリリークが問題になるのはおもに常駐するワーカーやキューのコンシューマーです。
PHPの配列はどう実装されていますか?
PHPの配列は順序付きのハッシュマップです。挿入順を保ち、intかstringのキーを値に対応させるので、同じ型がリスト、辞書、スタックの役割を果たせます。キーの検索(isset($arr[$key]))は平均で一定時間ですが、in_array()はすべての値を調べます。整数に見える文字列のキーは変換されるので、$a['1']と$a[1]は同じ要素です。
よく出るコーディング課題
その場でよく出される短い課題です。各関数はよくある回答の1つです。実行して自分の入力(空のものも)を加え、時間計算量を説明できるようにしておきましょう。面接官がたいてい確認しているのはそこです。
よくある質問
PHPエンジニアの面接にはどう準備すればいいですか?
基本について、調べずに説明してコードを書けるようにしておきましょう。比較と型、配列とその関数、OOP(インターフェース、抽象クラス、トレイト、static)、例外、セキュリティ(プリペアドステートメント、htmlspecialchars、password_hash)です。そのうえで、1つのフレームワーク(LaravelかSymfony)とComposerを、自分が作ったプロジェクトを説明できるくらいに知っておきます。
面接ではどのバージョンのPHPを知っておくべきですか?
PHP 8です。面接官は、8.0から8.3で追加された機能を知っていることを期待します。名前付き引数、match、nullsafe演算子?->、union型、コンストラクタでのプロパティの昇格、enum、readonlyプロパティ、ファーストクラスcallable構文です。PHP 7の挙動に触れるのは、0 == "a"のように変わった部分だけにしましょう。
新卒・未経験者にはどんなPHPの質問が出ますか?
ほとんどは言語の基本です。==と===、echoとprint、シングルクォートとダブルクォート、issetとempty、includeとrequire、GETとPOST、セッションとCookie、そして値の反転、絞り込み、数え上げのような簡単な配列の課題です。少なくとも1つは短いコーディングの課題があると考えておきましょう。
経験者にはどんなPHPの質問が出ますか?
設計と内部の挙動です。インターフェースと抽象クラス、トレイト、遅延静的束縛、参照、ジェネレータとメモリ、例外とエラー処理、SQLインジェクションとトランザクション、キャッシュ、Composerのオートロード、そして機能をどう構成しテストするかです。回答には定義ではなく、トレードオフが求められます。