この内容は古いバージョンです。最新バージョンを表示するには、戻るボタンを押してください。
バージョン:3
ページ更新者:atom
更新日時:2026-06-11 07:07:02

タイトル: 可変変数
SEOタイトル: 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 7 の挙動変更)

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

 '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)

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

$method();          // run() が呼ばれる
$obj->{$method}();        // 同じ(明示形)

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

なぜ可変変数は非推奨か

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

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

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

 $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 でプロパティ動的アクセス

$field;     // 一応動くがダメ

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

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

セキュリティ事故例

 $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)は警告を出します。

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

 '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() の方が速い。