Dalam proyek PHP, pola apa yang ada untuk menyimpan, mengakses, dan mengatur objek pembantu? [Tutup]

114

Bagaimana Anda mengatur dan mengelola objek pembantu Anda seperti mesin database, pemberitahuan pengguna, penanganan kesalahan, dan sebagainya dalam proyek berorientasi objek berbasis PHP?

Katakanlah saya memiliki CMS PHP yang besar. CMS diatur dalam berbagai kelas. Beberapa contoh:

  • objek database
  • manajemen pengguna
  • API untuk membuat / memodifikasi / menghapus item
  • objek olahpesan untuk menampilkan pesan kepada pengguna akhir
  • pengendali konteks yang membawa Anda ke halaman yang benar
  • kelas bilah navigasi yang menunjukkan tombol
  • objek penebangan
  • mungkin, penanganan error kustom

dll.

Saya berurusan dengan pertanyaan abadi, bagaimana cara terbaik membuat objek ini dapat diakses oleh setiap bagian dari sistem yang membutuhkannya.

Apporach pertama saya, bertahun-tahun yang lalu adalah memiliki $ application global yang berisi instance yang diinisialisasi dari kelas-kelas ini.

global $application;
$application->messageHandler->addMessage("Item successfully inserted");

Saya kemudian beralih ke pola Singleton dan fungsi pabrik:

$mh =&factory("messageHandler");
$mh->addMessage("Item successfully inserted");

tapi saya juga tidak senang dengan itu. Tes unit dan enkapsulasi menjadi semakin penting bagi saya, dan dalam pemahaman saya, logika di balik global / lajang menghancurkan ide dasar OOP.

Maka tentu saja ada kemungkinan memberikan setiap objek sejumlah petunjuk ke objek pembantu yang dibutuhkannya, mungkin cara yang paling bersih, hemat sumber daya, dan ramah pengujian, tetapi saya ragu tentang pemeliharaan ini dalam jangka panjang.

Sebagian besar kerangka kerja PHP yang saya lihat menggunakan pola tunggal, atau fungsi yang mengakses objek yang diinisialisasi. Keduanya merupakan pendekatan yang bagus, tetapi seperti yang saya katakan, saya tidak senang dengan keduanya.

Saya ingin memperluas wawasan saya tentang pola umum apa yang ada di sini. Saya mencari contoh, gagasan tambahan dan pointer terhadap sumber daya yang membahas ini dari jangka panjang , dunia nyata perspektif.

Selain itu, saya tertarik untuk mendengar tentang pendekatan khusus, khusus, atau aneh untuk masalah ini.

Pekka 웃
sumber
1
Saya baru saja mengajukan pertanyaan yang sangat mirip yang juga memiliki bounty. Anda mungkin menghargai beberapa jawaban di sana: stackoverflow.com/questions/1967548/…
philfreo
3
Perlu diketahui, mengembalikan objek baru dengan referensi - suka $mh=&factory("messageHandler");tidak ada gunanya dan tidak menghasilkan manfaat kinerja apa pun. Selain itu, ini tidak digunakan lagi di 5.3.
ryeguy

Jawaban:

68

Saya akan menghindari pendekatan Singleton yang disarankan oleh Flavius. Ada banyak alasan untuk menghindari pendekatan ini. Itu melanggar prinsip OOP yang baik. Blog pengujian google memiliki beberapa artikel bagus di Singleton dan bagaimana menghindarinya:

http://googletesting.blogspot.com/2008/08/by-miko-hevery-so-you-join-new-project.html http://googletesting.blogspot.com/2008/05/tott-using-dependancy -injection-to.html http://googletesting.blogspot.com/2008/08/where-have-all-singletons-gone.html

Alternatif

  1. penyedia layanan

    http://java.sun.com/blueprints/corej2eepatterns/Patterns/ServiceLocator.html

  2. injeksi ketergantungan

    http://en.wikipedia.org/wiki/Dependency_injection

    dan penjelasan php:

    http://components.symfony-project.org/dependency-injection/trunk/book/01-Dependency-Injection

Ini adalah artikel bagus tentang alternatif ini:

http://martinfowler.com/articles/injection.html

Menerapkan injeksi ketergantungan (DI):

Beberapa pemikiran lagi tentang solusi Flavius. Saya tidak ingin posting ini menjadi anti-posting tetapi saya pikir penting untuk melihat mengapa injeksi ketergantungan, setidaknya bagi saya, lebih baik daripada global.

Meskipun ini bukan implementasi Singleton yang 'sebenarnya' , saya masih berpikir Flavius ​​salah. Keadaan global itu buruk . Perhatikan bahwa solusi tersebut juga menggunakan metode statis yang sulit untuk diuji .

Saya tahu banyak orang melakukannya, menyetujuinya, dan menggunakannya. Tapi membaca artikel blog Misko Heverys ( pakar google testability ), membaca ulang dan perlahan mencerna apa yang dia katakan benar-benar mengubah cara saya melihat banyak desain.

Jika Anda ingin dapat menguji aplikasi Anda, Anda harus mengadopsi pendekatan yang berbeda untuk mendesain aplikasi Anda. Ketika Anda melakukan pemrograman uji-pertama, Anda akan mengalami kesulitan dengan hal-hal seperti ini: 'selanjutnya saya ingin menerapkan pencatatan pada bagian kode ini; mari kita tulis tes pertama yang mencatat pesan dasar 'dan kemudian menghasilkan tes yang memaksa Anda untuk menulis dan menggunakan logger global yang tidak dapat diganti.

Saya masih berjuang dengan semua informasi yang saya dapatkan dari blog itu, dan penerapannya tidak selalu mudah, dan saya punya banyak pertanyaan. Tapi tidak mungkin saya bisa kembali ke apa yang saya lakukan sebelumnya (ya, status global dan Singletons (S besar)) setelah saya memahami apa yang dikatakan Misko Hevery :-)

koen
sumber
1 untuk DI. Meskipun saya tidak menggunakannya sesering yang saya inginkan, itu sangat membantu dalam jumlah kecil apa pun yang saya gunakan.
Anurag
1
@ Koen: Mau memberikan contoh PHP dari implementasi DI / SP di PHP? Mungkin kode @Flavius ​​diimplementasikan menggunakan pola alternatif yang Anda sarankan?
Alix Axel
Menambahkan tautan ke implementasi dan penampung DI dalam jawaban saya.
Thomas
Saya membaca semua ini sekarang tetapi saya belum membaca semuanya, saya ingin bertanya, apakah kerangka kerja injeksi ketergantungan pada dasarnya adalah Registry?
JasonDavis
Tidak terlalu. Tetapi Kontainer Injeksi Ketergantungan dapat berfungsi sebagai registri juga. Baca saja tautan yang saya posting di jawaban saya. Cita-cita DI dijelaskan dengan sangat praktis.
Thomas
16
class Application {
    protected static $_singletonFoo=NULL;

    public static function foo() {
        if(NULL === self::$_singletonFoo) {
            self::$_singletonFoo = new Foo;
        }
        return self::$_singletonFoo;
    }

}

Ini adalah cara saya melakukannya. Ini menciptakan objek sesuai permintaan:

Application::foo()->bar();

Begitulah cara saya melakukannya, ini menghormati prinsip-prinsip OOP, ini lebih sedikit kode daripada bagaimana Anda melakukannya sekarang, dan objek dibuat hanya ketika kode membutuhkannya untuk pertama kalinya.

Catatan : apa yang saya sajikan bahkan bukanlah pola tunggal yang nyata. Singleton hanya akan mengizinkan satu instance dirinya sendiri dengan mendefinisikan konstruktor (Foo :: __ constructor ()) sebagai privat. Ini hanya variabel "global" yang tersedia untuk semua instance "Aplikasi". Itu sebabnya saya pikir penggunaannya valid karena TIDAK mengabaikan prinsip OOP yang baik. Tentu saja, seperti apapun di dunia ini, "pola" ini juga tidak boleh digunakan secara berlebihan!

Saya telah melihat ini digunakan di banyak kerangka kerja PHP, Zend Framework dan Yii di antaranya. Dan Anda harus menggunakan kerangka kerja. Saya tidak akan memberi tahu Anda yang mana.

Tambahan Bagi Anda yang mengkhawatirkan TDD , Anda masih bisa membuat beberapa kabel untuk menyuntikkan dependensi. Ini bisa terlihat seperti ini:

class Application {
        protected static $_singletonFoo=NULL;
        protected static $_helperName = 'Foo';

        public static function setDefaultHelperName($helperName='Foo') {
                if(is_string($helperName)) {
                        self::$_helperName = $helperName;
                }
                elseif(is_object($helperName)) {
                        self::$_singletonFoo = $helperName;
                }
                else {
                        return FALSE;
                }
                return TRUE;
        }
        public static function foo() {
                if(NULL === self::$_singletonFoo) {
                        self::$_singletonFoo = new self::$_helperName;
                }
                return self::$_singletonFoo;
        }
}

Ada cukup ruang untuk perbaikan. Ini hanya PoC, gunakan imajinasi Anda.

Kenapa seperti itu? Yah, sebagian besar waktu aplikasi tidak akan diuji unit, itu benar-benar akan dijalankan, semoga dalam lingkungan produksi . Kekuatan PHP adalah kecepatannya. PHP BUKAN dan tidak akan pernah menjadi "bahasa OOP yang bersih", seperti Java.

Dalam sebuah aplikasi, hanya ada satu kelas Aplikasi dan hanya satu contoh dari setiap pembantunya, paling banyak (sesuai dengan lazy loading seperti di atas). Tentu, lajang itu buruk, tapi sekali lagi, hanya jika mereka tidak mengikuti dunia nyata. Dalam contoh saya, mereka melakukannya.

"Aturan" stereotip seperti "lajang itu buruk" adalah sumber kejahatan, itu untuk orang malas yang tidak mau berpikir sendiri.

Ya, saya tahu, manifesto PHP BURUK, secara teknis. Namun itu adalah bahasa yang sukses, dengan caranya yang meretas.

Tambahan

Gaya satu fungsi:

function app($class) {
    static $refs = array();

    //> Dependency injection in case of unit test
    if (is_object($class)) {
        $refs[get_class($class)] = $class;
        $class = get_class($class);
    }

    if (!isset($refs[$class]))
        $refs[$class] = new $class();

    return $refs[$class];
}

//> usage: app('Logger')->doWhatever();
Flavius
sumber
2
Saya tidak menyukai jawabannya karena saya yakin menyarankan pola tunggal untuk menangani masalah tersebut bertentangan dengan prinsip OOP yang solid.
koen
1
@ Koen: apa yang Anda katakan itu benar, secara umum, TAPI sejauh yang saya mengerti masalahnya, dia berbicara tentang pembantu untuk aplikasi, dan di dalam aplikasi hanya ada satu ... uhm, aplikasi.
Flavius
Catatan: apa yang saya sajikan bukanlah pola tunggal yang nyata. Singleton hanya akan mengizinkan satu instance kelas dengan mendefinisikan konstruktor sebagai privat. Ini hanya variabel "global" yang tersedia untuk semua instance "Aplikasi". Itu sebabnya saya pikir validnya TIDAK mengabaikan prinsip OOP yang baik. Tentu saja, seperti apapun di dunia, "pola" ini juga tidak boleh digunakan secara berlebihan.
Flavius
-1 dari saya juga. Ini mungkin hanya setengah dari Singleton DP, tapi itu yang paling jelek: "sediakan akses global untuk itu".
hanya seseorang
2
Ini memang membuat pendekatannya yang ada jauh lebih bersih.
Daniel Von Fange
15

Saya suka konsep Injeksi Ketergantungan:

"Injeksi Ketergantungan adalah tempat komponen diberikan ketergantungannya melalui konstruktor, metode, atau langsung ke bidang. (Dari Situs Wadah Pico )"

Fabien Potencier menulis serangkaian artikel yang sangat bagus tentang Injeksi Ketergantungan dan kebutuhan untuk menggunakannya. Dia juga menawarkan Kontainer Injeksi Ketergantungan yang bagus dan kecil bernama Jerawat yang sangat saya suka gunakan (info lebih lanjut tentang github ).

Seperti yang dinyatakan di atas, saya tidak suka penggunaan Singletons. Ringkasan yang bagus tentang mengapa Singletons bukan desain yang bagus dapat ditemukan di sini di blog Steve Yegge .

Thomas
sumber
Saya suka implementasi melalui Closures di PHP, bacaan yang sangat menarik
Juraj Blahunka
Saya juga dan dia memiliki beberapa barang kebutuhan lain terkait penutupan di situsnya: fabien.potencier.org/article/17/…
Thomas
2
semoga saja, webhouse utama akan segera bermigrasi ke PHP 5.3, karena masih belum umum melihat server php 5.3 berfitur lengkap
Juraj Blahunka
Mereka harus melakukannya, ketika semakin banyak proyek yang membutuhkan PHP 5.3 seperti Zend Framework 2.0 akan framework.zend.com/wiki/display/ZFDEV2/…
Thomas
1
Injeksi ketergantungan juga diterima jawaban atas pertanyaan tentang decupling from GOD object: stackoverflow.com/questions/1580210/… dengan contoh yang sangat bagus
Juraj Blahunka
9

Pendekatan terbaik adalah memiliki semacam wadah untuk sumber daya tersebut. Beberapa cara paling umum untuk menerapkan penampung ini :

Singleton

Tidak disarankan karena sulit untuk diuji dan menyiratkan status global. (Singletonitis)

Registri

Menghilangkan singletonitis, bug Saya tidak akan merekomendasikan registry juga, karena ini juga sejenis singleton. (Sulit untuk menguji unit)

Warisan

Sayangnya, tidak ada beberapa warisan dalam PHP, jadi ini membatasi semua pada rantai.

Injeksi ketergantungan

Ini adalah pendekatan yang lebih baik, tetapi topik yang lebih besar.

Tradisional

Cara paling sederhana untuk melakukannya adalah menggunakan injeksi konstruktor atau penyetel (meneruskan objek ketergantungan menggunakan penyetel atau dalam konstruktor kelas).

Kerangka

Anda dapat menggulung injektor ketergantungan Anda sendiri, atau menggunakan beberapa kerangka kerja injeksi ketergantungan, misalnya. Yadif

Sumber daya aplikasi

Anda dapat menginisialisasi setiap sumber daya Anda dalam bootstrap aplikasi (yang bertindak sebagai wadah), dan mengaksesnya di mana saja dalam aplikasi yang mengakses objek bootstrap.

Ini adalah pendekatan yang diterapkan di Zend Framework 1.x

Pemuat sumber daya

Semacam objek statis yang memuat (membuat) sumber daya yang dibutuhkan hanya saat diperlukan. Ini adalah pendekatan yang sangat cerdas. Anda mungkin melihatnya beraksi, misalnya mengimplementasikan komponen Injeksi Ketergantungan Symfony

Injeksi ke lapisan tertentu

Sumber daya tidak selalu dibutuhkan di mana pun dalam aplikasi. Terkadang Anda hanya membutuhkannya misalnya di pengontrol (MV C ). Kemudian Anda dapat menyuntikkan sumber daya hanya di sana.

Pendekatan umum untuk ini adalah menggunakan komentar docblock untuk menambahkan metadata injeksi.

Lihat pendekatan saya untuk ini di sini:

Bagaimana cara menggunakan injeksi ketergantungan di Zend Framework? - Stack Overflow

Terakhir, saya ingin menambahkan catatan tentang hal yang sangat penting di sini - caching.
Secara umum, terlepas dari teknik yang Anda pilih, Anda harus memikirkan bagaimana sumber daya akan di-cache. Cache akan menjadi sumber daya itu sendiri.

Aplikasinya bisa sangat besar, dan memuat semua sumber daya pada setiap permintaan sangat mahal. Ada banyak pendekatan, termasuk appserver-in-php ini - Hosting Proyek di Google Code .

takeshin
sumber
6

Jika Anda ingin membuat objek tersedia secara global, pola registri mungkin menarik bagi Anda. Untuk inspirasi, lihat Zend Registry .

Begitu juga pertanyaan Registry vs. Singleton .

Felix Kling
sumber
Jika Anda tidak ingin menggunakan Zend Framework, berikut ini implementasi yang bagus dari pola registri untuk PHP5: phpbar.de/w/Registry
Thomas
Saya lebih suka Pola Registri yang diketik, seperti Registry :: GetDatabase ("master"); Registry :: GetSession ($ user-> SessionKey ()); Registry :: GetConfig ("lokal"); [...] dan menentukan antarmuka untuk setiap jenis. Dengan cara ini Anda memastikan Anda tidak secara tidak sengaja menimpa kunci yang digunakan untuk sesuatu yang berbeda (misalnya, Anda mungkin memiliki "master Database" dan "master Config". Dengan menggunakan Antarmuka Anda memastikan bahwa hanya objek yang valid yang digunakan. Tentu ini bisa juga diimplementasikan dengan menggunakan beberapa kelas Registry tetapi imho satu lebih sederhana dan lebih mudah digunakan tetapi masih memiliki keuntungan.
Morfildur
Atau tentu saja yang dibangun ke dalam PHP - $ _GLOBALS
Gnuffo1
4

Objek di PHP membutuhkan banyak memori, seperti yang mungkin Anda lihat dari pengujian unit Anda. Oleh karena itu, sangat ideal untuk menghancurkan objek yang tidak dibutuhkan secepat mungkin untuk menghemat memori untuk proses lain. Dengan pemikiran itu saya menemukan bahwa setiap benda cocok dengan salah satu dari dua cetakan.

1) Objek mungkin memiliki banyak metode yang berguna atau perlu dipanggil lebih dari sekali dalam hal ini saya menerapkan singleton / registri:

$object = load::singleton('classname');
//or
$object = classname::instance(); // which sets self::$instance = $this

2) Objek hanya ada selama masa aktif metode / fungsi yang memanggilnya dalam hal ini, kreasi sederhana bermanfaat untuk mencegah referensi objek yang tertinggal dari membuat objek hidup terlalu lama.

$object = new Class();

Menyimpan objek sementara DI MANA SAJA dapat menyebabkan kebocoran memori karena referensi ke objek tersebut mungkin terlupa tentang cara menyimpan objek dalam memori selama sisa skrip.

Xeoncross
sumber
3

Saya akan menggunakan fungsi yang mengembalikan objek yang diinisialisasi:

A('Users')->getCurrentUser();

Dalam lingkungan pengujian, Anda dapat menentukannya untuk mengembalikan mock-up. Anda bahkan bisa mendeteksi di dalam siapa yang memanggil fungsi menggunakan debug_backtrace () dan mengembalikan objek yang berbeda. Anda dapat mendaftar di dalamnya yang ingin mendapatkan objek apa untuk mendapatkan wawasan tentang apa yang sebenarnya terjadi di dalam program Anda.

Kamil Szot
sumber
-1

Mengapa tidak membaca manual yang bagus?

http://php.net/manual/en/language.oop5.autoload.php

gcb
sumber
Terima kasih gcb, tetapi pemuatan kelas bukan urusan saya, pertanyaan saya lebih bersifat arsitektur.
Pekka
Meskipun ini secara teoritis dapat menjawab pertanyaan, akan lebih baik jika menyertakan bagian penting dari jawaban di sini, dan memberikan tautan untuk referensi.
jjnguy