Perbandingan dan Kloning Objek

Perbandingan == versus === pada objek, __clone, serta kloning dangkal versus dalam.

Daftar isi

Objek dibandingkan dan digandakan dengan aturan yang berbeda dari nilai sederhana, karena sebuah objek selalu dipegang melalui rujukan ke satu penyimpanan yang sama. Topik ini membahas perbedaan == dan === pada objek, penggandaan dengan clone beserta metode __clone(), serta perbedaan kloning dangkal (shallow copy) dan kloning dalam (deep copy) ketika objek memuat objek lain di dalam propertinya. Contohnya melanjutkan domain perpustakaan dengan dua eksemplar buku dengan judul yang sama, dan fungsi spl_object_id() dipakai untuk membuktikan identitas setiap objek pada keluaran.

Perbandingan == dan === pada objek#

Operator == menyatakan dua objek sama apabila keduanya dibuat dari kelas yang sama dan seluruh propertinya bernilai sama. Operator === menyatakan sama hanya apabila keduanya merujuk objek yang sama persis. Aturan ini berbeda dari perbandingan nilai sederhana, karena === pada objek memeriksa identitas penyimpanan, bukan kesamaan isi. Perilaku terkait juga muncul pada penugasan biasa. Pernyataan $c = $a; tidak menggandakan objek, melainkan membuat nama $c menunjuk objek yang sama dengan $a.

Fungsi spl_object_id() mengembalikan pengenal angka yang unik untuk setiap objek yang masih ada di memori, sehingga cocok membuktikan identitas pada keluaran. Dua objek yang berbeda selalu memperoleh pengenal yang berbeda, sedangkan dua nama yang merujuk objek yang sama memperoleh pengenal yang sama.

Skrip berikut membuat dua objek Buku dengan isi properti yang sama persis, lalu menetapkan $c = $a; sebagai penugasan biasa. Ketiga perbandingan dan pengenalnya diperlihatkan berdampingan.

php perbandingan-objek.php
<?php
declare(strict_types=1);

class Buku
{
    public function __construct(
        public string $judul,
        public int $tahun,
    ) {
    }
}

$a = new Buku('Algoritma Dasar', 2021);
$b = new Buku('Algoritma Dasar', 2021);
$c = $a;

var_dump($a == $b);
var_dump($a === $b);
var_dump($a === $c);

echo 'id a: ', spl_object_id($a), "\n";
echo 'id b: ', spl_object_id($b), "\n";
echo 'id c: ', spl_object_id($c), "\n";
text Keluaran
bool(true)
bool(false)
bool(true)
id a: 1
id b: 2
id c: 1

Baris pertama bernilai true karena isi kedua objek memang sama, sedangkan baris kedua bernilai false karena $a dan $b adalah dua penyimpanan yang berbeda, dan perbedaan pengenal keduanya pada keluaran menegaskan hal tersebut. Baris ketiga bernilai true dengan pengenal yang sama persis antara $a dan $c, karena penugasan hanya menambah nama untuk objek yang sama. Apabila keputusan program bergantung pada identitas objek, misalnya dua eksemplar buku dengan judul yang sama tetapi tetap merupakan dua eksemplar berbeda, maka === atau pengenal objek adalah alat yang tepat.

Kloning dangkal dengan clone#

Kata kunci clone menggandakan objek menjadi penyimpanan baru yang berdiri sendiri. Penggandaan yang dilakukan PHP bersifat dangkal. Properti bertipe sederhana disalin isinya, sedangkan properti yang berisi objek hanya disalin rujukannya, sehingga kedua objek masih berbagi objek dalam tersebut. Metode __clone() memberi kesempatan menyesuaikan hasil salinan, dan PHP menjalankannya pada objek salinan setelah penyalinan dangkal selesai.

Sifat dangkal ini sering baru terlihat ketika perubahan dilakukan. Mengubah properti sederhana pada salinan tidak memengaruhi aslinya, tetapi mengubah isi objek dalam melalui salinan ikut terlihat pada aslinya karena keduanya merujuk objek dalam yang sama.

Skrip berikut mengkloning objek Buku yang memuat objek Penulis, lalu mengubah judul dan nama penulis melalui salinannya. Pengenal objek pada keluaran membuktikan bahwa kedua buku terpisah, sedangkan kedua penulis masih satu.

php kloning-dangkal.php
<?php
declare(strict_types=1);

class Penulis
{
    public function __construct(public string $nama)
    {
    }
}

class Buku
{
    public function __construct(
        public string $judul,
        public Penulis $penulis,
    ) {
    }
}

$asli = new Buku('Algoritma Dasar', new Penulis('Citra'));
$salinan = clone $asli;

echo 'id buku asli: ', spl_object_id($asli), "\n";
echo 'id buku salinan: ', spl_object_id($salinan), "\n";
echo 'id penulis asli: ', spl_object_id($asli->penulis), "\n";
echo 'id penulis salinan: ', spl_object_id($salinan->penulis), "\n";

$salinan->judul = 'Algoritma Dasar, edisi kedua';
$salinan->penulis->nama = 'Budi';

echo $asli->judul, "\n";
echo $asli->penulis->nama, "\n";
echo $salinan->judul, "\n";
echo $salinan->penulis->nama, "\n";
text Keluaran
id buku asli: 1
id buku salinan: 3
id penulis asli: 2
id penulis salinan: 2
Algoritma Dasar
Budi
Algoritma Dasar, edisi kedua
Budi

Pengenal buku yang berbeda pada dua baris pertama menunjukkan penggandaannya berhasil, sedangkan pengenal penulis yang sama pada dua baris berikutnya membuktikan objek dalamnya masih dibagi. Empat baris terakhir memperlihatkan akibatnya, yaitu judul salinan berubah secara terpisah, tetapi nama penulis yang diubah melalui salinan ikut berubah pada aslinya.

Kloning dangkalbuku aslisalinanpenulissatu penulis dibagi dua bukuKloning dalambuku aslisalinanpenulis Apenulis Bsetiap buku memegang penulisnya sendiri
Perbedaan kloning dangkal dan kloning dalam pada objek yang memuat objek lain

Kloning dalam dengan __clone#

Kloning dalam adalah penggandaan yang turun sampai objek-objek di dalam propertinya, sehingga salinan tidak lagi berbagi objek dalam dengan aslinya. PHP tidak menyediakannya secara otomatis, dan cara yang lazim adalah mendefinisikan __clone() untuk menggandakan properti berisi objek satu per satu. Di dalam __clone(), $this menunjuk objek salinan, sehingga penugasan ulang properti pada $this hanya memengaruhi salinan.

Bentuk paling sederhana mengkloning setiap properti berisi objek secara langsung, misalnya $this->penulis = clone $this->penulis;. Struktur yang lebih dalam memerlukan penggandaan berlapis, karena setiap objek dalam yang memuat objek lain memerlukan __clone() sendiri. Kelas yang memuat banyak properti objek dapat menyusun metode ini berurutan. Oleh karena itu, keputusan antara dangkal dan dalam sebaiknya dipertimbangkan secara sengaja pada setiap kelas.

Skrip berikut menambahkan __clone() pada kelas Buku yang sama dengan contoh sebelumnya, lalu mengulang pengubahan nama penulis melalui salinan.

php kloning-dalam.php
<?php
declare(strict_types=1);

class Penulis
{
    public function __construct(public string $nama)
    {
    }
}

class Buku
{
    public function __construct(
        public string $judul,
        public Penulis $penulis,
    ) {
    }

    public function __clone(): void
    {
        $this->penulis = clone $this->penulis;
    }
}

$asli = new Buku('Algoritma Dasar', new Penulis('Citra'));
$salinan = clone $asli;

echo 'id penulis asli: ', spl_object_id($asli->penulis), "\n";
echo 'id penulis salinan: ', spl_object_id($salinan->penulis), "\n";

$salinan->penulis->nama = 'Budi';

echo $asli->penulis->nama, "\n";
echo $salinan->penulis->nama, "\n";
text Keluaran
id penulis asli: 2
id penulis salinan: 4
Citra
Budi

Pengenal penulis yang kini berbeda menunjukkan objek dalamnya ikut digandakan, dan dua baris terakhir membuktikan perubahan pada salinan tidak menyentuh aslinya lagi. Nama penulis asli tetap Citra, sedangkan salinannya menjadi Budi, dan itulah perbedaan yang dihadirkan kloning dalam.

Kesalahan umum#

  • Menganggap == memeriksa identitas objek. Operator == menyamakan isi properti, sehingga dua eksemplar berbeda dengan isi sama lulus perbandingan itu. Gunakan === apabila yang dimaksud adalah objek yang sama persis, sehingga maksud kode terbaca tanpa menebak.
  • Mengira clone menggandakan seluruh struktur. Penggandaan bawaan bersifat dangkal, sehingga objek dalam tetap dibagi dan perubahannya menyebar ke aslinya. Definisikan __clone() yang menggandakan properti berisi objek apabila kelasnya membutuhkan kloning dalam.
  • Menggandakan objek dengan new lalu menyalin properti satu per satu. Salinan manual mudah tertinggal ketika properti baru ditambahkan, dan perbedaannya hanya muncul saat program berjalan. Gunakan clone beserta __clone() agar seluruh keadaan ikut tertangani di satu tempat.
  • Menyimpan spl_object_id() sebagai pengenal permanen. Angka tersebut unik hanya selama objek tersebut masih ada di memori dan dapat dipakai ulang oleh objek lain setelah yang lama dibersihkan. Pengenal yang harus bertahan disimpan sebagai atribut data kelas, misalnya kode koleksi, bukan sebagai pengenal memori.

Ringkasan#

  • == menyamakan kelas dan isi properti, sedangkan === hanya menyamakan identitas penyimpanan, dan penugasan biasa hanya menambah nama bagi objek yang sama.
  • clone menggandakan objek secara dangkal, yaitu properti objek dalam masih dibagi, dan __clone() dijalankan pada salinan untuk menyesuaikannya.
  • Kloning dalam ditulis manual melalui __clone() dengan menggandakan setiap properti berisi objek, sehingga salinan sepenuhnya berdiri sendiri.
  • spl_object_id() membuktikan identitas objek pada keluaran dan berguna saat debug, tetapi tidak layak disimpan sebagai pengenal jangka panjang.

Referensi#