Refleksi php mengatur properti pribadi

PHP memiliki sekumpulan API refleksi yang dapat digunakan untuk mengintrospeksi kelas, antarmuka, fungsi, metode, dan ekstensi. Di antara banyak fitur lainnya, refleksi memungkinkan kita untuk mengakses properti kelas dan memanggil metode kelas meskipun dideklarasikan sebagai pribadi/dilindungi. Saya banyak menggunakannya untuk menulis pengujian unit untuk metode privat di beberapa proyek lawas di mana pengujian metode publik terlalu rumit

Jadi, bagaimana cara melakukannya?

class Foo {
    private $bar = 'a';
    
    protected function call() {
        return 'bazz';
    }
}


$obj = new Foo;

// lets create reflection for private $bar property
$reflectedProperty = new ReflectionProperty(Foo::class, 'bar');

// set it as accessible
$reflectedProperty->setAccessible(true);

// now we can read the value of the property
echo ($reflectedProperty)->getValue($obj);

// now reflection for protected call() method
$reflectedMethod = new ReflectionMethod(Foo::class, 'call');

// set it as accessible
$reflectedMethod->setAccessible(true);

// now we can invoke/call that method as if it is public method
echo $reflectedMethod->invoke($obj); 


Seperti yang dapat kita lihat, untuk mengakses properti atau metode pribadi/dilindungi, kita perlu memanggil metode setAccessible(true) sebelum mengakses. Jika kami tidak memanggil metode ini, kami akan mendapatkan pengecualian

Uncaught ReflectionException: Trying to invoke protected method Foo::call() from scope ReflectionMethod
_

Agak mengganggu karena jika kita menerima objek ReflectionProperty atau ReflectionMethod dari pustaka atau modul pihak ketiga mana pun, kita tidak tahu apakah setAccessible() telah dipanggil pada objek itu atau tidak. Jadi agar lebih aman, kita perlu memanggil setAccessible() lagi


PHP 8. 1 memecahkan masalah itu

Mulai sekarang, kita dapat mengakses properti atau memanggil metode melalui API refleksi tanpa perlu memanggil setAccessible() secara eksplisit, meskipun dilindungi/pribadi

Setiap kali kami mencoba mengakses properti pribadi/dilindungi atau memanggil metode, itu akan berperilaku seolah-olah setAccessible(true) telah dipanggil di muka

Saya sedang mendiskusikan teknik ini selama tinjauan kode baru-baru ini, dan saya menyadari teknik keren ini mungkin tidak begitu dikenal. Saya menemukannya secara tidak sengaja beberapa tahun yang lalu

Kadang-kadang, semata-mata untuk tujuan pengujian, kita mungkin perlu mengakses properti atau metode pribadi atau yang dilindungi. Kecenderungan kita yang biasa adalah menggunakan Refleksi untuk melakukan ini. Refleksi agak rumit, karena ada banyak boilerplate tambahan untuk menyiapkannya

Tapi penutupan sebenarnya memberi kita cara keren untuk melakukannya dengan lebih mudah. Jika Anda pernah menggunakan metode

(fn (int $newAge) => $this->age = $newAge)->call($me, 30); // Changes $me->age to 30
4 JavaScript, kita dapat melakukan hal yang sama di PHP

Mari kita mulai dengan kelas sampel

class Person
{
    public string $name = 'Dan';
    protected int $age  = 40;
    private bool $cool  = true;

    protected static string $species = 'homo sapien';

    protected static function evolve(): void
    {
        static::$species = 'homo superior';
    }

    protected function birthday(): void
    {
        $this->age++;
    }

    public function howOldAmI(): int
    {
        return $this->age;
    }
}

$me = new Person();

Masuk ke mode layar penuh Keluar dari mode layar penuh

Properti non-statis

Kami tahu cara membaca dan mengubah

(fn (int $newAge) => $this->age = $newAge)->call($me, 30); // Changes $me->age to 30
5. Ini publik, jadi tidak ada masalah di sana

Sekarang, bagaimana jika kita perlu, di suatu tempat dalam ujian, untuk menetapkan usia yang tepat. Itu adalah properti yang dilindungi, jadi kita tidak bisa langsung mengubahnya, tetapi Penutupan bisa memberi kita jalan keluarnya

(fn (int $newAge) => $this->age = $newAge)->call($me, 30); // Changes $me->age to 30

Masuk ke mode layar penuh Keluar dari mode layar penuh

Jadi, apa yang kita lakukan di sini?

$changeAge = (fn (int $newAge) => $this->age = $newAge);
_

Masuk ke mode layar penuh Keluar dari mode layar penuh

Pertama, kami membuat penutupan yang mengubah

(fn (int $newAge) => $this->age = $newAge)->call($me, 30); // Changes $me->age to 30
6 menjadi apa pun yang diteruskan ke sana. Jika kami mencoba melakukan
(fn (int $newAge) => $this->age = $newAge)->call($me, 30); // Changes $me->age to 30
_7, kami akan mendapatkan kesalahan, karena kami mendefinisikan ini di luar kelas.
(fn (int $newAge) => $this->age = $newAge)->call($me, 30); // Changes $me->age to 30
8 sebenarnya tidak ada di sini, atau, jika kita memasukkan kode ini ke dalam pengujian unit,
(fn (int $newAge) => $this->age = $newAge)->call($me, 30); // Changes $me->age to 30
8 akan merujuk ke kelas pengujian unit itu sendiri

Di situlah

$changeAge = (fn (int $newAge) => $this->age = $newAge);
_0 masuk.
$changeAge = (fn (int $newAge) => $this->age = $newAge);
1 mengikat penutupan ke objek baru, dan memanggilnya dengan argumen apa pun yang Anda berikan padanya. Dengan kata lain, argumen pertama diteruskan ke
$changeAge = (fn (int $newAge) => $this->age = $newAge);
0 menjadi
(fn (int $newAge) => $this->age = $newAge)->call($me, 30); // Changes $me->age to 30
8 dalam penutupan. Argumen lain apa pun diteruskan ke fungsi itu sendiri

$changeAge->call($me, 30);

Masuk ke mode layar penuh Keluar dari mode layar penuh

Kami juga dapat dengan mudah mengakses properti

$changeAge = (fn (int $newAge) => $this->age = $newAge);
_4 dan
$changeAge = (fn (int $newAge) => $this->age = $newAge);
5 tanpa mengubahnya. Jadi, saya bisa melakukannya

$amICool = (fn () => $this->cool)->call($me); // true
$myAge   = (fn () => $this->age)->call($me);

Masuk ke mode layar penuh Keluar dari mode layar penuh

Sifat statis

Itu berfungsi dengan baik untuk properti atau metode non-statis. Bagaimana dengan yang statis?

$mySpecies = (fn () => static::$species)->bindTo(null, Person::class)(); // homo sapien
(fn () => static::evolve())->bindTo(null, Person::class)(); // Changes to homo superior
(fn (string $newSpecies) => static::$species = $newSpecies)->bindTo(null, Person::class)('homo erectus'); // devolve to homo erectus

Masuk ke mode layar penuh Keluar dari mode layar penuh

Jadi, ini sedikit berbeda. Pertama, kami menggunakan

$changeAge = (fn (int $newAge) => $this->age = $newAge);
_6 yang mengembalikan penutupan baru, yang telah diikat ulang ke objek atau kelas yang ditentukan. Dengan
$changeAge = (fn (int $newAge) => $this->age = $newAge);
_7, Anda dapat meneruskan sebuah objek (seperti
$changeAge = (fn (int $newAge) => $this->age = $newAge);
0), atau Anda dapat membiarkan null itu dan meneruskan kelas, yang mengubah pengikatan statis. Itulah yang telah kami lakukan di sini. Dan karena mengembalikan penutupan baru, Anda kemudian harus memanggilnya, itulah sebabnya kami memiliki tambahan
$changeAge = (fn (int $newAge) => $this->age = $newAge);
9 setelah

Jadi, mari kita uraikan ini selangkah demi selangkah juga

$getSpecies = (fn () => static::$species);

Masuk ke mode layar penuh Keluar dari mode layar penuh

Penutupan yang mengembalikan

$changeAge->call($me, 30);
_0 dari cakupan saat ini

$boundGetSpecies = $getSpecies->bindTo(null, Person::class);

Masuk ke mode layar penuh Keluar dari mode layar penuh

Penutupan baru, yang mengubah cakupan menjadi

$changeAge->call($me, 30);
1, artinya setiap referensi ke
$changeAge->call($me, 30);
2 mengacu pada
$changeAge->call($me, 30);
1

$mySpecies = $boundGetSpecies();

Masuk ke mode layar penuh Keluar dari mode layar penuh

Atau, jika kita mengubah spesies menjadi nilai arbitrer, seperti yang kita lakukan pada yang ketiga

// Create species changing closure, initially bound to the current scope
$changeSpecies = (fn (string $newSpecies) => static::$species = $newSpecies);
// Create a new Closure, bound to the Person scope
$changePersonSpecies = $changeSpecies->bindTo(null, Person::class);
// Change it to whatever
$changePersonSpecies('homo erectus');

Masuk ke mode layar penuh Keluar dari mode layar penuh

Menggunakan $changeAge = (fn (int $newAge) => $this->age = $newAge); _7 dengan objek

Anda dapat menggunakan

$changeAge = (fn (int $newAge) => $this->age = $newAge);
_7 dengan cara yang mirip dengan
$changeAge = (fn (int $newAge) => $this->age = $newAge);
0

(fn (int $newAge) => $this->age = $newAge)->call($me, 30); // Changes $me->age to 30
0

Masuk ke mode layar penuh Keluar dari mode layar penuh

Mari kita hancurkan

(fn (int $newAge) => $this->age = $newAge)->call($me, 30); // Changes $me->age to 30
1

Masuk ke mode layar penuh Keluar dari mode layar penuh

Penutupan yang memanggil

$changeAge->call($me, 30);
_7 pada objek dalam lingkup saat ini

(fn (int $newAge) => $this->age = $newAge)->call($me, 30); // Changes $me->age to 30
2

Masuk ke mode layar penuh Keluar dari mode layar penuh

Buat penutupan baru dengan

$changeAge->call($me, 30);
_8 sebagai ruang lingkup. Dengan cara ini, setiap referensi ke
(fn (int $newAge) => $this->age = $newAge)->call($me, 30); // Changes $me->age to 30
_8 merujuk ke
$changeAge->call($me, 30);
8

(fn (int $newAge) => $this->age = $newAge)->call($me, 30); // Changes $me->age to 30
_3

Masuk ke mode layar penuh Keluar dari mode layar penuh

Karena

$amICool = (fn () => $this->cool)->call($me); // true
$myAge   = (fn () => $this->age)->call($me);
_1 adalah penutupan, kita harus benar-benar menyebutnya

Kesimpulan

Tentu, kita dapat menggunakan

$amICool = (fn () => $this->cool)->call($me); // true
$myAge   = (fn () => $this->age)->call($me);
2 dan
$amICool = (fn () => $this->cool)->call($me); // true
$myAge   = (fn () => $this->age)->call($me);
3 untuk melakukan semua ini, tetapi teknik ini sangat menyederhanakannya, karena memanggil satu metode pribadi adalah satu baris kode

Saya benar-benar menggunakan ini dalam paket yang baru-baru ini saya tulis yang seharusnya membuat ini lebih mudah

Dan untuk mengulangi, saya tidak menyarankan melakukan ini dalam kode produksi. Ada alasan visibilitas ada. Kita tidak boleh menghindarinya seperti ini dalam kode di server kita. Namun, jika kita perlu mengutak-atik beberapa objek untuk pengujian, teknik ini dapat menyederhanakannya untuk kita

Bagaimana cara mengakses properti pribadi di PHP?

php gunakan PhpPrivateAccess \MyClass; . $penutupan = \Penutupan. bind(function (MyClass $class) { return $class->property; }, null, MyClass. kelas); . "

Apa gunanya Setvalue() di PHP?

Deskripsi ¶ . Sets (changes) the property's value.

Apa gunanya refleksi di PHP?

Istilah "refleksi" dalam pengembangan perangkat lunak berarti bahwa suatu program mengetahui strukturnya sendiri pada waktu proses dan juga dapat memodifikasinya. Kemampuan ini juga disebut sebagai "introspeksi". Di area PHP, refleksi digunakan untuk memastikan keamanan jenis dalam kode program .