11.

PHP 代入の注意点完全ガイド

編集
この記事の要点
  • PHP のデフォルトは値渡し= は値のコピー。配列も自動でコピーされる
  • オブジェクトだけは参照のように扱われる(実体はオブジェクト ID のコピー)。深いコピーは clone が必要
  • 参照渡し: $b = &$a または関数引数 function f(&$x)
  • 厳密比較 === 必須。0 == "abc" は PHP 7 までは true、PHP 8 で false に修正された
  • 未定義変数は警告: PHP 7 で notice、PHP 8 で warning。$x ?? nullisset() で安全化

値渡しと参照渡し

PHP の代入はデフォルトで値のコピーです。元の変数を書き換えても、コピー先には影響しません。

// スカラ値: 値渡し
$a = 10;
$b = $a;       // ★ 値のコピー
$b = 20;
echo $a;       // 10(変わらない)

// 配列: 値渡し(PHP の特徴)
$arr1 = [1, 2, 3];
$arr2 = $arr1;    // ★ 自動でコピー(書込み時にコピーされる Copy-on-Write)
$arr2[] = 4;
print_r($arr1);   // [1, 2, 3]
print_r($arr2);   // [1, 2, 3, 4]

// 参照渡し: & 演算子で明示的に
$x = 10;
$y = &$x;       // ★ $y は $x のエイリアス
$y = 20;
echo $x;        // 20($x も変わる)

// 解除
unset($y);
echo $x;        // 20($x は残る)

オブジェクトの代入 (大きな罠)

オブジェクトは内部的にオブジェクト ID(ハンドル)のコピーで動きます。実質的に参照のように振る舞います。

class User {
    public string $name;
    public function __construct(string $name) {
        $this->name = $name;
    }
}

$u1 = new User('Taro');
$u2 = $u1;           // ★ オブジェクト ID のコピー(実体は同じ)
$u2->name = 'Jiro';

echo $u1->name;      // Jiro ★ $u1 も変わってしまう!
echo $u2->name;      // Jiro

// 解決: clone で浅いコピー
$u3 = clone $u1;
$u3->name = 'Saburo';
echo $u1->name;      // Jiro(影響なし)
echo $u3->name;      // Saburo

clone と __clone マジックメソッド

class Order {
    public string $id;
    public array $items;
    public Customer $customer;    // 別オブジェクト

    public function __clone() {
        // ★ 深いコピーをカスタマイズ
        $this->id = uniqid('order_');         // 新 ID
        $this->customer = clone $this->customer;
        // items は配列(プリミティブ)なので自動でコピー
    }
}

$o1 = new Order();
$o2 = clone $o1;    // __clone が呼ばれる
代入の挙動深いコピー
int / float / bool / string値渡し不要
array値渡し (Copy-on-Write)不要(要素もコピーされる)
object参照のような振る舞いclone + __clone()
resource (リソース)参照言語標準ではコピー不可
null値渡し不要

null / 0 / '' / false の取り扱い

PHP は暗黙の型変換 (Type Juggling) を行うため、緩い比較 == は事故の温床です。

// ❌ PHP 7 までの罠(PHP 8 で挙動修正)
var_dump(0 == 'abc');        // PHP 7: true / PHP 8: false
var_dump('0' == false);      // true(両バージョン)
var_dump(null == 0);         // true(両バージョン)
var_dump([] == false);       // true

// ✅ 厳密比較を使う
var_dump(0 === 'abc');       // false
var_dump(null === 0);        // false
var_dump('' === null);       // false

// null と空文字を区別したいケース
$name = $request->input('name');
if ($name === null) {
    // フィールド未送信
} elseif ($name === '') {
    // 空欄で送信された
} else {
    // 何か入力された
}

未定義変数 / 未定義キー

// ❌ PHP 8 で Warning
$user = $data['user'];        // Warning: Undefined array key "user"
$value = $undefined;          // Warning: Undefined variable $undefined

// ✅ Null Coalescing Operator (??)
$user = $data['user'] ?? null;
$value = $undefined ?? 'default';

// ✅ Null Coalescing Assignment (??=)
$config['timeout'] ??= 30;    // 未設定なら 30

// ✅ isset / array_key_exists
if (isset($data['user'])) {
    $user = $data['user'];
}

// ★ isset vs array_key_exists
$a = ['x' => null];
isset($a['x']);                // false(null 扱い)
array_key_exists('x', $a);     // true(キーは存在する)

配列の初期化

// ❌ 初期化忘れ
function collect() {
    foreach ($items as $i) {
        $result[] = $i;    // 未初期化 → PHP 8 で Warning
    }
    return $result;
}

// ✅ 明示的に初期化
function collect(array $items): array {
    $result = [];
    foreach ($items as $i) {
        $result[] = $i;
    }
    return $result;
}

// 型宣言(PHP 7.4+ プロパティ型)
class Cart {
    public array $items = [];      // 初期値 [] で安全
    public ?User $user = null;
}

関数引数の参照渡し

// 値渡し(デフォルト)
function increment(int $n): int {
    return $n + 1;
}

$x = 10;
$y = increment($x);    // $x は変わらず 10、$y = 11

// 参照渡し
function incrementByRef(int &$n): void {
    $n++;
}

$x = 10;
incrementByRef($x);
echo $x;    // 11(破壊的変更)

// 配列ソートは標準関数で参照渡し
$arr = [3, 1, 2];
sort($arr);           // ★ $arr 自体が変わる
print_r($arr);        // [1, 2, 3]

PHP 8 の Named Arguments と代入

function createUser(string $name, int $age = 0, bool $active = true) {
    return compact('name', 'age', 'active');
}

// 名前付き引数で読みやすく
$user = createUser(
    name: 'Taro',
    active: false,      // age はデフォルト
);

// readonly プロパティ(一度だけ代入可)
class Coordinate {
    public function __construct(
        public readonly float $x,
        public readonly float $y,
    ) {}
}

$p = new Coordinate(1.0, 2.0);
// $p->x = 3.0;    ★ Error: Cannot modify readonly property

Type Juggling の例外

PHP 7PHP 8
0 == "abc"truefalse ★
0 == ""truefalse ★
0 == "0"truetrue
"abc" + 11 (notice)TypeError ★
(int)"abc"00

FAQ

Q: なぜ PHP 8 で 0 == "abc" の結果が変わった?
A: 旧仕様では文字列が数値に変換できないとき 0 として比較していました。これが多数のバグの原因になり、PHP 8 で文字列⇔数値比較ルールが見直されました(RFC: Saner string to number comparisons)。

Q: Laravel の null safety はどう書く?
A: Null Coalescing ??、Optional ヘルパー optional($user)->name、PHP 8+ の Null Safe Operator $user?->name など。

Q: $a = $b = $c = 0 はどう評価される?
A: 右結合で $a = ($b = ($c = 0))。代入式の戻り値は代入された値なので、3 変数とも 0 になります。

編集
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で全てのエラーを拾う方法

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