はじめに
PHPで変数を代入する場合に値の参照を渡すのか実体を新しく作るのか忘れてしまうことが多く、毎回動作確認をして試していました。
AIにコードを書いてもらう機会が増え、意識しないと見逃してしまいそうだなと思ったので、理解してまとめようと思います。
各操作における元変数の値の変化
プリミティブ型の場合
プリミティブ型は変数ごとに独立しています。
値を変更しても他の変数の値が変わることはありません。
<?php
// 変数を作成し、別の変数に代入
$a = 'init';
$b = $a;
// それぞれ違う値に書き換える
$a = 'update_1';
$b = 'update_2';
// それぞれが書き換えた値になっていることを確認
echo 'a ===> ' . $a;
echo PHP_EOL;
echo 'b ===> ' . $b;
a ===> update_1
b ===> update_2
<?php
function update($c)
{
$c = 'update';
}
// 変数に値を代入し、更新する関数を呼び出す
$a = 'init';
update($a);
// 値は変わっていないことを確認
echo 'a ===> ' . $a;
echo PHP_EOL;
a ===> init
影響させたい場合は参照渡しをする。
<?php
// 変数を作成し、別の変数に代入
$a = 'init';
$b = &$a; // ★参照渡し
// それぞれ違う値に書き換える
$a = 'update_1';
$b = 'update_2';
// どちらも同じ値になる
echo 'a ===> ' . $a;
echo PHP_EOL;
echo 'b ===> ' . $b;
a ===> update_2
b ===> update_2
<?php
// 参照渡し
function update(&$c)
{
$c = 'update';
}
$a = 'init';
update($a);
echo 'a ===> ' . $a;
echo PHP_EOL;
a ===> update
オブジェクトの場合
オブジェクトの場合、値の変更をした場合は元の値にも影響があります
<?php
class Test
{
protected string $name;
protected int $price;
public function __construct(){
$this->name = 'init';
$this->price = 0;
}
public function getName() { return $this->name; }
public function getPrice() { return $this->price; }
public function setName($name) { $this->name = $name; }
public function setPrice($price) { $this->price = $price; }
}
// オブジェクトを作成し、新しい変数に代入
$a = new Test();
$b = $a;
// 代入元の変数と代入先の変数でそれぞれ値を更新
$a->setName('updateName');
$b->setPrice(1);
// どちらの変更でも両方の値が変更されていることを確認
echo 'a ===> name:' . $a->getName() . ', price: ' . $a->getPrice();
echo PHP_EOL;
echo 'b ===> name:' . $b->getName() . ', price: ' . $b->getPrice();
a ===> name:updateName, price: 1
b ===> name:updateName, price: 1
関数内で更新する場合も同様です。
<?php
class Test
{
protected string $name;
protected int $price;
public function __construct(){
$this->name = 'init';
$this->price = 0;
}
public function getName() { return $this->name; }
public function getPrice() { return $this->price; }
public function setName($name) { $this->name = $name; }
public function setPrice($price) { $this->price = $price; }
}
function update($c)
{
$c->setName('updateName');
$c->setPrice(2);
}
// オブジェクトを作成
$a = new Test();
// 関数内でオブジェクトの値を更新
update($a);
// 関数内でされた更新で値が変わっている事を確認
echo 'a ===> name:' . $a->getName() . ', price: ' . $a->getPrice();
a ===> name:updateName, price: 2
参照させたくない場合はcloneすることで別の実体を作成することができます。
<?php
class Test
{
protected string $name;
protected int $price;
public function __construct(){
$this->name = 'init';
$this->price = 0;
}
public function getName() { return $this->name; }
public function getPrice() { return $this->price; }
public function setName($name) { $this->name = $name; }
public function setPrice($price) { $this->price = $price; }
}
// オブジェクトを作成し、新しい変数に代入
$a = new Test();
$b = clone($a); // ★cloneすると新しいオブジェクトになる
// 代入元の変数と代入先の変数でそれぞれ値を更新
$a->setName('updateName');
$b->setPrice(1);
// 変更がそれぞれのオブジェクトにしか影響していないことを確認
echo 'a ===> name:' . $a->getName() . ', price: ' . $a->getPrice();
echo PHP_EOL;
echo 'b ===> name:' . $b->getName() . ', price: ' . $b->getPrice();
a ===> name:updateName, price: 0
b ===> name:init, price: 1
一方で、オブジェクトを保持するクラスをcloneしてもオブジェクト型のメンバ変数はcloneされません。
<?php
class Test
{
protected string $name;
public function __construct(){
$this->name = 'init';
}
public function getName() { return $this->name; }
public function setName($name) { $this->name = $name; }
}
class ParentTest
{
protected Test $test;
public function __construct()
{
$this->test = new Test();
}
public function getTest() { return $this->test; }
public function setTest($test) { $this->test = $test; }
}
// オブジェクトを作成し、新しい変数に代入
$a = new ParentTest();
$b = clone($a);
$a->getTest()->setName('update');
// 代入元の変数と代入先の変数でそれぞれ値を更新
echo 'a ===> name:' . $a->getTest()->getName();
echo PHP_EOL;
echo 'b ===> name:' . $b->getTest()->getName();
a ===> name:update
b ===> name:update
その場合、__clone()にcloneされた時の操作を定義できます。
<?php
class Test
{
protected string $name;
public function __construct(){
$this->name = 'init';
}
public function getName() { return $this->name; }
public function setName($name) { $this->name = $name; }
}
class ParentTest
{
protected Test $test;
public function __construct()
{
$this->test = new Test();
}
public function __clone()
{
// cloneされたら$testもcloneする
$this->test = clone($this->test);
}
public function getTest() { return $this->test; }
public function setTest($test) { $this->test = $test; }
}
// オブジェクトを作成し、新しい変数に代入
$a = new ParentTest();
$b = clone($a);
$a->getTest()->setName('update');
// 代入元の変数と代入先の変数でそれぞれ値を更新
echo 'a ===> name:' . $a->getTest()->getName();
echo PHP_EOL;
echo 'b ===> name:' . $b->getTest()->getName();
a ===> name:update
b ===> name:init
配列の場合
配列自体は各変数で独立しています。
配列操作が別の配列に影響を及びすことはありません。
一方で、配列の要素については、各要素の型に従います。
<?php
class Test
{
protected string $name;
protected int $price;
public function __construct(){
$this->name = 'init';
$this->price = 0;
}
public function getName() { return $this->name; }
public function getPrice() { return $this->price; }
public function setName($name) { $this->name = $name; }
public function setPrice($price) { $this->price = $price; }
}
// 配列を作成し、別の変数に代入
$a = ['first', 0, new Test(), new Test()];
$b = $a;
// 代入元の変数と代入先の変数でそれぞれ値を更新
$a[0] = 'fffff';
$b[1] = 1;
// a[2] を別のオブジェクトに差し替えても $b には影響しない
$a[2] = new Test();
$a[2]->setName('aaaa');
// 代入前から存在するオブジェクトの場合、変更が代入先まで影響する
$a[3]->setName('bbbb');
// それぞれの結果を確認
echo 'a ===> 0:' . $a[0] . ', 1: ' . $a[1] . ', 2: ' . $a[2]->getName() . ', 3: ' . $a[3]->getName();
echo PHP_EOL;
echo 'b ===> 0:' . $b[0] . ', 1: ' . $b[1] . ', 2: ' . $b[2]->getName() . ', 3: ' . $b[3]->getName();
a ===> 0:fffff, 1: 0, 2: aaaa, 3: bbbb
b ===> 0:first, 1: 1, 2: init, 3: bbbb
おわりに
今回は変数を代入した場合の動作を調べました。
AI時代こそこのような基礎を理解しておくのは非常に重要だと思います。
「試すのも面倒なのでよくわからないけどApprove」みたいなことをしないよう、これからも曖昧なところは理解していこうと思います。
ここまでご覧いただきありがとうございました!