Mencipta objek dalam php. Belajar PHP

  • Terjemahan

Objek banyak digunakan hari ini, walaupun sukar untuk dibayangkan selepas keluaran PHP 5 pada tahun 2005. Pada masa itu, saya masih tahu sedikit tentang kemungkinan bahasa ini. Versi kelima PHP dibandingkan dengan versi sebelumnya, yang keempat, dan kelebihan utama keluaran baharu ialah model objek baharu yang sangat berkuasa. Dan hari ini, sepuluh tahun kemudian, kira-kira 90% daripada semua kod PHP mengandungi objek yang tidak berubah sejak PHP 5.0. Ini bercakap banyak tentang peranan yang dimainkan oleh pengenalan model objek, yang telah diperbaiki berkali-kali selama bertahun-tahun. Dalam jawatan ini saya ingin bercakap tentang bagaimana semuanya berfungsi "di bawah tudung". Supaya orang ramai memahami intipati proses - mengapa ia dilakukan dengan cara ini dan bukan sebaliknya - dan lebih baik, lebih menggunakan sepenuhnya kemungkinan bahasa. Saya juga akan menyentuh topik penggunaan memori oleh objek, termasuk perbandingan dengan tatasusunan yang setara (jika boleh).

Saya akan bercakap tentang PHP 5.4 sebagai contoh, dan perkara yang saya terangkan adalah benar untuk 5.5 dan 5.6, kerana model objek hampir tidak berubah di sana. Sila ambil perhatian bahawa dalam versi 5.3 perkara tidak begitu baik dari segi ciri dan prestasi keseluruhan.

Dalam PHP 7, yang masih dalam pembangunan aktif, model objek tidak banyak diolah semula, hanya perubahan kecil telah dibuat. Hanya kerana semuanya berfungsi dengan baik, dan yang terbaik adalah musuh yang baik. Ciri telah ditambahkan yang tidak menjejaskan teras, tetapi ini tidak akan dibincangkan di sini.

Sebagai demonstrasi, saya akan mulakan dengan penanda aras sintetik:

Kelas Foo ( awam $a = "foobarstring"; awam $b; awam $c = ["beberapa", "nilai"]; ) untuk ($i=0; $i<1000; $i++) { $m = memory_get_usage(); ${"var".$i} = new Foo; echo memory_get_usage() - $m"\n"; }
Di sini, kelas mudah dengan tiga atribut diisytiharkan, dan kemudian 1000 objek kelas ini dicipta dalam gelung. Perhatikan cara memori digunakan dalam contoh ini: apabila anda mencipta objek kelas Foo dan pembolehubah, 262 bait memori timbunan PHP diperuntukkan untuk menyimpannya.

Mari gantikan objek dengan tatasusunan yang setara:

Untuk ($i=0; $i<1000; $i++) { $m = memory_get_usage(); ${"var".$i} = [["some", "values"], null, "foobarstring"]; echo memory_get_usage() - $m . "\n"; }
Dalam kes ini, elemen yang sama digunakan: tatasusunan itu sendiri, null dan rentetan pembolehubah foobarstring . Itu baru sahaja menggunakan 1160 bait memori, iaitu 4.4 kali lebih banyak.

Berikut adalah contoh lain:

$kelas =<<<"CL" class Foo { public $a = "foobarstring"; public $b; public $c = ["some", "values"]; } CL; echo memory_get_usage() . "\n"; eval($class); echo memory_get_usage() . "\n";
Memandangkan kelas diisytiharkan pada masa penyusunan, kami menggunakan pernyataan eval() untuk mengisytiharkan dan mengukur memori yang digunakan (menggunakan pengurus memori PHP). Walau bagaimanapun, tiada objek dicipta dalam kod ini. Jumlah memori yang terlibat (memori perbezaan) ialah 2216 bait.

Sekarang mari kita lihat bagaimana semuanya berfungsi dalam kedalaman PHP, menyandarkan pemerhatian praktikal dengan teori.

Semuanya bermula dengan kelas

Di dalam PHP, kelas diwakili menggunakan struktur zend_class_entry:

Struct _zend_class_entry ( char type; const char *name; zend_uint name_length; struct _zend_class_entry *ibu bapa; int refcount; zend_uint ce_flags; HashTable function_table; HashTable properties_info; zval **default_properties_table_standard_properties **default_properties_table_statik_tetap;zvalmevalable_table_default_properties_table;zvalmevalable_table_default_properties_table;zvalmevalable_table_default_properties_table;zvalmevalable_table_default_properties int default_static_members_count; union _zend_function *constructor; union _zend_function *destructor; union _zend_function *clone; union _zend_function *__get; union _zend_function *__set; union _zend_function *__unset; union _zend_function __function _zend_union *__call_static; union _zend_function __call *__tostring; union _zend_function *serialize_func; union _zend_function *unserialize_func; zend_class_iterator_funcs iterator_funcs; /* pengendali */ zend_object_value (*create_object)(zend_class_entry *class_type TSRMLS_object*class_type TSRMLS_object*); cuba *ce, zval *objek, int by_ref TSRMLS_DC); int (*interface_gets_implemented)(zend_class_entry *iface, zend_class_entry *class_type TSRMLS_DC); /* kelas melaksanakan antara muka ini */ union _zend_function *(*get_static_method)(zend_class_entry *ce, char* method, int method_len TSRMLS_DC); /* panggil balik serializer */ int (*serialize)(zval *objek, unsigned char **buffer, zend_uint *buf_len, zend_serialize_data *data TSRMLS_DC); int (*unserialize)(zval **objek, zend_class_entry *ce, const unsigned char *buf, zend_uint buf_len, zend_unserialize_data *data TSRMLS_DC); zend_class_entry **antara muka; zend_uint num_interfaces; zend_class_entry **sifat; zend_uint num_traits; zend_trait_alias **trait_aliases; zend_trait_precedence **trait_precedences; kesatuan ( struct ( const char *nama fail; zend_uint line_start; zend_uint line_end; const char *doc_comment; zend_uint doc_comment_len; ) pengguna; struct ( const struct _zend_function_entry *builtin_functions; struct _zend_module_en ) internal );
Saiz struktur, berdasarkan model LP64, adalah 568 bait. Iaitu, setiap kali PHP mengisytiharkan kelas, ia terpaksa mencipta zend_class_entry menggunakan lebih daripada setengah kilobait memori timbunan sahaja. Sudah tentu, perkara ini tidak terhad kepada ini: seperti yang anda perhatikan, struktur itu mengandungi banyak petunjuk, yang juga perlu diperuntukkan dalam ingatan. Iaitu, kelas itu sendiri menggunakan lebih banyak memori daripada semua objek yang kemudiannya dicipta daripadanya.

Antara lain, kelas mengandungi atribut (statik dan dinamik) serta kaedah. Semua ini juga memerlukan ingatan. Apabila bercakap tentang kaedah, sukar untuk mengira pergantungan yang tepat di sini, tetapi satu perkara adalah benar: lebih besar badan kaedah, lebih besar OPArraynya, yang bermaksud lebih banyak memori yang digunakan. Tambahkan pada pembolehubah statik yang boleh diisytiharkan dalam kaedah. Seterusnya datang sifat-sifat, kemudian ia juga akan diletakkan dalam ingatan. Jumlahnya bergantung pada nilai lalainya: integer akan mengambil sedikit masa, tetapi tatasusunan statik yang besar akan memakan banyak memori.

Satu lagi perkara penting yang perlu diketahui tentang zend_class_entry ialah komen PHP. Ia juga dikenali sebagai anotasi. Ini adalah pembolehubah rentetan (dalam C, char* buffer), yang juga perlu diletakkan dalam ingatan. Untuk C, yang tidak menggunakan Unicode, tidak seperti PHP, peraturannya sangat mudah: satu aksara = satu bait. Lebih banyak anotasi yang anda ada dalam kelas, lebih banyak memori akan digunakan selepas menghuraikan.

Medan doc_comment zend_class_entry mengandungi anotasi kelas. Kaedah dan atribut juga mempunyai medan ini.

Kelas adat dan dalaman

Kelas tersuai ialah kelas yang ditakrifkan dengan PHP, manakala kelas dalaman ditakrifkan sama ada dengan menyuntik kod sumber ke dalam PHP itu sendiri atau dengan menggunakan sambungan. Perbezaan terbesar antara kedua-dua jenis kelas ini ialah kelas yang ditentukan pengguna beroperasi pada memori yang diperuntukkan permintaan, manakala kelas dalaman beroperasi pada memori "kekal".

Ini bermakna apabila PHP selesai memproses permintaan HTTP semasa, ia mengosongkan dan memusnahkan semua kelas pengguna sebagai persediaan untuk permintaan seterusnya. Pendekatan ini dikenali sebagai seni bina share nothing. Ini telah dibina ke dalam PHP sejak awal lagi, dan belum ada rancangan untuk mengubahnya.

Jadi, setiap kali permintaan dibentuk dan kelas dihuraikan, memori diperuntukkan untuk mereka. Selepas kelas digunakan, semua yang berkaitan dengannya dimusnahkan. Jadi pastikan anda menggunakan semua kelas yang diisytiharkan, jika tidak, memori akan terbuang. Gunakan pemuat auto, mereka menangguhkan penghuraian/pengisytiharan pada masa berjalan apabila PHP perlu menggunakan kelas. Walaupun memperlahankan pelaksanaan, autoloader menggunakan memori dengan baik kerana ia tidak akan berjalan sehingga kelas benar-benar memerlukannya.

Perkara berbeza dengan kelas dalaman. Ia disimpan secara kekal dalam ingatan, sama ada ia digunakan atau tidak. Iaitu, mereka hanya dimusnahkan apabila PHP itu sendiri berhenti berfungsi - selepas semua permintaan telah diproses (bermaksud SAPI web, sebagai contoh, PHP-FPM). Oleh itu, kelas dalaman lebih cekap daripada kelas pengguna (hanya atribut statik dimusnahkan pada akhir permintaan, tiada yang lain).

Jika (EG (full_tables_cleanup)) (zend_hash_reverse_apply (EG (function_table), (apply_func_t) clean_non_persistent_function_full TSRMLS_CC); zend_hash_reverse_apply (EG (class_table), (apply_func_t) clean_non_persistentRM_LS_verse_table_c) (apply_func_t) clean_non_persistentRM_LS_verse_full) (apply_non_persistentRM_LS_verse_table_c) (apply_non_persistentRM_LS_verse_table) (apply_non_persistentRMLS_verse_table) TSRMLS_CC); zend_hash_reverse_apply(EG(class_table), (apply_func_t) clean_non_persistent_class TSRMLS_CC);)
Ambil perhatian bahawa walaupun dengan caching opcode seperti OPCache, kelas dibuat dan dimusnahkan pada setiap permintaan, seperti yang berlaku dengan kelas tersuai. OPCache hanya mempercepatkan kedua-dua proses ini.

Seperti yang anda lihat, jika anda mengaktifkan banyak sambungan PHP, setiap satunya mengisytiharkan banyak kelas, tetapi hanya menggunakan sebilangan kecil daripadanya, maka memori akan terbuang. Ingat bahawa sambungan PHP mengisytiharkan kelas pada masa permulaan PHP, walaupun permintaan seterusnya tidak akan menggunakan kelas tersebut. Oleh itu, tidak disyorkan untuk memastikan sambungan aktif jika ia tidak digunakan pada masa ini, jika tidak, anda akan membazirkan memori. Terutama jika sambungan ini mengisytiharkan banyak kelas - walaupun ia boleh menyumbat memori dengan sesuatu yang lain.

Kelas, antara muka atau sifat - tidak mengapa

PHP menggunakan struktur yang sama untuk mengurus kelas, antara muka dan sifat - zend_class_entry . Dan seperti yang telah anda lihat, struktur ini agak rumit. Kadangkala pembangun mengisytiharkan antara muka dalam kod untuk dapat menggunakan nama mereka dalam blok tangkapan. Ini membolehkan anda menangkap hanya jenis pengecualian tertentu. Sebagai contoh, seperti ini:

Antara muka BarException ( ) kelas MyException memanjangkan Exception melaksanakan BarException ( ) cuba ( $foo->bar(): ) catch (BarException $e) ( )
Ia tidak terlalu baik bahawa 912 bait digunakan di sini hanya untuk mengisytiharkan antara muka BarException.

$kelas =<<<"CL" interface Bar { } CL; $m = memory_get_usage(); eval($class); echo memory_get_usage() - $m . "\n"; /* 912 bytes */
Saya tidak mahu mengatakan bahawa ini buruk atau bodoh, saya tidak cuba menyalahkan sesiapa atau apa-apa. Saya hanya menarik perhatian anda pada saat ini. Dari segi struktur dalaman PHP, kelas, antara muka dan sifat digunakan dengan cara yang sama. Anda tidak boleh menambah atribut pada antara muka, penghurai atau pengkompil tidak akan membenarkannya. Walau bagaimanapun, struktur zend_class_entry tidak akan ke mana-mana, cuma beberapa medan, termasuk static_members_table , tidak akan menjadi penunjuk yang diperuntukkan memori. Mengisytiharkan kelas, sifat yang setara, atau antara muka yang setara akan memerlukan jumlah memori yang sama kerana kesemuanya menggunakan struktur yang sama.

mengikat kelas

Ramai pembangun tidak memikirkan tentang mengikat kelas sehingga mereka mula tertanya-tanya bagaimana perkara itu benar-benar berfungsi. Pengikatan kelas boleh diterangkan sebagai "proses di mana kelas itu sendiri dan semua data yang berkaitan dengannya disediakan untuk kegunaan penuh oleh pembangun." Proses ini sangat mudah dan tidak memerlukan banyak sumber jika kita bercakap tentang satu kelas yang tidak melengkapkan yang lain, tidak menggunakan ciri dan tidak melaksanakan antara muka. Proses mengikat untuk kelas tersebut berlaku sepenuhnya pada masa penyusunan, dan semasa pelaksanaan, sumber tidak lagi dibelanjakan untuk ini. Sila ambil perhatian bahawa kami bercakap tentang mengikat kelas yang diisytiharkan oleh pengguna. Untuk kelas dalaman, proses yang sama dilakukan apabila kelas didaftarkan dengan teras atau sambungan PHP, sejurus sebelum skrip pengguna dijalankan - dan ini hanya dilakukan sekali dalam seumur hidup PHP.

Perkara menjadi lebih rumit apabila ia datang untuk menyuntik antara muka atau mewarisi kelas. Kemudian, semasa mengikat kelas, semuanya disalin daripada objek induk dan anak (sama ada kelas atau antara muka).

/* Kelas tunggal */ kes ZEND_DECLARE_CLASS: jika (do_bind_class(CG(active_op_array), opline, CG(class_table), 1 TSRMLS_CC) == NULL) ( return; ) table = CG(class_table); pecah;
Dalam kes pengisytiharan kelas yang mudah, kami menjalankan do_bind_class() . Fungsi ini hanya mendaftarkan kelas yang layak sepenuhnya dalam jadual kelas untuk kegunaan kemudian pada masa jalan, dan juga menyemak kemungkinan kaedah abstrak:

batalkan zend_verify_abstract_class(zend_class_entry *ce TSRMLS_DC) ( zend_abstract_info ai; jika ((ce->ce_flags & ZEND_ACC_IMPLICIT_ABSTRACT_CLASS) && !(ce->ce_flags & ZEND_ACC_EXPLIC_ASS_ABSTR_saiz(>memfaksi_CLASS_ABSTRA)(membuat_jadual&0STR) , (apply_func_arg_t) zend_verify_abstract_class_function, &ai TSRMLS_CC); jika (ai.cnt) ( zend_error(E_ERROR, "Kelas %s mengandungi %d kaedah abstrak%s dan oleh itu mesti diisytiharkan abstrak atau melaksanakan kaedah yang selebihnya (" MAX_ABSTRFACT_IN_FOAB_INFACT_MAX_ABSTRFACT_IN_AB_FACT ", ce->name, ai.cnt, ai.cnt > 1 ? "s" : "", DISPLAY_ABSTRACT_FN(0), DISPLAY_ABSTRACT_FN(1), DISPLAY_ABSTRACT_FN(2)); ) ) )
Tiada apa-apa untuk ditambah di sini, kes mudah.

Apabila mengikat kelas yang melaksanakan antara muka, anda perlu melakukan perkara berikut:

  • Semak sama ada antara muka sudah diisytiharkan.
  • Semak sama ada kelas yang dikehendaki adalah benar-benar kelas, dan bukan antara muka itu sendiri (seperti yang dinyatakan di atas, dari sudut pandangan struktur dalaman, ia disusun sama).
  • Salin pemalar dari antara muka ke kelas, semak kemungkinan perlanggaran.
  • Salin kaedah daripada antara muka ke kelas, menyemak kemungkinan perlanggaran dan ketidakkonsistenan dalam perisytiharan (contohnya, menukar kaedah antara muka kepada kaedah statik dalam kelas kanak-kanak).
  • Tambahkan antara muka dan semua antara muka induk yang mungkin pada senarai antara muka yang dilaksanakan oleh kelas.
Dengan "salinan" dimaksudkan bukan salinan dalam penuh. Untuk pemalar, atribut dan fungsi, satu demi satu, pengiraan semula dijalankan, berapa banyak entiti dalam ingatan menggunakannya.

ZEND_API batal zend_do_implement_interface(zend_class_entry *ce, zend_class_entry *iface TSRMLS_DC) ( /* ... */ ) else ( if (ce->num_interfaces >= current_iface_num) ( if (ce->type == ZEND_INTERNAL_CLASS) ( ce ->interfaces = (zend_class_entry **) realloc(ce->interfaces, sizeof(zend_class_entry *) * (++current_iface_num)); ) else ( ce->interfaces = (zend_class_entry **) eralloc(ce->interfaces, sizeof (zend_class_entry * ) * (++current_iface_num)); ) ) ce->antara muka = ​​iface; zend_hash_merge_ex(&ce->constants_table, &iface->constants_table, (copy_ctor_func_t) zval_add_ref, sizeof(zval *), (merge_checker_funccheck_t) do_inherit_constant
Beri perhatian kepada perbezaan antara kelas dalaman dan pengguna. Yang pertama akan menggunakan realloc() untuk peruntukan memori, yang terakhir akan menggunakan eralloc() . realloc() memperuntukkan memori "kekal", manakala eralloc() beroperasi pada memori "diperuntukkan atas permintaan".

Anda boleh melihat bahawa apabila dua jadual malar (antara muka-1 dan kelas-1) digabungkan, mereka melakukannya menggunakan panggilan balik zval_add_ref. Ia tidak menyalin pemalar dari satu jadual ke jadual lain, tetapi berkongsi penunjuknya, hanya menambah bilangan rujukan.

Untuk setiap jadual fungsi (kaedah), do_inherit_method digunakan:

Statik void do_inherit_method(zend_function *function) ( function_add_ref(function); ) ZEND_API void function_add_ref(zend_function *function) ( jika (function->type == ZEND_USER_FUNCTION) ( zend_op_array *op_array = &function->op_array;->ref )++; jika (op_array->static_variables) ( HashTable *static_variables = op_array->static_variables; zval *tmp_zval; ALLOC_HASHTABLE(op_array->static_variables); zend_hash_init(op_array->static_variables_static_zendVAL_variables, NUD_static_variables, NUD_static_variables ); zend_hash_copy(op_array->static_variables, static_variables, (copy_ctor_func_t) zval_add_ref, (void *) &tmp_zval, sizeof(zval *)); ) op_array->run_time_cache = NULL; ) )
Kiraan semula ditambahkan pada OPArray fungsi, dan semua pembolehubah statik yang mungkin diisytiharkan dalam fungsi (ini adalah kaedah) disalin menggunakan zval_add_ref . Oleh itu, keseluruhan proses penyalinan memerlukan banyak sumber pengkomputeran, kerana terdapat banyak gelung dan semakan yang terlibat. Tetapi ingatan digunakan sedikit. Malangnya, pengikatan antara muka adalah masa jalan sepenuhnya hari ini, dan anda akan merasakannya pada setiap permintaan. Mungkin pemaju akan mengubahnya tidak lama lagi.

Bagi warisan, di sini, pada dasarnya, semuanya sama seperti ketika melaksanakan antara muka. Hanya lebih ramai "peserta" yang terlibat. Tetapi saya ingin ambil perhatian bahawa jika PHP sudah mengetahui tentang kelas, maka pengikatan dilakukan pada masa penyusunan, dan jika ia tidak tahu, maka pada masa larian. Jadi lebih baik untuk mengisytiharkan seperti ini:

/* baik */ kelas A ( ) kelas B memanjangkan A ( )
bukannya:

/* teruk */ kelas B memanjangkan A ( ) kelas A ( )
Dengan cara ini, prosedur mengikat kelas rutin boleh membawa kepada tingkah laku yang sangat pelik:

/* ini berfungsi */ kelas B memanjangkan A ( ) kelas A ( )

/* ini bukan */ Ralat maut: Kelas "B" tidak ditemui */ kelas C melanjutkan B ( ) kelas B melanjutkan A ( ) kelas A ( )

Dalam varian pertama, pengikatan kelas B ditangguhkan pada masa larian, kerana apabila pengkompil mencapai pengisytiharan kelas ini, ia belum mengetahui apa-apa tentang kelas A. Apabila pelaksanaan bermula, kelas A terikat tanpa persoalan, kerana ia sudah disusun, menjadi satu kelas. Dalam kes kedua, semuanya berbeza. Pengikatan kelas C ditangguhkan pada masa jalan kerana pengkompil belum mengetahui apa-apa tentang B apabila cuba menyusunnya. Tetapi apabila kelas C terikat pada masa larian, ia mencari B, yang tidak wujud kerana ia tidak disusun kerana B ialah penambahan. Mesej "Kelas B tidak wujud" keluar.

Objek

Jadi sekarang kita tahu bahawa:
  • Kelas mengambil banyak ingatan.
  • Kelas dalaman jauh lebih baik dioptimumkan daripada kelas pengguna kerana kelas yang terakhir mesti dibuat dan dimusnahkan pada setiap permintaan. Kelas dalaman wujud sepanjang masa.
  • Kelas, antara muka dan ciri menggunakan struktur dan prosedur yang sama, perbezaannya sangat kecil.
  • Semasa pewarisan atau pengisytiharan, proses pengikatan adalah intensif CPU dan memakan masa, tetapi penggunaan memori adalah rendah kerana banyak perkara tidak diduplikasi tetapi dikongsi. Selain itu, adalah lebih baik untuk menjalankan pengikatan kelas pada masa penyusunan.

Sekarang mari kita bercakap tentang objek. Bab pertama menunjukkan bahawa penciptaan objek "klasik" (kelas yang ditentukan pengguna "klasik") memerlukan memori yang sangat sedikit, kira-kira 200 bait. Ini semua tentang kelas. Penyusunan kelas selanjutnya juga menggunakan memori, tetapi ini adalah lebih baik, kerana lebih sedikit bait diperlukan untuk mencipta objek tunggal. Malah, objek itu adalah satu set kecil struktur kecil.

Menguruskan Kaedah Objek

Pada peringkat enjin, kaedah dan fungsi adalah satu dan sama - struktur zend_function_structure. Hanya nama yang berbeza. Kaedah-kaedah tersebut disusun dan ditambah pada atribut function_table dalam zend_class_entry . Oleh itu, setiap kaedah dibentangkan semasa runtime, ia hanya soal menterjemahkan penunjuk kepada pelaksanaan.

Typedef _zend_function kesatuan (jenis zend_uchar; struct (jenis zend_uchar; char const * FUNCTION_NAME; zend_class_entry * skop; fn_flags zend_uint; kesatuan _zend_function * prototaip; num_args zend_uint; required_num_args zend_uint; * zend_arg_info arg_info;) bersama; op_array zend_op_array; zend_internal_function internal_function;) zend_function ;
Apabila objek cuba memanggil kaedah, enjin lalai melihat dalam jadual nilai untuk fungsi kelas objek. Jika kaedah tidak wujud, maka __call() dipanggil. Keterlihatan juga disemak - awam/dilindungi/swasta - bergantung pada tindakan berikut yang diambil:

Kesatuan statik _zend_function *zend_std_get_method(zval **object_ptr, char *method_name, int method_len, const zend_literal *key TSRMLS_DC) ( zend_function *fbc; zval *objek = *objek_ptr; zend_objek *zobj = Zob_nilai *zobj = Zob_nilai_zobj lc_method_name; ALLOCA_FLAG(use_timbunan) jika (EXPECTED(key!= NULL)) ( lc_method_name = Z_STRVAL(key->constant); hash_value = key->hash_value; ) else ( lc_method_name = do_alloca(method_len+1, use_tolo_copy_len+1, zenwerdhea_copy); lc_method_name, method_name, method_len); hash_value = zend_hash_func(lc_method_name, method_len+1); ) /* Jika kaedah tidak ditemui */ jika (TIDAK DIJANGKA(zend_hash_quick_find(&zobj->ce->function_table, lc_len_value_name, kaedah_nilai_hash, kaedah_nilai_hash, hash , (void **)&fbc) == FAILURE)) ( if (UNEXPECTED(!key)) ( free_alloca(lc_method_name, use_heap); ) if (zobj->ce->__call) ( /* jika kelas telah mendapat __call() pengendali */ return zend_get_user_call_function(zobj->ce, method_name, method_len); /* panggil __call() pengendali */ ) else ( return NULL; /* else return NULL, yang berkemungkinan akan membawa kepada ralat maut: method not found */ ) ) /* Semak tahap akses */ if (fbc->op_array.fn_flags & ZEND_ACC_PRIVATE) ( zend_function *updated_fbc; updated_fbc = zend_check_private_int(fbc , Z_OBJ_HANDLER_P(objek, get_class_entry)(objek TSRMLS_CC), lc_method_name, method_len, hash_value TSRMLS_CC); if (EXPECTED(updated_fbc != NULL)) ( fbc = updated_fbc; ) else ( if (zobj->ce->__call) = zend_get_user_call_function(zobj->ce, method_name, method_len); ) else ( zend_error_noreturn(E_ERROR, "Panggil ke %s kaedah %s::%s() daripada konteks "%s"", zend_visibility_string(fbc->common.fn_flags ), ZEND_FN_SCOPE_NAME(fbc), method_name, EG(skop) ? EG(skop)->nama: ""); ) ) ) lain ( /* ... ... */ )
Anda mungkin melihat satu perkara yang menarik, lihat pada baris pertama:

Jika (EXPECTED(key!= NULL)) ( lc_method_name = Z_STRVAL(key->constant); hash_value = key->hash_value; ) else ( lc_method_name = do_alloca(method_len+1, use_heap); /* Buat zend_copy_str_tolower(dest, src, src_length); */ zend_str_tolower_copy(lc_method_name, method_name, method_len); hash_value = zend_hash_func(lc_method_name, method_len+1); )
Ini adalah manifestasi ketidakpekaan huruf PHP. Sistem mesti terlebih dahulu menggunakan huruf kecil setiap fungsi (zend_str_tolower_copy()) sebelum memanggilnya. Bukan semua orang, tetapi yang terdapat pernyataan if. Pembolehubah utama menghalang pelaksanaan fungsi huruf kecil (bahagian lain) - ini adalah sebahagian daripada pengoptimuman PHP yang dilaksanakan dalam versi 5.4. Jika panggilan kaedah tidak dinamik, maka pengkompil telah pun mengira key , dan kurang sumber yang terbuang pada masa jalan.

Kelas Foo ( fungsi awam BAR() ( ) ) $a = Foo baharu; $b = "bar"; $a->bar(); /* panggilan statik: baik */ $a->$b(); /* panggilan dinamik: buruk */
Semasa penyusunan fungsi/kaedah, ia segera ditukar kepada huruf kecil. Fungsi BAR() di atas ditukar kepada bar() oleh pengkompil apabila kaedah itu ditambahkan pada kelas dan jadual fungsi.

Dalam contoh di atas, panggilan pertama adalah statik: pengkompil telah mengira kunci untuk rentetan "bar", dan apabila tiba masanya untuk memanggil kaedah, ia mempunyai lebih sedikit kerja untuk dilakukan. Panggilan kedua sudah dinamik, pengkompil tidak tahu apa-apa tentang "$b", ia tidak boleh mengira kunci untuk memanggil kaedah. Kemudian, pada masa jalanan, kita perlu menukar rentetan kepada huruf kecil dan mengira cincangnya (zend_hash_func ()), yang tidak mempunyai kesan terbaik pada prestasi.

Setakat __call() berkenaan, ia bukanlah pencapaian yang banyak. Walau bagaimanapun, dalam kes ini, lebih banyak sumber terbuang daripada semasa memanggil fungsi sedia ada.

Menguruskan Atribut Objek

Inilah yang berlaku:

Seperti yang anda lihat, apabila berbilang objek dari kelas yang sama dicipta, enjin mengubah hala setiap atribut ke penunjuk yang sama seperti dalam kes atribut kelas. Sepanjang hayatnya, kelas menyimpan bukan sahaja atribut statiknya sendiri, tetapi juga atribut objek. Dalam kes kelas dalaman, sepanjang masa PHP berjalan. Mencipta objek tidak melibatkan penciptaan atributnya, jadi ini adalah pendekatan yang agak cepat dan menjimatkan. Hanya apabila objek akan menukar salah satu atributnya, enjin mencipta yang baharu untuk berbuat demikian, dengan mengandaikan anda menukar atribut $a bagi Foo #2:

Jadi apabila kita mencipta objek, kita "hanya" mencipta struktur zend_object 32-bait:

Typedef struct _zend_object ( zend_class_entry *ce; HashTable *properties; zval **properties_table; HashTable *guards; /* melindungi daripada __get/__set ... rekursi */ ) zend_object;
Struktur ini ditambah pada stor objek. Dan itu, seterusnya, adalah struktur zend_object_store. Ini ialah pendaftaran objek global enjin Zend - tempat di mana semua objek dikumpulkan dan disimpan dalam satu contoh:

ZEND_API zend_object_value zend_objects_new(zend_object **objek, zend_class_entry *class_type TSRMLS_DC) ( zend_object_value retval; *objek = emalloc(sizeof(zend_object);(*objek)->ce = class_object =NUIP;>ce = class_object *objek)->properties_table = NULL; (*objek)->guards = NULL; /* Tambahkan objek ke dalam stor */ retval.handle = zend_objects_store_put(*objek, (zend_objects_store_dtor_t) zend_objects_destroy_object, (zend_objects_object_free_object) .pengendali = &std_object_handlers; kembalikan semula; )
Seterusnya, enjin mencipta vektor ciri objek kami:

ZEND_API batal object_properties_init(zend_object *objek, zend_class_entry *class_type) ( int i; if (class_type->default_properties_count) ( object->properties_table = emalloc(sizeof(zval*) * class_type->default_properties =_count);< class_type->default_properties_count; i++) ( object->properties_table[i] = class_type->default_properties_table[i]; if (class_type->default_properties_table[i]) ( #if ZTS ALLOC_ZVAL(objek->properties_table[i]); MAKE_COPY_ZVAL(&class_jenis->default_properties [i], object->properties_table[i]); #else Z_ADDREF_P(object->properties_table[i]); #endif ) ) object->properties = NULL; ) )
Seperti yang anda lihat, kami telah memperuntukkan jadual/vektor dalam ingatan (seperti dalam bahasa C) untuk zval* , berdasarkan sifat yang diisytiharkan bagi kelas objek. Dalam kes PHP bukan thread-safe, kami hanya menambah pengiraan semula pada atribut, dan jika thread-safe Zend (ZTS, Zend thread safety) digunakan, maka kami perlu menyalin sepenuhnya zval . Ini adalah salah satu daripada banyak contoh yang menunjukkan prestasi lemah dan penggunaan sumber mod ZTS yang tinggi berbanding PHP bukan ZTS.

Anda mungkin mempunyai dua soalan:

  • Apakah perbezaan antara properties_table dan properties dalam struktur zend_object?
  • Jika kita meletakkan atribut objek kita dalam vektor-C, bagaimana kita mendapatkannya kembali? Lihat vektor setiap kali (yang menjejaskan prestasi)?

Kedua-dua soalan dijawab oleh zend_property_info .

Typedef struct _zend_property_info ( zend_uint flags; const char *name; int name_length; ulong h; int offset; const char *doc_comment; int doc_comment_len; zend_class_entry *ce; ) zend_property_info;
Setiap diisytiharkan atribut (property) objek kami mempunyai maklumat harta yang sepadan ditambah pada medan property_info dalam zend_class_entry . Ini dilakukan pada masa penyusunan untuk atribut yang diisytiharkan dalam kelas:

Kelas Foo ( awam $a = "foo"; dilindungi $b; persendirian $c; ) struct _zend_class_entry ( /* ... ... */ HashTable function_table; HashTable properties_info; /* berikut ialah maklumat sifat tentang $a, $b dan $c */ zval **default_properties_table; /* dan di sini, kita akan menemui $a, $b dan $c dengan nilai lalai */ int default_properties_count; /* ini akan mempunyai nilai 3: 3 sifat */ /* ... ... */
properties_infos ialah jadual yang memberitahu objek tentang kewujudan atribut yang diminta. Dan jika ia wujud, ia melepasi nombor indeksnya dalam tatasusunan object->properties. Kemudian kami menyemak keterlihatan dan akses kepada skop (awam/dilindungi/swasta).

Jika atribut tidak wujud dan kita perlu menulis kepadanya, maka kita boleh cuba memanggil __set() . Sekiranya berlaku kegagalan, kami mencipta atribut dinamik yang akan disimpan dalam medan object->property_table.

Property_info = zend_get_property_info_quick(zobj->ce, ahli, (zobj->ce->__set != NULL), kunci TSRMLS_CC); jika (JANGKAAN(info_harta != NULL) && ((JANGKAAN((info_harta->bendera & ZEND_ACC_STATIC)) == 0) && info_harta->offset >= 0) ?(zobj->sifat ? ((pembolehubah_ptr = (zval**) )zobj->properties_table) != NULL) : (*(variable_ptr = &zobj->properties_table) != NULL)) : (EXPECTED(zobj->properties != NULL) && EXPECTED(zend_hash_quick_find(zobj->properties, property_info >nama, info_properti->panjang_nama+1, info_properti->h, (kosong **) &variable_ptr) == SUCCESS)))) ( /* ... ... */ ) else ( zend_guard *guard = NULL; if (zobj->ce->__set && /* kelas mempunyai __set() ? */ zend_get_property_guard(zobj, property_info, ahli, &guard) == SUCCESS && !guard->in_set) ( Z_ADDREF_P(objek); if (PZVAL_IS_REF( objek)) ( SEPARATE_ZVAL(&objek); ) guard->in_set = 1; /* halang tetapan bulat */ jika (zend_std_call_setter(objek, ahli, nilai TSRMLS_CC) != SUCCESS) ( /* call __set() */ ) guard ->in_set = 0; zval_ptr_dtor(&objek); /* ... ... */
Selagi anda tidak menulis pada objek, penggunaan memorinya tidak berubah. Selepas ditulis, ia mengambil lebih banyak ruang (sehingga ia dimusnahkan), kerana ia mengandungi semua atribut yang ditulis kepadanya.

Objek berkelakuan seperti pautan terima kasih kepada penyimpanan objek

Objek bukan pautan. Ini ditunjukkan dengan skrip kecil:

Fungsi foo($var) ( $var = 42; ) $o = MyClass baharu; foo($o); var_dump($o); /* ini masih objek, bukan integer 42 */
Semua orang kini akan mengatakan bahawa "dalam PHP 5, objek adalah rujukan," malah manual rasmi menyebut ini. Secara teknikal, ini benar-benar palsu. Walau bagaimanapun, objek boleh berkelakuan dengan cara yang sama seperti rujukan. Sebagai contoh, apabila anda menghantar pembolehubah yang merupakan objek kepada fungsi, fungsi itu boleh mengubah suai objek yang sama.

Ini kerana zval yang diluluskan sebagai fungsi tidak melepasi objek itu sendiri, tetapi pengecam uniknya digunakan untuk mencari stor objek kongsi. Dan hasilnya adalah sama. Adalah mungkin untuk memperuntukkan tiga zval berbeza dalam ingatan, dan semuanya boleh mengandungi deskriptor objek yang sama.

Objek(Kelas Saya)#1 (0) ( ) /* #1 ialah pemegang objek (nombor), ia unik */

Zend_object_store menyediakan storan objek sekali sahaja. Satu-satunya cara untuk menulis ke kedai ialah mencipta objek baharu dengan kata kunci baharu, fungsi unserialize(), API pantulan atau kata kunci klon. Tiada operasi lain akan membenarkan anda menduplikasi atau mencipta objek baharu dalam repositori.

Typedef struct _zend_objects_store ( zend_object_store_bucket *object_buckets; zend_uint top; saiz zend_uint; int free_list_head; ) zend_objects_store; typedef struct _zend_object_store_bucket (zend_bool destructor_called; zend_bool sah; apply_count zend_uchar; kesatuan _store_bucket (struct _store_object (tidak sah * objek; dtor zend_objects_store_dtor_t; FREE_STORAGE kosong zend_objects_free_object_storage_t; klon zend_objects_store_clone_t; zend_object_handlers const * Pengendali; refcount zend_uint; gc_root_buffer * buffered;) obj; struct (int seterusnya; ) senarai_percuma; ) baldi; ) zend_object_store_bucket;

Apakah $ini?

Memahami struktur $this tidaklah terlalu sukar, tetapi terdapat kepingan kod yang dikaitkan dengan alat ini di beberapa tempat dalam enjin: dalam pengkompil, dalam kod untuk mendapatkan pembolehubah semasa runtime, dan sebagainya. $ini muncul dan hilang seperti yang diperlukan, secara automatik menyerahkan dirinya kepada objek semasa - secara umum, perkara "ajaib". Dan kod dalaman dengan sempurna membolehkan ia dikawal.

Pertama, pengkompil tidak akan membenarkan penulisan ke $this . Untuk melakukan ini, ia akan menyemak setiap tugasan yang anda buat, dan jika ia menemui tugasan kepada $this , ralat maut akan dilemparkan.

/* ... ... */ jika (opline_is_fetch_this(last_op TSRMLS_CC)) ( zend_error(E_COMPILE_ERROR, "Tidak boleh menetapkan semula $ini"); ) /* ... ... */ zend_bool statik opline_is_fetch_this(const zend_op *opline TSRMLS_DC) ( jika ((opline->opcode == ZEND_FETCH_W) && (opline->op1_type == IS_CONST) && (Z_TYPE(CONSTANT(opline->op1.constant)) == IS_STRING) && ((opline-> nilai_lanjutan & ZEND_FETCH_STATIC_MEMBER) != ZEND_FETCH_STATIC_MEMBER) && (Z_HASH_P(&CONSTANT(opline->op1.constant)) == THIS_HASHVAL) && (Z_STRLEN(CONSTANT(opline->op1.constant))" == 1)) && !memcmp(Z_STRVAL(CONSTANT(opline->op1.constant)), "this", sizeof("this"))) ( return 1; ) else ( return 0; ) )
Bagaimanakah $ini diuruskan? Penggunaannya hanya boleh dilakukan di dalam kaedah, di mana pengkompil menjana OPCode INIT_METHOD_CALL semasa panggilan. Enjin mengetahui siapa yang memanggil kaedah, dalam kes $a->foo() ia adalah $a . Selepas itu, nilai $a diambil dan disimpan dalam ruang kongsi. Seterusnya, kaedah dipanggil menggunakan OPCode DO_FCALL . Pada ketika ini, nilai yang disimpan diambil semula (objek memanggil kaedah) dan diberikan kepada penunjuk $this dalaman global - EG(This) .

Jika (fbc->taip == ZEND_USER_FUNCTION || fbc->common.scope) ( should_change_scope = 1; EX(current_this) = EG(This); EX(current_scope) = EG(skop); EX(current_called_scope) = EG( called_scope); EG(This) = EX(objek); /* ambil objek yang disediakan dalam opcode INIT_METHOD sebelumnya dan kesannya kepada EG(This) */ EG(skop) = (fbc->type == ZEND_USER_FUNCTION || !EX (objek)) ?fbc->common.scope: NULL; EG(called_scope) = EX(call)->called_scope; )
Sekarang, apabila kaedah dipanggil, jika dalam badannya anda menggunakan $this untuk bertindak pada pembolehubah atau memanggil kaedah (cth $this->a = 8), ia akan menghasilkan OPCode ZEND_ASSIGN_OBJ , yang seterusnya akan mengambil semula $ini daripada EG(This) .

Zval zend_always_inline statik **_get_obj_zval_ptr_ptr_unused(TSRMLS_D) ( if (EXPECTED(EG(This) != NULL)) ( return &EG(This); ) else ( zend_error_noreturn(E_ERROR, "Menggunakan $this apabila tidak dalam konteks objek"); NULL; ))
Sekiranya anda menggunakan $this untuk memanggil kaedah (contohnya, $this->foo()) atau menghantarnya ke panggilan fungsi lain ($this->foo($this);), maka enjin akan cuba untuk ekstrak $ini daripada jadual aksara semasa, seperti yang dilakukannya untuk setiap pembolehubah piawai. Tetapi di sini, penyediaan khas dibuat semasa penciptaan bingkai tindanan fungsi semasa:

Jika (op_array->this_var != -1 && EG(This)) ( Z_ADDREF_P(EG(This)); if (!EG(active_symbol_table)) ( EX_CV(op_array->this_var) = (zval **) EX_CV_NUM(execute_data , op_array->last_var + op_array->this_var); *EX_CV(op_array->this_var) = EG(This); ) else ( if (zend_hash_add(EG(active_symbol_table), "this", sizeof("this"), &EG (This), sizeof(zval *), (void **) EX_CV_NUM(execute_data, op_array->this_var))==FAILURE) ( Z_DELREF_P(EG(This)); ) ) )
Apabila kita memanggil kaedah, enjin menukar skop:

Jika (fbc->taip == ZEND_USER_FUNCTION || fbc->common.scope) ( /* ... ... */ Cth(skop) = (fbc->type == ZEND_USER_FUNCTION || !EX(objek)) ?fbc->common.scope: NULL; /* ... ... */ )
EG(skop) adalah jenis zend_class_entry . Ini ialah kelas yang memiliki kaedah yang anda minta. Dan ia akan digunakan untuk sebarang operasi pada objek yang akan anda lakukan dalam badan kaedah selepas pemeriksaan keterlihatan oleh enjin:

Statik zend_always_inline int zend_verify_property_access(zend_property_info *property_info, zend_class_entry *ce TSRMLS_DC) ( suis (property_info->flags & ZEND_ACC_PPP_MASK) ( case ZEND_ACC_TEGED_PROCTED=(;_property_property_PUBLIC_DECTEDA):(;_property_property_PUBLICEND_PROCTED:(;_property_PROCED_DE_PROCTED: case ZEND_ACC_PROTYEND_PUBLIC: return_propertyEND_DECTEDA): (skop) || property_info->ce == EG(skop)) && EG(skop)) ( return 1; ) else ( return 0; ) break; ) return 0; )
Beginilah cara anda boleh mengakses ahli peribadi objek yang bukan milik anda tetapi kanak-kanak skop semasa anda:

Kelas A ( persendirian $a; fungsi awam foo(A $obj) ( $this->a = "foo"; $obj->a = "bar"; /* ya, ini mungkin */ ) ) $a = A baru; $b = baruA; $a->foo($b);
Ciri ini telah menyebabkan sejumlah besar laporan pepijat daripada pembangun. Tetapi ini adalah bagaimana model objek dalam PHP berfungsi - sebenarnya, kami menetapkan skop bukan berdasarkan objek, tetapi pada kelas. Dalam kes kelas "Foo" kami, anda boleh bekerja dengan mana-mana Foo peribadi mana-mana Foo lain, seperti yang ditunjukkan di atas.

Tentang pemusnah

Pemusnah adalah berbahaya, jangan bergantung pada mereka kerana PHP tidak memanggilnya walaupun pada ralat yang membawa maut:

Kelas Foo ( public function __destruct() ( echo "byebye foo"; ) ) $f = new Foo; thisfunctiondoesntexist(); /* ralat maut, fungsi tidak ditemui, pemusnah Foo TIDAK dijalankan */
Bagaimana pula dengan susunan pemusnah dipanggil jika mereka dipanggil juga? Jawapannya jelas kelihatan dalam kod:

Void shutdown_destructors(TSRMLS_D) ( zend_try ( int symbols; do ( symbols = zend_hash_num_elements(&EG(symbol_table)); zend_hash_reverse_apply(&EG(symbol_table), (apply_func_t) zval_call_destructor TSRMLS_s_table ! ); zend_objects_store_call_destructors(&EG(objects_store) TSRMLS_CC); ) zend_catch ( /* jika kita tidak dapat memusnahkan dengan bersih, tandakan semua objek sebagai musnah juga */ zend_objects_store_mark_destructed(&EG(objects_store) TSRMLS_end_call ) dalam TSRMLS_end_call static); (zval **zv TSRMLS_DC) ( jika (Z_TYPE_PP(zv) == IS_OBJECT && Z_REFCOUNT_PP(zv) == 1) ( kembalikan ZEND_HASH_APPLY_REMOVE; ) lain ( kembalikan ZEND_HASH_APPLY_KEEP; ) )
Ini menunjukkan tiga peringkat memanggil pemusnah:

  • Kitar melalui jadual simbol global dalam arah yang bertentangan dan panggil pemusnah untuk objek yang mempunyai pengiraan semula = 1.
  • Kemudian arah kitaran berubah, dan pemusnah dipanggil untuk semua objek lain, dengan pengiraan semula > 1.
  • Jika masalah berlaku pada salah satu peringkat sebelumnya, maka panggilan pemusnah yang tinggal terganggu.
Apakah yang membawa kepada:

Kelas Foo ( fungsi awam __destruct() ( var_dump("Foo musnah"); ) )
class Bar ( public function __destruct() ( var_dump("destroyed Bar"); ) )

Contoh satu:

$a = Foo baharu; $b = Bar baharu; "bar musnah" "foo musnah"
Contoh yang sama:

$a = Bar baharu; $b = Foo baharu; "Foo musnah" "Bar musnah"
Contoh kedua:

$a = Bar baharu; $b = Foo baharu; $c = $b; /* penambahan kiraan semula objek $b */ "Bar musnah" "Foo musnah"
Contoh tiga:

Class Foo ( public function __destruct() ( var_dump("destroyed Foo"); die();) ) /* perhatikan die() di sini */ class Bar ( public function __destruct() ( var_dump("destroyed Bar"); ) ) $a = Foo baharu; $a2 = $a; $b = Bar baharu; $b2 = $b; Foo
Prosedur ini dipilih atas sebab tertentu. Tetapi jika ia tidak sesuai dengan anda, maka lebih baik untuk memusnahkan objek anda sendiri. Ini adalah satu-satunya cara untuk mengawal panggilan __destruct(). Jika anda meninggalkan PHP untuk melakukannya untuk anda, maka jangan membenci hasil kerjanya kemudian. Anda sentiasa mempunyai pilihan untuk memusnahkan objek anda secara manual untuk mempunyai kawalan penuh ke atas pesanan.

PHP tidak memanggil pemusnah sekiranya berlaku sebarang ralat yang membawa maut. Hakikatnya ialah dalam kes ini Zend tidak stabil, dan memanggil pemusnah membawa kepada pelaksanaan kod pengguna, yang boleh mengakses petunjuk yang salah dan, akibatnya, PHP ranap. Adalah lebih baik untuk memastikan sistem stabil - itulah sebabnya panggilan pemusnah disekat. Mungkin sesuatu akan berubah dalam PHP 7.

Bagi rekursi, ia dilindungi dengan lemah dalam PHP, dan ini hanya terpakai untuk __get() dan __set() . Jika anda memusnahkan objek anda di suatu tempat dalam bingkai tindanan pemusnah, anda akan berakhir dalam gelung rekursif tak terhingga yang akan memakan semua sumber tindanan proses anda (biasanya 8 kB, ulimit -s) dan memecahkan PHP.

Class Foo ( public function __destruct() ( new Foo; ) /* anda akan ranap */ )
Ringkasnya, jangan percaya pemusnah untuk kod kritikal, seperti mengurus mekanisme kunci, kerana PHP mungkin tidak memanggil pemusnah atau memanggilnya dalam urutan yang tidak terkawal. Jika, bagaimanapun, kod penting diproses oleh pemusnah, maka sekurang-kurangnya mengawal kitaran hayat objek sendiri. PHP akan memanggil pemusnah apabila kiraan semula objek anda menurun kepada sifar, yang bermaksud objek itu tidak lagi digunakan dan boleh dimusnahkan dengan selamat.

Kesimpulan

Saya harap kini anda lebih memahami kerja harian anda dengan objek. Mereka tidak menggunakan banyak memori, dan pelaksanaannya di peringkat enjin dioptimumkan dengan baik. Cuba gunakan autoloader yang direka bentuk dengan baik untuk meningkatkan penggunaan memori. Isytiharkan kelas dalam susunan warisan logik, dan jika anda menukar yang paling kompleks kepada sambungan C, anda boleh mengoptimumkan banyak proses, malah meningkatkan prestasi keseluruhan kelas tersebut dengan lebih banyak lagi.

JavaScript disekat dalam penyemak imbas anda. Benarkan JavaScript untuk tapak ini berfungsi!

Pengaturcaraan objek

Pengaturcaraan Berorientasikan Objek (OOP) dalam PHP

Objek ialah satu set pembolehubah khas - harta benda dan fungsi khas - kaedah. Apa yang dipanggil pembolehubah dalam pengaturcaraan prosedur dipanggil harta dalam OOP. Apa yang dipanggil fungsi dalam pengaturcaraan prosedur dipanggil kaedah kelas dalam OOP. Objek yang dicipta daripada kelas dipanggil contoh kelas, atau hanya objek.

Mengakses sifat daripada kaedah hanya melalui kata kunci $this: $this->name; (perhatikan kekurangan tanda dolar di hadapan nama) Memanggil kaedah lain di dalam kaedah juga melalui $this: $this->foo(); Operator "->" digunakan untuk mengakses sifat dan kaedah objek: $this->name; (perhatikan kekurangan tanda dolar di hadapan nama)
Memanggil kaedah lain di dalam kaedah juga melalui $this: $this->foo(); . Objek dicipta menggunakan operator baru berdasarkan templat yang dipanggil kelas. Kelas ditakrifkan oleh kata kunci kelas.

Contoh 1

Kelas dengan harta dan kaedah ".$ini->nama."! Hey!"; ) function Bye($a) ( $this->name = $a; echo "

".$ini->nama."! selamat tinggal!

"; ) ) $obj = new classN1(); $obj->Hello(); $obj->name = "Misha"; $obj->Hello(); $obj->Bye("Yasha"); $ obj->Hello(); ?>

Akses pengubah suai dalam OOP:

  • awam- membolehkan anda mempunyai akses kepada sifat dan kaedah dari mana-mana sahaja (skop global)
  • dilindungi- akses kepada induk dan kelas yang diwarisi (skop kelas pengganti)
  • persendirian- akses hanya dari kelas di mana elemen itu sendiri diisytiharkan (skop kelas itu sendiri)
Kaedah lalai adalah awam. Sifat tidak mempunyai nilai pengubah suai secara lalai.

Pemalar kelas dalam OOP

const NAMA = 2; Dengan cara ini anda boleh mencipta pemalar di luar kelas. Ini ialah pemalar kelas, ia bukan milik mana-mana objek, ia adalah biasa kepada semua objek, jadi penggunaan di dalam kaedah: function printname()( echo self::NAME; ) self ialah kelas itu sendiri! Panggilan di luar kelas (boleh dipanggil dari skop global tanpa memulakan contoh kelas): echo OurClass::NAME;

ini dan diri sendiri

Pembolehubah khas digunakan di dalam kelas ini. Ia adalah penunjuk di mana objek boleh merujuk kepada dirinya sendiri.

Untuk mengakses kaedah statik, gunakan diri sendiri::

Kaedah Bye diluluskan hujah dengan cara yang sama seperti fungsi biasa. Apabila kaedah ini dipanggil, objek menukar sifat namanya.

CONTOH 1 KEPUTUSAN:

Masha! Hey!

Misha! Hey!

Yasha! selamat tinggal!

Yasha! Hey!

Pembina ialah kaedah yang dipanggil secara automatik apabila objek baharu dicipta: public function __construct()() . Apabila memulakan6 objek melalui binaan utiliti baharu, PHP mencari __construct dan, jika wujud, ia dipanggil.

Ia juga mungkin untuk mencipta kaedah yang namanya sama dengan nama kelas - kaedah sedemikian juga akan dianggap sebagai pembina. Pembina boleh mengambil hujah, yang sangat memudahkan kerja dengan kelas.

Contoh 2

Kelas dengan pembina nama = $a; ) fungsi Hello() ( echo "

".$ini->nama."! Hey!

"; ) ) $obj0 = new classN2(); $obj1 = new classN2("Misha"); $obj2 = new classN2("Masha"); $obj0->Hello(); $obj1->Hello(); $obj2->Hai(); ?>

CONTOH 2 KEPUTUSAN:

Seseorang di sana! Hey!

Misha! Hey!

Masha! Hey!

Dengan menggabungkan semua perkara di atas, anda boleh membuat kelas yang lebih bermakna. Sebagai contoh, kelas yang akan menyusun data dalam bentuk jadual dengan lajur bernama.

Contoh 3

Kelas meja tajuk = $headers; ) fungsi addRow ($row) ( $tmp = ; foreach ($this->headers as $header) ( if (! isset($row[$header])) $row[$header] = ""; $tmp = $row[$header]; ) array_push($this->
"; foreach($this->
"; foreach($this->
"; ) gema "
"; ) ) $test = new Table (array("a","b","c")); $test->addRow(array("a" =>1,"b" =>3,"c "=>2)); $test->addRow(array("b"=>1,"a"=>3)); $test->addRow(array("c"=>1,"b"= >3,"a" =>4)); $test->output(); ?>

Sifat kelas Jadual ialah tatasusunan nama lajur jadual dan tatasusunan dua dimensi baris data. Pembina kelas Jadual menerima tatasusunan nama lajur jadual. Kaedah addRow menambah baris data baharu pada jadual. Kaedah output memaparkan jadual pada skrin.

CONTOH 3 KEPUTUSAN:

a b c 1 3 2 3 1 4 3 1

Sifat dan kaedah tersembunyi

Sifat dan kaedah kelas boleh sama ada awam (awam) atau tersembunyi (peribadi). Sifat dan kaedah tersembunyi tidak boleh diakses dari luar kelas, i.e. daripada skrip yang menggunakan kelas ini, atau daripada kelas lain.

pusaka

Berdasarkan kelas sedia ada, anda boleh membuat kelas baharu menggunakan mekanisme pewarisan. Mekanisme pewarisan ialah penggunaan kelas yang ditakrifkan sebelum ini sebagai induk. Pada masa yang sama, set sifat dan kaedah kelas induk boleh dilanjutkan. Perlu diingat bahawa kelas terbitan hanya mempunyai satu induk.

Untuk mencipta kelas baharu yang mewarisi gelagat kelas sedia ada, gunakan kata kunci extends dalam pengisytiharannya. Sebagai contoh:

Kelas N2 memanjangkan kelasN1 ( ....... )

Di sini classN1 ialah kelas induk, classN2 ialah kelas terbitan.

Jika kelas terbitan tidak mengandungi pembinanya sendiri, maka pembina kelas induk digunakan semasa mencipta objeknya. Jika kelas terbitan mempunyai pembina sendiri, maka pembina kelas induk tidak dipanggil. Jika anda perlu memanggil pembina kelas induk, ini mesti dilakukan secara eksplisit. Sebagai contoh:

ClassN1::classN1();

Kelas terbitan akan mempunyai semua sifat dan kaedah kelas induk. Tetapi mereka boleh ditindih dalam kelas terbitan.

Contoh 4

Mengatasi kaedah kelas induk ".$ini->nama."! Hey!"; ) ) class classN4 memanjangkan classN3 ( function Hello() ( echo "

".$ini->nama."! Sungguh pertemuan!

"; ) ) $obj = new classN4(); $obj->Hello(); ?>

Kaedah Hello ditindih untuk kelas terbitan. Harta nama diwarisi daripada harta induk.

CONTOH 4 KEPUTUSAN:

Masha! Sungguh pertemuan!

Sejak PHP versi 4, adalah mungkin untuk memanggil kaedah kelas induk yang telah ditindih dalam objek kelas terbitan.

Contoh 5

Memanggil Kaedah Kelas Ibu Bapa ".$ini->nama."! Hey!"; ) fungsi Bye() ( echo "

".$this->name.", bye!

"; ) ) /** * Kelas kelasN6 */ kelas kelasN6 memanjangkan kelasN5 ( /** * */ fungsi Hello() ( echo "

".$ini->nama."! Sungguh pertemuan!

"; classN5::Hi(); ) ) $obj = new classN6(); $obj->Hi(); $obj->Bye(); ?>

HASIL CONTOH 5:

Masha! Sungguh pertemuan!

Masha! Hey!

Masha, selamat tinggal!

Jadi, kelas terbitan boleh mewarisi, mengatasi dan melanjutkan sifat dan kaedah kelas lain.

Contoh berikut mencipta kelas HTMLTable berdasarkan kelas Jadual dalam Contoh 3. Kelas baharu menjana data yang disimpan oleh kaedah addRow kelas induk dan mengeluarkannya ke jadual HTML. Sifat $cellpadding dan $bgcolor membenarkan argumen yang sepadan ditukar, dengan pembolehubah $cellpadding ditetapkan kepada nilai lalai 2.

Contoh 6

Kelas Jadual dan HTMLTable tajuk = $headers; ) fungsi addRow ($row) ( $tmp = ; foreach ($this->headers as $header) ( if (! isset($row[$header])) $row[$header] = ""; $tmp = $row[$header]; ) array_push ($this->data, $tmp); ) fungsi output () ( echo "
"; foreach ($this->headers sebagai $header) echo "$header "; echo "
"; foreach ($this->data as $y) ( foreach ($y as $x) echo "$x "; echo "
"; ) gema "
"; ) ) class HTMLTable memanjangkan Jadual ( public $cellpadding = "2"; public $bgcolor; function HTMLTable ($headers, $bg="FFFFFF") ( Tables::Tables($headers); $this->bgcolor = $bg; ) set fungsiCellpadding ($padding) ( $this->cellpadding = $padding; ) output fungsi () ( echo " pelapik sel.""> "; foreach ($this->headers as $header) echo " "; foreach ($y sebagai $x) gema "
bgcolor."">".$header; foreach ($this->data as $y) ( echo "
bgcolor."">$x"; ) echo "
"; ) ) $test = new HTMLTable (array("a","b","c"), "#00FFFF"); $test->setCellpadding (7); $test->addRow(array("a "=>1,"b" =>3,"c" =>2)); $test->addRow(array("b"=>1,"a" =>3)); $test->addRow (array("c" =>1,"b" =>3,"a" =>4)); $test->output(); ?>

Ambil perhatian bahawa nilai sifat cellpadding ditukar menggunakan kaedah setCellpadding yang berasingan. Sudah tentu, nilai harta boleh diubah secara langsung, di luar objek:

$test->cellpadding = 7 ;

Tetapi ini dianggap bentuk buruk, kerana. dalam objek kompleks, apabila salah satu sifat berubah, sifat lain juga boleh berubah.

HASIL CONTOH 6:

abc
1 3 2
3 1
4 3 1

Untuk menggunakan atau tidak menggunakan teknik pengaturcaraan objek? Di satu pihak, projek yang banyak menggunakan teknologi objek boleh mengambil terlalu banyak sumber pada masa jalan. Sebaliknya, pendekatan objek yang teratur dengan ketara akan mengurangkan masa pembangunan dan menjadikan program lebih fleksibel.

Memadam objek

Anda boleh memadam objek yang dibuat sebelum ini seperti berikut:

Unset($objName);

Berikut ialah contoh di mana objek kelas Kereta dicipta dan kemudian dipadamkan.

$myCar = Kereta baru; unset($myCar);

Selepas memanggil fungsi unset(), objek tidak lagi wujud. PHP mempunyai kaedah __destruct() khas yang dipanggil secara automatik apabila objek dimusnahkan. Di bawah ialah kelas yang mengandungi kaedah ini.

Jambatan Kelas ( function __destruct() ( echo "Jambatan musnah"; ) ) $bigBridge = Jambatan baharu; unset($bigBridge);

Apabila anda mencipta objek kelas Bridge dan kemudian memadamkannya, mesej berikut akan dipaparkan:

Jambatan musnah

Ia dipaparkan kerana panggilan kepada kaedah __destruct() apabila memanggil fungsi unset(). Apabila memadam objek, anda mungkin perlu menutup beberapa fail atau menulis maklumat ke pangkalan data.

Menyalin (pengklonan) objek

Mengklon objek:

$a = klon $b;

Pembina tidak dipanggil apabila diklon, kaedah sihir dipanggil __klon()() . Ia TIDAK mengambil hujah dan tidak boleh dipanggil sebagai kaedah.

Tukar objek kepada rentetan

Untuk menukar objek kepada rentetan dan sebaliknya, fungsi berikut digunakan:
bersiri()- mengambil objek dan mengembalikan perwakilan rentetan kelas dan sifatnya;
unserialize()- mengambil rentetan yang dibuat dengan serialize() dan mengembalikan objek.

serialize() dan unserialize() berfungsi pada semua jenis data, tetapi ia tidak berfungsi pada sumber.


Kaedah khas untuk melayan fungsi serialize() dan unserialize():
__tidur()- dipanggil dengan ketat sebelum objek disiri menggunakan fungsi serialize(). Fungsi __sleep() perlu mengembalikan senarai medan kelas yang fungsi serialize() akan disertakan dalam rentetan yang dikembalikan. Anda boleh menggunakan ini untuk menanggalkan medan yang tidak diingini daripada perwakilan rentetan objek. Sebagai contoh:

Fungsi awam __sleep() ( // bersihkan kembali array_keys(get_object_vars($this)); ) __bangun()- dipanggil serta-merta selepas objek dinyahsiri menggunakan unserialize().

Kelas abstrak

Kelas abstrak ialah kelas yang tidak boleh dilaksanakan, iaitu, anda tidak boleh mencipta objek kelas jika ia abstrak. Sebaliknya, anda membuat kelas kanak-kanak daripadanya dan secara senyap-senyap mencipta objek daripada kelas kanak-kanak tersebut. Kelas abstrak adalah templat untuk membuat kelas. kelas abstrak Orang ( swasta $firstName = ""; private $lastName = ""; public function setName($firstName, $lastName) ( $this->firstName = $firstName; $this->lastName = $lastName; ) fungsi awam getName() ( return "$this->firstName $this->lastName"; ) abstrak fungsi awam showWelcomeMessage(); /* showWelcomeMessage() kaedah abstrak Memandangkan ia abstrak, ia tidak mengandungi satu baris kod, ia hanya pengisytiharannya Mana-mana kelas kanak-kanak mesti menambah dan menerangkan kaedah showWelcomeMessage() */ )

Antara muka

Antara muka ialah templat yang menentukan tingkah laku satu atau lebih kelas. Berikut ialah perbezaan utama antara antara muka dan kelas abstrak:

  • Tiada kaedah boleh diisytiharkan dalam antara muka. Mereka semua abstrak. Kelas abstrak juga boleh mempunyai kaedah bukan abstrak.
  • Antara muka tidak boleh mengandungi medan - kaedah sahaja.
  • Kelas melaksanakan antara muka, dan kelas mewarisi atau melanjutkan kelas lain.
  • Kelas boleh melaksanakan berbilang antara muka pada masa yang sama. Kelas yang sama boleh mewarisi kelas lain. Tetapi kelas kanak-kanak hanya boleh mempunyai satu kelas super (abstrak atau tidak).
antara muka MyInterface ( public function aMethod(); public function anotherMethod(); ) class MyClass melaksanakan MyInterface ( public function aMethod() ( // (pelaksanaan kaedah) ) public function anotherMethod() ( // (pelaksanaan kaedah) ) )

Kaedah Pemintas (Kaedah Ajaib)

Contoh menggunakan sifat kelas yang tidak diisytiharkan

Di mana dan mengapa kaedah pemintas boleh digunakan?

Sebagai contoh, anda mempunyai jadual dalam pangkalan data yang dipanggil pengguna dan terdapat beberapa medan di dalamnya, contohnya id, nama, e-mel, telefon, kata laluan, avatar Dan anda mencipta kelas untuk bekerja dengan pengguna, jadi mereka memanggilnya - Pengguna

Apakah sifat yang akan dimiliki oleh kelas ini? Jika anda melakukan perkara yang sama seperti dalam pangkalan data - id, nama, e-mel, dan sebagainya, maka ternyata setiap kali anda menukar pangkalan data, anda perlu menukar kod dalam kelas Pengguna, yang tidak begitu mudah. Sebagai contoh, anda menambah medan tapak, yang bermaksud anda perlu menambahkannya pada kelas Pengguna dan seterusnya.
Menggunakan kaedah __get() dan __set(), anda boleh mengautomasikan semua ini. Anda tidak akan mempunyai satu harta pun daripada pangkalan data dalam kelas Pengguna sama sekali, kami hanya mempunyai satu $data - kami membawanya ke sana, dan memuatkan semua yang ada dalam pangkalan data untuk pengguna ini. Dan kemudian, apabila pengaturcara meminta sesuatu, contohnya $user->email, kita hanya boleh melihat dalam kaedah __get() - jika kita telah memuatkan maklumat tersebut daripada pangkalan data, dan ia berada dalam $data["email"] - maka di sini kami untuk anda dan kembali. Dan dalam __set() ia adalah sebaliknya. Adakah terdapat medan sedemikian dalam pangkalan data? Jadi mari kita tetapkan nilai baharu kepadanya.

/** * Pengguna Kelas * @property-read integer id pengguna semasa * @property-write String tapak mengembalikan pautan ke tapak pengguna */ class User ( private $data; private $f_write=false; public function __set( $name, $ value) ($ ​​this->data[$name] = $value; $this->f_write=true; // tanda bahawa data perlu disimpan ) public function __get($name) ( if( kosong($data))( // baca rekod dari pangkalan data ke data ) kembalikan $this->data[$name]; ) fungsi __destruct() ( if(!empty($data)&&$this->f_write) ( // simpan perubahan pada pangkalan data) ) ) $user=new User(); $user->site="http://kdg.site/"; //assign to variable echo $user->site; //paparkan nilai pembolehubah //tuliskannya ke pangkalan data. Anda jelas tidak boleh melakukan ini, kerana. pada penghujung skrip ia akan berlaku secara automatik unset($user);

Contoh menggunakan harta kelas yang tidak diisytiharkan sebagai elemen tatasusunan

Ambil perhatian bahawa rujukan dikembalikan daripada __get :

Kelas Foo ( $data peribadi = ; fungsi awam __set($nama, $value) ($ ​​this->data[$name] = $value; ) public function & __get($name) ( return $this->data [$ nama]; ) ) $foo = new Foo(); $foo->bar = "lol"; var_dump($foo->bar);

Menggunakan pemintas untuk mengakses kaedah kelas yang tidak diisytiharkan

class OurClass ( public function __call($name,array $params) ( echo "Anda mahu memanggil $Object->".$name.", tetapi ia tidak wujud, dan kini ".__METHOD__."()"; return; ) public static function __callStatic($name,array $params) ( echo "Anda mahu memanggil ".__CLASS__."::".$name.", tetapi ia tidak wujud, dan ".__METHOD__."( )"; return; ) ) $Object=New OurClass; $Object->DynamicMethod(); OurClass::StaticMethod();

Contoh memintas meta kelas persendirian:

Kelas _byCallStatic( // Contoh memintas kaedah kelas "peribadi", // apabila menggunakan kaedah "__callStatic()" untuk memanggil kaedah statik. fungsi statik awam __callStatic($_name, $_param) ( return call_user_func_array("static: :". $ _name, $_param); ) fungsi statik peribadi _newCall()( echo "Kaedah: ". __METHOD__; ) ) echo _byCallStatic::_newCall(114, "Integer", 157); # Keputusan: Kaedah: _byCallStatic::_newCall

Cara memanggil mana-mana kaedah dinamik melalui kaedah statik:

/** * Class o * @method static void __f(int $a1 = 1) */ class o ( public static function __callStatic($method, $args) ($class = get_called_class(); $obj = new $class( $args); $method = substr($method, 2); $pass = array_slice($args,1); $reflection = new ReflectionMethod($obj, $method); return $reflection->invokeArgs($obj, $ lulus); ) fungsi awam f($a1 = 1) ( var_dump("oo", func_get_args()); ) ) kelas a dilanjutkan o ( public function f($a1 = 1, $a2 = 2) ( var_dump(" aa", $a1); ) ) kelas b memanjangkan o ( fungsi awam f($b1 = 1) ( var_dump("bb", $b1); ) ) a::__f(1,2,3); b::__f(4,5,6);

Penerangan berguna tentang bekerja dengan ReflectionClass, apabila anda boleh menganalisis sifat dan kaedah kelas, semak parameter terhadap templat, dsb.: http://habrahabr.ru/post/139649/

Bagaimana untuk menggunakan objek sebagai fungsi?

kelas Anjing ( $nama peribadi; fungsi awam __construct($NamaNama = "Tuzik") ( $this->nama = $NamaNama; ) fungsi statik awam __invoke() ( $args = func_get_args(); echo "Anjing diterima: " . implode(" dan ", $args); ) ) $dog = new Dog("Mukhtar"); $dog("tulang", "tali");

Anjing diterima: tulang dan tali

Bagaimana untuk mengakses objek sebagai tatasusunan?

Untuk melakukan ini, anda perlu mencipta objek yang melaksanakan antara muka ArrayAccess daripada SPL. Contoh berikut melaksanakan objek yang datanya boleh diakses dalam gaya mengakses tatasusunan dan melalui mendapatkan sifat:

Kelas MyArray melaksanakan ArrayAccess ( protected $arr = array(); public function offsetSet($key, $value) ($this->arr[$key] = $value; ) public function offsetUnset($key) ( unset( $this ->arr[$key]); ) public function offsetGet($key) ( return $this->arr[$key]; ) public function offsetExists($key) ( return isset ($this->arr[$ key] ); ) public function __get($key) ( return $this->offsetGet($key); ) public function __set($key, $val) ($this->offsetSet($key, $val); ) ) $ a = newMyArray(); $a["whoam"] = "Adakah saya nilai tatasusunan atau objek?
"; echo $a["whoam"]; echo $a->whoam;

Adakah saya nilai tatasusunan, atau objek? Adakah saya nilai tatasusunan, atau objek?

Kelas autoload

Fail kelas yang dimuat secara automatik biasanya terletak di lokasi biasa seperti /include/class/. Nama fail dibentuk dalam format CLASS_NAME.php. Kod ini mesti disertakan dalam semua skrip PHP: spl_autoload_register(function ($class_name) ( //echo "Autoload ".$class_name; $file = $_SERVER["DOCUMENT_ROOT"] . "/include/class/" . strtolower($ class_name) . ".php"; if (file_exists($file) == false) ( if($GLOBALS["DEBUG"]) echo "Tiada fail ".$file; return false; ) include_once($file); return benar;));

Anda juga boleh menggunakan definisi fungsi __autoload() untuk autoload kelas;

Pengendalian Pengecualian dalam OOP

Pengecualian digunakan untuk mengendalikan ralat bukan kritikal.

Cuba ( $a = 1; $b = 0; if($b == 0) buang Exception baharu ("bahagi dengan sifar!"); $c = $a/$b; ) tangkap (Exception $e) ( echo $e->getMessage(); echo $e->getLine(); )

Pengecualian ialah kelas terbina dalam. Jika anda menekan lontaran, maka kod di bawah tidak dilaksanakan dan peralihan dibuat kepada blok tangkapan.

Blok cuba-tangkap digunakan dalam kedua-dua pengaturcaraan prosedural dan OOP. Ia digunakan untuk menangkap ralat - blok percubaan besar dengan banyak lontaran dan semuanya ditangkap di satu tempat - blok tangkapan.

Pengecualian boleh diwarisi, adalah wajar untuk memuatkan semula pembina: kelas MyException memanjangkan Pengecualian ( function __construct($msg)( parent::__construct($msg); ) )

Terdapat beberapa blok tangkapan - untuk setiap kelas Pengecualian.

Kemas kini terakhir: 1.11.2015

Apabila mencipta program dalam PHP dan blok individunya, fungsi yang diwakili oleh fungsi mungkin cukup untuk kita. Walau bagaimanapun, PHP mempunyai keupayaan pengaturcaraan lain yang diwakili oleh pengaturcaraan berorientasikan objek. Dalam sesetengah kes, program yang menggunakan OOP lebih mudah difahami, lebih mudah diselenggara dan diubah.

Konsep utama paradigma OOP ialah konsep "kelas" dan "objek". Penerangan objek ialah kelas, dan objek mewakili contoh kelas itu. Kita boleh membuat analogi berikut: setiap orang mempunyai idea tentang seseorang - kehadiran dua lengan, dua kaki, kepala, sistem pencernaan, sistem saraf, otak, dll. Terdapat beberapa templat - templat ini boleh dipanggil kelas. Tetapi orang yang benar-benar sedia ada (sebenarnya contoh kelas ini) ialah objek kelas ini.

Kata kunci yang digunakan untuk membuat kelas dalam PHP ialah kelas . Sebagai contoh, kelas baharu yang mewakili pengguna:

ClassUser()

Untuk mencipta objek kelas Pengguna, kata kunci baharu digunakan:

Dalam kes ini, pembolehubah $user ialah objek kelas Pengguna. Menggunakan fungsi print_r(), anda boleh mencetak kandungan objek, sama seperti tatasusunan.

Sifat dan Kaedah

Kelas boleh mengandungi sifat yang menerangkan beberapa ciri objek, dan kaedah yang menentukan kelakuannya. Mari tambahkan beberapa sifat dan kaedah pada kelas Pengguna:

nama; Umur: $ini->umur
"; ) ) $user = Pengguna baharu; $user->name="Tom"; // tetapkan $name property $user->age=30; // tetapkan $age property $user->getInfo(); // panggil ke kaedah getInfo() print_r($user); ?>

Di sini kelas Pengguna mengandungi dua sifat: $name dan $age . Sifat diisytiharkan sebagai pembolehubah biasa didahului oleh pengubah suai akses - dalam kes ini, pengubah suai awam.

Kaedah mewakili fungsi biasa yang melakukan tindakan tertentu. Di sini, fungsi getInfo() mengeluarkan kandungan pembolehubah yang ditakrifkan sebelum ini.

Untuk merujuk kepada objek semasa daripada kelas yang sama, ungkapan $this digunakan - ia mewakili objek semasa. Pengendali capaian -> digunakan untuk mengakses sifat dan kaedah sesuatu objek. Sebagai contoh, untuk mendapatkan nilai harta $name, anda akan menggunakan ungkapan $this->name . Selain itu, apabila mengakses sifat, tanda $ tidak digunakan.

Apabila menggunakan objek kelas Pengguna, pernyataan akses juga digunakan untuk mendapatkan atau menetapkan nilai sifat, serta untuk memanggil kaedah.

Pembina dan pemusnah

Pembina ialah kaedah khas yang dilaksanakan apabila objek dicipta dan berfungsi untuk memulakan sifatnya. Untuk mencipta pembina, anda mesti mengisytiharkan fungsi bernama __construct (dengan dua garis bawah di hadapan):

nama = $nama; $ini->umur = $umur; ) function getInfo() ( echo "Nama: $this->name ; Umur: $this->age
"; ) ) $user2 = new User("John", 33); $user2->getInfo(); ?>

Fungsi pembina dalam kes ini mengambil dua parameter. Nilai mereka dihantar ke sifat kelas. Dan sekarang, untuk mencipta objek, kita perlu menghantar nilai untuk parameter yang sepadan: $user2 = new User("John", 33);

Pilihan Lalai

Untuk menjadikan pembina lebih fleksibel, kita boleh menetapkan satu atau lebih parameter sebagai pilihan. Kemudian, apabila mencipta objek, tidak perlu menentukan semua parameter. Sebagai contoh, mari tukar pembina seperti ini:

Fungsi __build($name="Volume", $age=33) ( $this->name = $name; $this->age = $age; )

Oleh itu, jika tiada parameter diberikan, nilai "Volume" dan 33 akan digunakan sebaliknya. Dan kini kita boleh mencipta objek Pengguna dalam beberapa cara:

$user1 = new User("John", 25); $user1->getInfo(); $user2 = new User("Jack"); $user2->getInfo(); $user3 = new User(); $user3->getInfo();

Pemusnah

Pemusnah digunakan untuk membebaskan sumber yang digunakan oleh program - untuk membebaskan fail terbuka, membuka sambungan pangkalan data, dan sebagainya. Pemusnah objek dipanggil oleh penterjemah PHP itu sendiri selepas rujukan terakhir kepada objek itu dalam program telah hilang.

Pemusnah ditakrifkan menggunakan fungsi __destruct (dua garis bawah utama):

Pengguna Kelas ( public $name, $age; function __construct($name, $age) ( $this->name = $name; $this->age = $age; ) function getInfo() ( echo "Nama: $this ->nama ; Umur: $ini->umur
"; ) fungsi __destruct() ( echo "Panggil pemusnah"; ) )

Fungsi pemusnah ditakrifkan tanpa parameter, dan apabila objek tidak lagi dirujuk dalam program, ia akan dimusnahkan dan pemusnah akan dipanggil.

Pembolehubah yang menjadi ahli kelas dipanggil "sifat". Ia juga dipanggil menggunakan istilah lain seperti "atribut" atau "medan", tetapi untuk tujuan dokumentasi ini, kami akan merujuknya sebagai sifat. Mereka ditakrifkan dengan kata kunci awam, dilindungi atau persendirian, mengikut peraturan untuk pengisytiharan pembolehubah yang betul. Pengisytiharan ini mungkin mengandungi pemulaan, tetapi pemulaan itu mestilah nilai malar, iaitu, nilai mesti dikira pada masa penyusunan dan tidak boleh bergantung pada maklumat yang diperoleh pada masa berjalan untuk mengiranya.

Pembolehubah pseudo $this tersedia dalam mana-mana kaedah kelas apabila kaedah itu dipanggil daripada konteks objek. $ini ialah rujukan kepada objek yang dipanggil (biasanya objek kepunyaan kaedah, tetapi mungkin objek lain jika kaedah dipanggil secara statik daripada konteks objek kedua).

Beispiel #1 Menentukan sifat

kelas SimpleClass
{
public $var1 = "hello " . "dunia" ;
awam $var2 =<<Hai dunia
EOD;
// takrifan harta yang betul pada PHP 5.6.0:
awam $var3 = 1 + 2 ;
// takrif harta yang salah:
awam $var4 = diri :: myStaticMethod();
awam $var5 = $myVar ;

// takrifan sifat yang betul:
awam $var6 = myConstant ;
public $var7 = array(true , false );

// definisi harta yang betul pada PHP 5.3.0:
awam $var8 =<<<"EOD"
Hai dunia
EOD;
}
?>

Komen:

Memandangkan PHP 5.3.0 dan nowdocs boleh digunakan dalam mana-mana konteks data statik, termasuk mentakrifkan sifat.

Beispiel #2 nowdoc contoh untuk memulakan sifat

kelas foo(
// Sejak PHP 5.3.0
awam $bar =<<<"EOT"
bar
EOT;
awam $asas =<<baz
EOT;
}
?>

Komen:

Sokongan untuk Nowdoc dan Heredoc telah ditambah dalam PHP 5.3.0.

7 tahun lepas

Sekiranya ini menjimatkan sesiapa sahaja pada bila-bila masa, saya menghabiskan masa yang lama untuk mencari sebab yang berikut tidak berfungsi:

kelas MyClass
{
persendirian $foo = SALAH;


{
$this->$foo = BENAR;

echo($this->$foo);
}
}

$bar = MyClass baharu();

memberikan "Ralat maut: Tidak dapat mengakses harta kosong dalam ...test_class.php pada baris 8"

Perubahan halus untuk mengalih keluar $ sebelum akses $foo membetulkan ini:

kelas MyClass
{
persendirian $foo = SALAH;

Fungsi awam __construct()
{
$this->foo = BENAR;

echo($this->foo);
}
}

$bar = MyClass baharu();

Saya rasa kerana ia menganggap $foo seperti pembolehubah dalam contoh pertama, jadi cuba panggil $this->FALSE (atau sesuatu di sepanjang baris tersebut) yang tidak masuk akal. Ia jelas apabila anda sedar, tetapi ada bukankah sebarang contoh akses pada halaman ini yang menunjukkan perkara itu.

4 tahun lepas

Anda boleh mengakses nama harta dengan sengkang di dalamnya (contohnya, kerana anda menukar fail XML kepada objek) dengan cara berikut:

$ref = new StdClass();
$ref ->( "ref-type" ) = "Artikel Jurnal" ;
var_dump($ref);
?>

8 tahun lepas

$ini boleh dihantar ke tatasusunan. Tetapi apabila berbuat demikian, ia memberi awalan kepada nama harta/kunci tatasusunan baharu dengan data tertentu bergantung pada klasifikasi harta. Nama harta awam tidak ditukar. Sifat yang dilindungi diawali dengan "*" berlapik ruang. Harta persendirian diawali dengan nama kelas berlapik ruang...

ujian kelas
{
awam $var1 = 1;
dilindungi $var2 = 2 ;
persendirian $var3 = 3 ;
statik $var4 = 4 ;

fungsi awam toArray()
{
pulangkan (susun) $this ;
}
}

$t = ujian baharu ;
print_r($t -> toArray());

/* output:

tatasusunan
=> 1
[*var2] => 2
[ujian var3] => 3
)

*/
?>
Ini adalah tingkah laku yang didokumenkan apabila menukar sebarang objek kepada tatasusunan (lihathalaman manual PHP). Semua sifat tanpa mengira keterlihatan akan ditunjukkan apabila menghantar objek ke tatasusunan (dengan pengecualian beberapa objek terbina dalam).

Untuk mendapatkan tatasusunan dengan semua nama sifat tidak diubah, gunakan fungsi "get_object_vars($this)" dalam mana-mana kaedah dalam skop kelas untuk mendapatkan tatasusunan semua sifat tanpa mengira keterlihatan luaran atau "get_object_vars($object)" di luar skop kelas untuk dapatkan semula tatasusunan harta awam sahaja (lihat:halaman manual PHP).

9 tahun lepas

Jangan mengelirukan versi php sifat dengan sifat dalam bahasa lain (C++ sebagai contoh). Dalam php, sifat adalah sama dengan atribut, pembolehubah mudah tanpa fungsi. Ia harus dipanggil atribut, bukan sifat.

Sifat mempunyai kefungsian pengakses tersirat dan mutator. Saya telah mencipta kelas abstrak yang membenarkan fungsi harta tersirat.

Kelas abstrak PropertyObject
{
fungsi awam __get($name )
{
if (method_exists ($this , ($method = "get_" . $name )))
{
pulangkan $ini -> $kaedah ();
}
lain kembali;
}

Fungsi awam __isset($name )
{
if (method_exists ($this , ($method = "isset_" . $name )))
{
pulangkan $ini -> $kaedah ();
}
lain kembali;
}

fungsi awam __set ($name , $value )
{
jika (kaedah_wujud ($ini , ($kaedah = "set_" . $nama )))
{
$this -> $method ($value );
}
}

Fungsi awam __unset($name )
{
if (method_exists ($this , ($method = "unset_" . $name )))
{
$this -> $method();
}
}
}

?>

selepas melanjutkan kelas ini, anda boleh mencipta aksesori dan mutator yang akan dipanggil secara automatik, menggunakan kaedah ajaib php, apabila harta yang sepadan diakses.

5 tahun lepas

Kaedah yang dikemas kini objectThis() kepada sifat tatasusunan kelas transtypage atau tatasusunan kepada stdClass.

Semoga ia membantu anda.

fungsi awam objectThis($array = null) (
jika (!$array) (
foreach ($ini sebagai $property_name => $property_values) (
if (is_array($property_values) && !empty($property_values)) (
$this->($property_name) = $this->objectThis($property_values);
) else if (is_array($property_values) && kosong($property_values)) (
$this->($property_name) = new stdClass();
}
}
) lain (
$objek = new stdClass();
foreach ($array as $index => $values) (
if (is_array($values) && kosong($values)) (
$objek->($index) = new stdClass();
) else if (is_array($values)) (
$objek->($indeks) = $ini->objekIni($nilai);
) lain (
$objek->($indeks) = $nilai;
}
}
pulangkan $objek;
}
}

Objek

Objek adalah salah satu konsep asas pengaturcaraan berorientasikan objek.

Objek ialah pembolehubah yang di instantiated mengikut corak khas yang dipanggil kelas. Konsep objek dan kelas adalah sebahagian daripada paradigma pengaturcaraan berorientasikan objek (OOP).

Objek ialah koleksi data (sifat) dan fungsi (kaedah) untuk memprosesnya. Data dan kaedah dipanggil ahli kelas. Secara umum, objek ialah apa sahaja yang menyokong pengkapsulan.

Struktur dalaman objek adalah serupa dengan cincang, kecuali operator -> digunakan untuk mengakses elemen dan fungsi individu dan bukannya kurungan segi empat sama.

Untuk memulakan objek, gunakan ungkapan new , yang mencipta contoh objek dalam pembolehubah.

kelas foo
{
fungsi do_foo()
{
echo "Melakukan foo." ;
}
}

$bar = foo baharu ;
$bar -> do_foo();
?>

Dalam objek, data dan kod (ahli kelas) boleh sama ada awam atau tidak. Data awam dan ahli kelas boleh diakses oleh bahagian lain program yang bukan sebahagian daripada objek. Tetapi data peribadi dan ahli kelas hanya tersedia di dalam objek ini.

Penerangan kelas dalam PHP bermula dengan perkataan fungsi kelas:

nama_kelas kelas (
// penerangan ahli kelas - data dan kaedah untuk memprosesnya
}

Untuk pengumuman objek operator mesti digunakan baru:

Objek = Nama Kelas baharu;

Data diterangkan menggunakan perkataan perkhidmatan var. Kaedah ini diterangkan dengan cara yang sama seperti fungsi biasa. Anda juga boleh menghantar parameter kepada kaedah.

Contoh kelas PHP:

// Buat kelas Coor baharu:
classCoor(
// data (sifat):
var$name;
var$addr;

// kaedah:
nama fungsi()(
bergema"

John

" ;
}

}

// Cipta sebuah objek Kelas Coor:
$objek = baru Coor ;
?>

Mengakses Kelas dan Objek dalam PHP

Kami telah melihat bagaimana kelas ditakrifkan dan objek dicipta. Sekarang kita perlu mengakses ahli kelas, untuk PHP ini menyediakan pengendali -> . Berikut adalah contoh:

// Buat kelas Coor baharu:
classCoor(
// data (sifat):
var$name;

// kaedah:
function getname()(
bergema"

John

" ;
}

}


$objek = baru Coor ;
// Dapatkan akses kepada ahli kelas:
$objek -> nama="Alex";
echo $objek -> nama ;
// Memaparkan "Alex"
// Dan sekarang kita mendapat akses kepada kaedah kelas (sebenarnya, kepada fungsi di dalam kelas):
$objek -> getname();
// Memaparkan "John" dalam huruf besar
?>

Untuk mengakses ahli kelas dalam kelas, anda perlu menggunakan penunjuk $ini, yang sentiasa merujuk kepada objek semasa. kaedah yang diubah suai getname():

fungsi Getname() (
echo $ini->nama;
}

Dengan cara yang sama, anda boleh menulis kaedah setname():

function Setname($name) (
$ini->nama = $nama;
}

Sekarang anda boleh menggunakan kaedah untuk menukar nama setname():

$objek->Setname("Peter");
$objek->Getname();

Dan berikut ialah penyenaraian kod lengkap:

// Buat kelas Coor baharu:
classCoor(
// data (sifat):
var$name;

// kaedah:
function getname()(
echo $ini -> nama ;
}

function Setname($name)(
$ini -> nama = $nama;
}

}

// Buat objek kelas Coor:
$objek = baru Coor ;
// Sekarang kita menggunakan kaedah Setname() untuk menukar nama:
$objek -> Nama Set("Nick");
// Dan untuk akses, seperti sebelumnya, Getname():
$objek -> getname();
// Keluaran skrip "Nick"
?>

penunjuk $ini juga boleh digunakan untuk mengakses kaedah, bukan hanya akses data:

function Setname($name) (
$ini->nama = $nama;
$this->Getname();
}

Inisialisasi objek

Kadang-kadang menjadi perlu untuk memulakan objek - untuk memberikan nilai awal kepada sifatnya. Katakan nama kelas ialah Coor dan ia mengandungi dua sifat: nama orang dan bandar kediamannya. Anda boleh menulis kaedah (fungsi) yang akan melaksanakan pemulaan objek, sebagai contoh Di dalamnya():

// Buat kelas Coor baharu:
classCoor(
// data (sifat):
var$name;
var$city;

// Kaedah permulaan:
function Init($name)(
$ini -> nama = $nama;
$ this -> city = "London" ;
}

}

// Buat objek kelas Coor:
$objek = baru Coor ;
// Untuk memulakan objek, panggil kaedah dengan segera:
$objek -> init();
?>

Perkara utama adalah jangan lupa untuk memanggil fungsi dengan segera selepas mencipta objek, atau memanggil beberapa kaedah antara penciptaan (operator baru) objek dan permulaannya (dengan memanggil Di dalamnya).

Agar PHP mengetahui bahawa kaedah tertentu harus dipanggil secara automatik apabila objek dicipta, ia perlu diberi nama yang sama dengan kelas ( Coor):

functionCoor($nama)
$ini->nama = $nama;
$this->city = "London";
}

Kaedah yang memulakan objek dipanggil pembina. Walau bagaimanapun, PHP tidak mempunyai pemusnah, kerana sumber dibebaskan secara automatik apabila skrip keluar.

Tukar kepada objek

Jika objek ditukar kepada objek, ia tidak berubah. Jika nilai mana-mana jenis lain ditukar kepada objek, contoh baharu kelas terbina dalam stdClass dicipta. Jika nilai itu kosong, kejadian baharu juga akan kosong. Sebarang nilai lain akan mengandunginya dalam pembolehubah ahli skalar:

Forum Portal PHP. SU

Apa lagi yang perlu dibaca