4.

PHP 可変変数 (Variable Variables) 完全ガイド($$var の落とし穴と代替策)

編集
この記事の要点
  • 可変変数 (Variable Variables): $$name で「$name の値」を変数名として展開する PHP の機能
  • 例: $name = "foo"; $$name = "bar";$foo という変数が動的に生成され値は "bar"
  • 配列キー動的生成: ${"key_$i"}${"key".$i} でローカル変数を動的命名できる
  • 非推奨: コード可読性 / 静的解析 / IDE 補完 / セキュリティすべてを損なう。現代では連想配列 / Reflection を使う
  • 関数の動的呼び出しは可変変数ではなく可変関数 $func()$method = "run"; $obj->$method();

可変変数とは

PHP の可変変数 (Variable Variables)$$var という二重ドル記号で、「変数 $var の値を新しい変数名として使う」機能です。1990 年代から PHP に存在する古い文法。

<?php
$name = 'foo';
$$name = 'bar';        // 実質 $foo = 'bar';

echo $foo;             // bar
echo $$name;           // bar
echo ${$name};         // bar(明示的に書いた形)

// 「$$name」は「${$name}」の糖衣構文

動作の仕組み

<?php
$prefix = 'user';

// ${$prefix . '_id'} = 100;
// ↑ これは「$prefix の値 = 'user'」+ '_id' = 'user_id' を変数名として使う
//   = $user_id = 100;
${$prefix . '_id'} = 100;
${$prefix . '_name'} = 'taro';

echo $user_id;     // 100
echo $user_name;   // taro

配列での利用(PHP 7 の挙動変更)

PHP 7 で式の評価順序が左から右に統一され、可変変数と配列の組み合わせは明示的な波括弧が必要になりました。

<?php
$arr = ['x' => 'hello'];
$name = 'arr';

// PHP 5: $$name['x'] → ${$name['x']} → $arr の x キー = 'hello' を変数名扱い
// PHP 7+: 左から評価 → $$name → $arr、その後 ['x']
echo ${$name}['x'];      // 'hello' (明示的)
echo ${$name['x']};      // PHP 5/7 で挙動差。明示化が必須

// 解釈を明確にするため、PHP 7+ では波括弧を強く推奨

可変関数(Variable Functions)

可変変数の親戚として、変数に格納した関数名 / メソッド名で呼び出す機能があります。こちらは現代でもよく使われる正当な使い方です。

<?php
// 関数の動的呼び出し
$func = 'strtoupper';
echo $func('hello');     // HELLO

// オブジェクトメソッドの動的呼び出し
class Service {
    public function run() { echo "running"; }
    public function stop() { echo "stopped"; }
}
$obj = new Service();
$method = 'run';
$obj->$method();          // run() が呼ばれる
$obj->{$method}();        // 同じ(明示形)

// クラスメソッドの動的呼び出し
$class = 'Service';
$instance = new $class();

なぜ可変変数は非推奨か

問題影響
可読性$$x のような変数がどこで定義されたか追えない
静的解析 (PHPStan / Psalm)変数の型 / 存在を追跡不能 → 警告 or 解析停止
IDE 補完動的生成のため補完できない
セキュリティユーザ入力を変数名にすると任意変数上書きに直結(過去の register_globals 問題)
パフォーマンスOPcache 等の最適化が効きにくい
リファクタ変数名検索 / リネームが効かない

代替策1: 連想配列を使う

動的命名がしたいなら連想配列で十分です。

<?php
// ❌ 古い可変変数パターン
foreach ($_POST as $key => $value) {
    $$key = $value;       // ⚠️ register_globals 相当。脆弱性の温床
}
echo $user_id;            // どこから来た値か分からない

// ✅ 明示的な配列アクセス
$input = [];
foreach ($_POST as $key => $value) {
    $input[$key] = $value;
}
echo $input['user_id'];   // ★ 出所が明確

// ✅ もっとシンプル: $_POST をそのまま使う
echo $_POST['user_id'] ?? '';

代替策2: Reflection でプロパティ動的アクセス

<?php
class User {
    public string $name = 'taro';
    public int $age = 30;
}

$user = new User();

// ❌ 可変変数で「無理やり」
// $field = 'name';
// echo $user->$field;     // 一応動くがダメ

// ✅ Reflection で意図を明示
$ref = new ReflectionObject($user);
$prop = $ref->getProperty('name');
echo $prop->getValue($user);     // 'taro'

// ✅ プロパティ存在チェック
if (property_exists($user, 'name')) {
    echo $user->{'name'};         // 同等だが意図明確
}

セキュリティ事故例

<?php
// ❌ 危険: 認証情報を上書きされる
$is_admin = false;

foreach ($_POST as $key => $value) {
    $$key = $value;       // ★ 攻撃者が is_admin=1 を POST で送ると...
}

if ($is_admin) {
    grantAdminAccess();   // ★ 認証バイパス成功
}

// ✅ 安全: 明示的に必要なキーだけ取り出す
$name = $_POST['name'] ?? '';
$email = $_POST['email'] ?? '';
// $is_admin はリクエストから上書きできない

これは古い register_globals 問題と同根で、PHP 5.4 で register_globals が削除された理由でもあります。

許容できる用途(限定的)

  • extract() の代替で、信頼できる連想配列を変数展開する場合(テンプレートエンジン内部など)
  • テスト用のクイックハック(本番コードに残さない)
  • レガシーコードを読み解く必要がある場合(書き換えはしない)

PHP 8 での挙動

可変変数は PHP 8 でも機能として残っていますが、エンジンレベルでの最適化対象外です。コード品質ツール(PHPStan level 5+ / Psalm)は警告を出します。

<?php
// PHP 8 でも動く
$x = 'hello';
$$x = 'world';
echo $hello;          // world

// PHPStan で実行
// $ vendor/bin/phpstan analyse --level 5
// Variable $hello might not be defined.

extract() / compact() という関連機能

<?php
// extract: 配列キーを変数として展開(可変変数の親戚)
$data = ['name' => 'taro', 'age' => 30];
extract($data);
echo $name;   // taro
echo $age;    // 30

// ⚠️ extract($_POST) は厳禁。register_globals と同じ脆弱性

// ✅ EXTR_SKIP / EXTR_PREFIX_ALL で対策可
extract($_POST, EXTR_PREFIX_ALL, 'in');
echo $in_name;    // in_ プレフィックス付きになる

// compact: 変数を配列にまとめる(逆操作)
$name = 'taro';
$age = 30;
$data = compact('name', 'age');
// ['name' => 'taro', 'age' => 30]

FAQ

Q: 可変変数を使っているレガシーコードを引き継いだ
A: まずは触らず動作を理解 → ユニットテストを書く → 連想配列に置き換え、というステップを推奨。一気に書き換えると挙動差で壊しやすい。

Q: $$x${$x} の違い?
A: 同じ意味です。${$x} が明示形でパース順が確定するため、複雑な式(配列・プロパティ絡み)では波括弧形を使うべき。

Q: 可変関数も非推奨?
A: 可変関数は OK です。コールバック / ストラテジーパターン / ルーティングで標準的に使われます。call_user_func よりも $callable() の方が速い。

編集
Post Share
子ページ

子ページはありません

同階層のページ
  1. 基本事項
  2. HTMLへの埋め込み
  3. 変数
  4. 可変変数
  5. 定数
  6. データ型
  7. キャスト
  8. エスケープ文字
  9. 配列
  10. 演算子
  11. 代入の際の注意点
  12. 条件分岐
  13. 繰り返し処理
  14. クラスとインスタンス
  15. コンストラクタ
  16. 関数
  17. スーパーグローバル変数
  18. スコープ
  19. staticについて
  20. yieldについて
  21. ファイルのアップロード方法
  22. DB接続方法
  23. SQL実行方法
  24. カプセル化の具体例
  25. 継承の構文
  26. オーバーライド
  27. ポリモーフィズム(多様性)の具体例
  28. 抽象クラス・メソッドの構文と具体例
  29. GET通信
  30. try catchで全てのエラーを拾う方法

最近更新/作成されたページ