Bir PHP projesinde, yardımcı nesneleri depolamak, bunlara erişmek ve bunları organize etmek için hangi modeller vardır? [kapalı]


114

PHP tabanlı, nesne yönelimli bir projede veritabanı motoru, kullanıcı bildirimi, hata işleme gibi yardımcı nesneleri nasıl düzenler ve yönetirsiniz?

Büyük bir PHP CMS'im olduğunu varsayalım. CMS, çeşitli sınıflarda düzenlenmiştir. Birkaç örnek:

  • veritabanı nesnesi
  • Kullanıcı yönetimi
  • öğeleri oluşturmak / değiştirmek / silmek için bir API
  • son kullanıcıya mesajları görüntülemek için bir mesajlaşma nesnesi
  • sizi doğru sayfaya götüren bir bağlam işleyici
  • düğmeleri gösteren bir gezinme çubuğu sınıfı
  • bir günlük nesnesi
  • muhtemelen, özel hata işleme

vb.

Sonsuz soruyla uğraşıyorum, bu nesneleri ihtiyaç duyan sistemin her parçası için en iyi şekilde nasıl erişilebilir kılacağım.

Yıllar önce ilk yaklaşımım, bu sınıfların başlatılmış örneklerini içeren global bir $ uygulamasına sahip olmaktı.

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

Daha sonra Singleton modeline ve bir fabrika işlevine geçtim:

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

ama bundan da memnun değilim. Birim testleri ve kapsülleme benim için gittikçe daha önemli hale geliyor ve anladığım kadarıyla küresellerin / tekillerin arkasındaki mantık temel OOP fikrini yok ediyor.

O zaman elbette her bir nesneye ihtiyaç duyduğu yardımcı nesnelere bir dizi işaretçi verme olasılığı vardır, muhtemelen en temiz, kaynak tasarrufu sağlayan ve test etmeye uygun yol, ancak bunun uzun vadede sürdürülebilirliği konusunda şüphelerim var.

Baktığım çoğu PHP çerçevesi, ya tekli kalıbı ya da başlatılmış nesnelere erişen işlevleri kullanmayı araştırdı. İkisi de iyi yaklaşımlar, ama dediğim gibi ikisinden de memnun değilim.

Burada hangi ortak modellerin var olduğuna dair ufkumu genişletmek istiyorum. Bunu uzun vadeli , gerçek dünya perspektifinden tartışan kaynaklara yönelik örnekler, ek fikirler ve işaretler arıyorum .

Ayrıca, konuya özel, niş veya düpedüz tuhaf yaklaşımlar hakkında bilgi almakla ilgileniyorum .


1
Az önce, aynı zamanda bir ödül de olan son derece benzer bir soru sordum. Oradaki bazı cevapları takdir edebilirsiniz: stackoverflow.com/questions/1967548/…
philfreo

3
Sadece bir kafa yukarıda, referansla yeni bir nesneyi döndürmek - $mh=&factory("messageHandler");anlamsızdır ve herhangi bir performans avantajı sağlamaz. Ayrıca bu, 5.3'te kullanımdan kaldırılmıştır.
ryeguy

Yanıtlar:


68

Flavius ​​tarafından önerilen Singleton yaklaşımından kaçınırdım. Bu yaklaşımdan kaçınmak için çok sayıda neden vardır. İyi OOP ilkelerini ihlal ediyor. Google test blogu, Singleton ve bundan nasıl kaçınılacağı hakkında bazı iyi makaleler içeriyor:

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

Alternatifler

  1. bir servis sağlayıcı

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

  2. bağımlılık ekleme

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

    ve bir php açıklaması:

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

Bu, bu alternatifler hakkında güzel bir makale:

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

Bağımlılık ekleme (DI) uygulama:

Flavius'un çözümü hakkında biraz daha düşünce. Bu gönderinin bir anti-posta olmasını istemiyorum, ancak bağımlılık enjeksiyonunun neden en azından benim için küresellerden daha iyi olduğunu görmenin önemli olduğunu düşünüyorum.

'Gerçek' bir Singleton uygulaması olmasa da, yine de Flavius'un yanlış anladığını düşünüyorum. Küresel durum kötü . Bu tür çözümlerin statik yöntemleri test etmesi zor olduğunu unutmayın .

Birçok insanın bunu yaptığını, onayladığını ve kullandığını biliyorum. Ancak Misko Heverys blog makalelerini ( bir google test edilebilirlik uzmanı ) okumak , yeniden okumak ve söylediklerini yavaşça sindirmek tasarıma bakışımı çok değiştirdi.

Uygulamanızı test edebilmek istiyorsanız, uygulamanızı tasarlamak için farklı bir yaklaşım benimsemeniz gerekir. Önce test programlaması yaptığınızda, aşağıdaki gibi şeylerde zorluk yaşarsınız: 'Şimdi bu kod parçasına günlük kaydı uygulamak istiyorum; Önce temel bir mesajı kaydeden bir test yazalım 've ardından sizi değiştirilemeyen global bir kaydedici yazmaya ve kullanmaya zorlayan bir test oluşturalım.

Hala o blogdan aldığım tüm bilgilerle mücadele ediyorum ve uygulanması her zaman kolay olmuyor ve birçok sorum var. Ama Misko Hevery'nin söylediklerini kavradıktan sonra daha önce yaptığım şeye (evet, küresel durum ve Singletons (büyük S)) geri dönmem mümkün değil :-)


DI için +1. İstediğim kadar kullanmasam da, küçük miktarlarda kullandıysam çok yardımcı oldu.
Anurag

1
@koen: PHP'deki DI / SP uygulamasına bir PHP örneği vermek ister misiniz? Belki @Flavius ​​kodu önerdiğiniz alternatif kalıpları kullanarak uygulandı?
Alix Axel

Cevabıma DI uygulamasına ve kapsayıcıya bir bağlantı eklendi.
Thomas

Şimdi tüm bunları okuyorum ama henüz hepsini okumadım, sormak istiyorum, bağımlılık enjeksiyon çerçevesi temelde bir Kayıt Defteri olabilir mi?
JasonDavis

Hayır gerçek değil. Ancak bir Bağımlılık Enjeksiyon Kabı da bir kayıt defteri olarak hizmet verebilir. Cevabımda yayınladığım bağlantıları okuyun. DI kavramı gerçekten pratik olarak açıklanmıştır.
Thomas

16
class Application {
    protected static $_singletonFoo=NULL;

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

}

Ben böyle yaparım. Nesneyi talep üzerine oluşturur:

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

Benim bunu yapma şeklim bu, OOP ilkelerine saygı duyuyor, şu anda yaptığınızdan daha az kod ve nesne yalnızca kod ilk kez ihtiyaç duyduğunda yaratılıyor.

Not : Sunduğum şey gerçek bir tekil kalıp bile değil. Bir singleton, yapıcıyı (Foo :: __ constructor ()) özel olarak tanımlayarak kendisinin yalnızca bir örneğine izin verir. Bu, tüm "Uygulama" örneklerinde kullanılabilen yalnızca "genel" bir değişkendir. Bu nedenle, iyi OOP ilkelerini göz ardı etmediği için kullanımının geçerli olduğunu düşünüyorum. Tabii ki, dünyadaki herhangi bir şey gibi, bu "kalıp" da aşırı kullanılmamalıdır!

Bunun birçok PHP çerçevesinde, aralarında Zend Framework ve Yii'de kullanıldığını gördüm. Ve bir çerçeve kullanmalısınız. Size hangisi olduğunu söylemeyeceğim.

Eklenti Aranızda TDD hakkında endişelenenler için , yine de onu bağımlılık enjekte etmek için bazı kablolama yapabilirsiniz. Şöyle görünebilir:

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;
        }
}

İyileştirme için yeterince yer var. Bu sadece bir PoC, hayal gücünüzü kullanın.

Neden böyle yapıyorsun? Pekala, uygulama çoğu zaman birim testine tabi tutulmayacak, umarız bir üretim ortamında çalıştırılacaktır . PHP'nin gücü hızıdır. PHP DEĞİLDİR ve asla Java gibi "temiz bir OOP dili" olmayacaktır.

Bir uygulama içinde, yalnızca bir Uygulama sınıfı ve yardımcılarının her birinin yalnızca bir örneği vardır (yukarıdaki gibi yavaş yüklemeye göre). Elbette, singletonlar kötüdür, ama yine de, sadece gerçek dünyaya bağlı kalmazlarsa. Benim örneğimde yapıyorlar.

"Tek tonlar kötüdür" gibi kalıplaşmış "kurallar" kötülüğün kaynağıdır, kendileri için düşünmeye istekli olmayan tembel insanlar içindir.

Evet, biliyorum, PHP manifestosu teknik olarak KÖTÜ. Yine de hile tarzında başarılı bir dil.

ek

Bir işlev stili:

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();

2
Sorunun üstesinden gelmek için tekli modelin sağlam OOP ilkelerine aykırı olduğuna inandığım için yanıtı reddettim.
koen

1
@koen: Söylediğiniz doğru, genel olarak konuşursak, AMA onun problemini anladığım kadarıyla, uygulama için yardımcılardan bahsediyor ve bir uygulamanın içinde sadece bir tane var ... uhm, uygulama.
Flavius

Not: Sunduğum şey gerçek bir tekil kalıp bile değil. Bir singleton, yapıcıyı özel olarak tanımlayarak bir sınıfın yalnızca bir örneğine izin verir. Bu, tüm "Uygulama" örneklerinde kullanılabilen yalnızca "genel" bir değişkendir. Bu yüzden geçerli olmasının iyi OOP ilkelerini göz ardı etmediğini düşünüyorum. Elbette dünyadaki her şey gibi bu "kalıp" da fazla kullanılmamalıdır.
Flavius

-1 de benden. Singleton DP'sinin yalnızca yarısı olabilir, ancak çirkin olanı: "ona küresel erişim sağlayın".
sadece birisi

2
Bu gerçekten de mevcut yaklaşımını çok daha temiz hale getiriyor.
Daniel Von Fange

15

Bağımlılık Enjeksiyonu kavramını beğendim:

"Bağımlılık Enjeksiyonu, bileşenlerin bağımlılıklarının oluşturucuları, yöntemleri veya doğrudan alanlara verildiği yerdir. ( Pico Container Web Sitesinden )"

Fabien Potencier, Dependency Injection ve bunları kullanma ihtiyacı hakkında gerçekten güzel bir makale dizisi yazdı . Ayrıca , kullanmayı çok sevdiğim Pimple adında güzel ve küçük bir Bağımlılık Enjeksiyon Kabı sunuyor ( github hakkında daha fazla bilgi ).

Yukarıda belirtildiği gibi, Tekil kullanımından hoşlanmıyorum. Singleton'ların neden iyi bir tasarım olmadığına dair iyi bir özet burada Steve Yegge'in blogunda bulunabilir .


PHP'de Closures aracılığıyla uygulamayı seviyorum, çok ilginç bir okuma
Juraj Blahunka

Ben de kendi sitesinde kapanışlarla ilgili başka ihtiyaç duyulan şeyler var: fabien.potencier.org/article/17/…
Thomas

2
Umalım, ana akım web evleri yakında PHP 5.3'e geçecek, çünkü tam özellikli bir php 5.3 sunucusu görmek hala yaygın değil
Juraj Blahunka

Zend Framework 2.0 gibi PHP 5.3'e ihtiyaç duyan daha fazla proje, framework.zend.com/wiki/display/ZFDEV2/…
Thomas

1
Bağımlılık enjeksiyonu da şu soruya yanıt olarak kabul edildi decupling from GOD object: stackoverflow.com/questions/1580210/… çok güzel bir örnekle
Juraj Blahunka

9

En iyi yaklaşım, bu kaynaklar için bir tür konteynere sahip olmaktır . Bu kapsayıcıyı uygulamanın en yaygın yollarından bazıları :

Singleton

Test edilmesi zor olduğu ve küresel bir durumu ifade ettiği için önerilmez. (Singletonitis)

Kayıt

Tektoniti ortadan kaldırır, hata kaydını da önermem çünkü bu da bir tür singleton. (Birim testi zor)

miras

Yazık ki, PHP'de çoklu kalıtım yoktur, bu yüzden bu hepsini zincirle sınırlar.

Bağımlılık enjeksiyonu

Bu daha iyi bir yaklaşım ama daha büyük bir konu.

Geleneksel

Bunu yapmanın en basit yolu yapıcı veya ayarlayıcı enjeksiyonu kullanmaktır (bağımlılık nesnesini ayarlayıcı kullanarak veya sınıf yapıcısında geçirin).

çerçeveler

Kendi bağımlılık enjektörünüzü veya bazı bağımlılık enjeksiyon çerçevelerini kullanabilirsiniz, örn. yadif

Uygulama kaynağı

Kaynaklarınızın her birini uygulama önyüklemesinde (bir kapsayıcı görevi gören) başlatabilir ve önyükleme nesnesine erişen uygulamanın herhangi bir yerinde bunlara erişebilirsiniz.

Zend Framework 1.x'te uygulanan yaklaşım budur

Kaynak yükleyici

İhtiyaç duyulan kaynağı yalnızca gerektiğinde yükleyen (oluşturan) bir tür statik nesne. Bu çok akıllıca bir yaklaşım. Bunu eylemde görebilirsiniz, örneğin Symfony's Dependency Injection bileşenini uygularken

Belirli katmana enjeksiyon

Kaynaklara uygulamanın herhangi bir yerinde her zaman ihtiyaç duyulmaz. Bazen bunlara, örneğin kontrolörlerde (MV C ) ihtiyaç duyarsınız . O zaman kaynakları sadece oraya enjekte edebilirsiniz.

Buna genel yaklaşım, enjeksiyon meta verilerini eklemek için docblock açıklamalarını kullanmaktır.

Bu konudaki yaklaşımımı burada görün:

Zend Framework'te bağımlılık enjeksiyonu nasıl kullanılır? - Yığın Taşması

Sonunda, burada çok önemli bir şey hakkında bir not eklemek istiyorum - önbelleğe alma.
Genel olarak, seçtiğiniz tekniğe rağmen, kaynakların nasıl önbelleğe alınacağını düşünmelisiniz. Önbellek, kaynağın kendisi olacaktır.

Uygulamalar çok büyük olabilir ve tüm kaynakları her istek üzerine yüklemek çok pahalıdır. Bu php uygulama sunucusu - Google Code'da Proje Barındırma dahil olmak üzere birçok yaklaşım vardır .


6

Nesneleri küresel olarak kullanılabilir yapmak istiyorsanız, kayıt defteri kalıbı sizin için ilginç olabilir. İlham almak için Zend Registry'ye bir göz atın .

Aynı zamanda Tescil ve Singleton sorusu.


Zend Framework kullanmak istemiyorsanız, işte PHP5 için kayıt kalıbının güzel bir uygulaması: phpbar.de/w/Registry
Thomas

Registry :: GetDatabase ("ana") gibi yazılı bir Kayıt Kalıbı tercih ederim; Kayıt :: getSession ($ kullanıcı-> SessionKey ()); Kayıt :: GetConfig ( "yerel"); [...] ve her tür için bir arayüz tanımlama. Bu şekilde, yanlışlıkla farklı bir şey için kullanılan bir anahtarın üzerine yazmadığınızdan emin olursunuz (yani bir "ana Veritabanına" ve bir "ana Yapılandırmaya" sahip olabilirsiniz. Arayüzleri kullanarak, yalnızca geçerli nesnelerin kullanıldığından emin olursunuz. avantajlar aynı zamanda birden fazla kayıt sınıfları kullanarak ancak tek tek basit ve kullanımı kolaydır imho uygulanacak ama hala var.
Morfildur

Veya elbette PHP'de yerleşik olan - $ _GLOBALS
Gnuffo1

4

PHP'deki nesneler, muhtemelen birim testlerinizden de gördüğünüz gibi, iyi miktarda bellek kaplar. Bu nedenle, diğer işlemler için bellek tasarrufu sağlamak için ihtiyaç duyulmayan nesneleri mümkün olan en kısa sürede yok etmek idealdir . Bunu aklımda tutarak her nesnenin iki kalıptan birine uyduğunu görüyorum.

1) Nesnenin birçok yararlı yöntemi olabilir veya birden fazla çağrılması gerekebilir, bu durumda bir singleton / kayıt defteri uygularım:

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

2) Nesne yalnızca, onu çağıran yöntem / işlevin ömrü boyunca var olur; bu durumda, kalan nesne referanslarının nesneleri çok uzun süre canlı tutmasını önlemek için basit bir oluşturma yararlıdır.

$object = new Class();

Geçici nesneleri HER YERDE depolamak bellek sızıntılarına neden olabilir çünkü bunlara yapılan başvurular, nesneyi betiğin geri kalanı için bellekte tutma konusunda unutulabilir.


3

Başlatılmış nesneleri döndüren işlev için giderdim:

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

Test ortamında, maketler döndürmek için tanımlayabilirsiniz. Hatta debug_backtrace () kullanarak işlevi kimin çağırdığını tespit edebilir ve farklı nesneler döndürebilirsiniz. Programınızda gerçekte neler olup bittiğini anlamak için hangi nesnelere sahip olmak isteyenlerin içine kayıt olabilirsiniz.


Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.