Jakie wzorce istnieją w projekcie PHP do przechowywania, uzyskiwania dostępu i organizowania obiektów pomocniczych? [Zamknięte]


114

W jaki sposób organizujesz i zarządzasz obiektami pomocniczymi, takimi jak silnik bazy danych, powiadamianie użytkownika, obsługa błędów itp. W projekcie obiektowym opartym na PHP?

Powiedzmy, że mam duży PHP CMS. CMS jest zorganizowany w różnych klasach. Kilka przykładów:

  • obiekt bazy danych
  • Zarządzanie użytkownikami
  • API do tworzenia / modyfikowania / usuwania elementów
  • obiekt wiadomości służący do wyświetlania komunikatów dla użytkownika końcowego
  • moduł obsługi kontekstu, który przenosi Cię na właściwą stronę
  • klasa paska nawigacji, która wyświetla przyciski
  • obiekt rejestrowania
  • ewentualnie niestandardowa obsługa błędów

itp.

Mam do czynienia z odwiecznym pytaniem, jak najlepiej udostępnić te obiekty każdej części systemu, która tego potrzebuje.

Moje pierwsze rozwiązanie, wiele lat temu, polegało na stworzeniu globalnej aplikacji $ application zawierającej zainicjowane instancje tych klas.

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

Następnie przeszedłem na wzór Singleton i funkcję fabryczną:

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

ale to też mi się nie podoba. Testy jednostkowe i hermetyzacja stają się dla mnie coraz ważniejsze, aw moim rozumieniu logika stojąca za globalnymi / singletonami niszczy podstawową ideę OOP.

Wtedy oczywiście istnieje możliwość nadania każdemu obiektowi szeregu wskaźników do potrzebnych mu obiektów pomocniczych, prawdopodobnie w najczystszy, oszczędzający zasoby i przyjazny dla testowania sposób, ale mam wątpliwości co do możliwości utrzymania tego w dłuższej perspektywie.

Większość frameworków PHP, które przyjrzałem się, używa wzorca singleton lub funkcji, które mają dostęp do zainicjowanych obiektów. Oba dobre podejścia, ale jak powiedziałem, nie jestem zadowolony z żadnego.

Chciałbym poszerzyć swoje horyzonty dotyczące tego, jakie wspólne wzorce istnieją tutaj. Szukam przykładów, dodatkowych pomysłów i wskazówek dotyczących zasobów, które omawiają to z długoterminowej , rzeczywistej perspektywy.

Interesują mnie również specjalistyczne, niszowe lub po prostu dziwne podejścia do problemu.


1
Właśnie zadałem niezwykle podobne pytanie, które również miało nagrodę. Możesz tam docenić niektóre odpowiedzi: stackoverflow.com/questions/1967548/…
philfreo

3
Tylko głowa do góry, zwrócenie nowego obiektu przez odniesienie - jak $mh=&factory("messageHandler");jest bezcelowe i nie daje żadnych korzyści wydajnościowych. Ponadto jest to przestarzałe w 5.3.
ryeguy

Odpowiedzi:


68

Unikałbym podejścia Singletona sugerowanego przez Flaviusa. Istnieje wiele powodów, dla których należy unikać tego podejścia. Narusza dobre zasady OOP. Blog testujący google zawiera dobre artykuły na temat Singletona i jak go uniknąć:

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

Alternatywy

  1. usługodawcą

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

  2. zastrzyk zależności

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

    i wyjaśnienie php:

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

To jest dobry artykuł o tych alternatywach:

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

Implementowanie iniekcji zależności (DI):

Jeszcze kilka przemyśleń na temat rozwiązania Flaviusa. Nie chcę, aby ten post był antypostem, ale myślę, że ważne jest, aby zobaczyć, dlaczego zastrzyk zależności jest, przynajmniej dla mnie, lepszy niż globals.

Mimo że nie jest to „prawdziwa” implementacja Singletona , nadal uważam, że Flavius ​​źle to zrozumiał. Globalny stan jest zły . Zwróć uwagę, że takie rozwiązania wykorzystują również trudne do przetestowania metody statyczne .

Wiem, że wiele osób to robi, aprobuje i używa. Ale czytając artykuły na blogu Misko Heverys ( ekspert Google w zakresie testowania ), ponowne ich przeczytanie i powolne przetrawienie tego, co mówi, bardzo zmieniło sposób, w jaki postrzegam projektowanie.

Jeśli chcesz mieć możliwość przetestowania swojej aplikacji, musisz przyjąć inne podejście do projektowania aplikacji. Podczas programowania w pierwszej kolejności będziesz miał trudności z takimi rzeczami: „następnie chcę zaimplementować rejestrowanie w tym fragmencie kodu; napiszmy najpierw test, który rejestruje podstawową wiadomość, a następnie wymyślmy test, który zmusza cię do napisania i używania globalnego rejestratora, którego nie można zastąpić.

Wciąż zmagam się ze wszystkimi informacjami, które otrzymałem z tego bloga, i nie zawsze jest to łatwe do wdrożenia i mam wiele pytań. Ale nie ma sposobu, abym mógł wrócić do tego, co robiłem wcześniej (tak, stan globalny i singletony (duże S)) po tym, jak zrozumiałem, co mówił Misko Hevery :-)


+1 dla DI. Chociaż nie używam go tak często, jak bym chciał, był bardzo pomocny w małych ilościach, w których go używałem.
Anurag

1
@koen: Chcesz podać przykład PHP implementacji DI / SP w PHP? Może kod @Flavius ​​został zaimplementowany przy użyciu alternatywnych wzorców, które zaproponowałeś?
Alix Axel

W mojej odpowiedzi dodano łącze do implementacji DI i kontenera.
Thomas

Czytam teraz to wszystko, ale jeszcze tego wszystkiego nie przeczytałem, chciałbym zapytać, czy struktura wstrzykiwania zależności byłaby w zasadzie rejestrem?
JasonDavis

Nie, nie bardzo. Ale kontener iniekcji zależności może również służyć jako rejestr. Po prostu przeczytaj linki, które zamieściłem w mojej odpowiedzi. Pojęcie DI zostało wyjaśnione naprawdę praktycznie.
Thomas

16
class Application {
    protected static $_singletonFoo=NULL;

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

}

Tak bym to zrobił. Tworzy obiekt na żądanie:

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

To sposób, w jaki to robię, przestrzega zasad OOP, jest mniej kodu niż sposób, w jaki robisz to teraz, a obiekt jest tworzony tylko wtedy, gdy kod potrzebuje tego po raz pierwszy.

Uwaga : to, co przedstawiłem, nie jest nawet prawdziwym singletonem. Singleton dopuszczałby tylko jedno wystąpienie samego siebie, definiując konstruktor (Foo :: __ constructor ()) jako prywatny. Jest to tylko zmienna „globalna” dostępna dla wszystkich instancji „Aplikacji”. Dlatego uważam, że jego użycie jest ważne, ponieważ NIE lekceważy dobrych zasad OOP. Oczywiście, jak wszystko na świecie, ten „wzorzec” również nie powinien być nadużywany!

Widziałem, jak to jest używane w wielu frameworkach PHP, między innymi Zend Framework i Yii. I powinieneś użyć frameworka. Nie powiem ci, który.

Dodatek Dla tych z was, którzy martwią się o TDD , nadal możecie stworzyć okablowanie, aby wstrzyknąć to zależność. Mogłoby to wyglądać tak:

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

Jest wystarczająco dużo miejsca na ulepszenia. To tylko PoC, użyj swojej wyobraźni.

Dlaczego tak się dzieje? Cóż, przez większość czasu aplikacja nie będzie testowana jednostkowo, w rzeczywistości będzie uruchamiana, miejmy nadzieję, w środowisku produkcyjnym . Siłą PHP jest szybkość. PHP NIE jest i nigdy nie będzie „czystym językiem OOP”, takim jak Java.

W aplikacji jest tylko jedna klasa Application i tylko jedno wystąpienie każdego z jej pomocników (zgodnie z leniwym ładowaniem, jak powyżej). Jasne, singletony są złe, ale z drugiej strony tylko wtedy, gdy nie trzymają się prawdziwego świata. W moim przykładzie tak.

Źródłem zła są stereotypowe „zasady”, takie jak „singletony są złe”, są dla leniwych ludzi, którzy nie chcą myśleć samodzielnie.

Tak, wiem, manifest PHP jest ZŁY, technicznie rzecz biorąc. Jednak jest to język odnoszący sukcesy na swój hackerski sposób.

Uzupełnienie

Jeden styl funkcji:

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
Odrzuciłem odpowiedź, ponieważ uważam, że sugerowanie pojedynczego wzorca do rozwiązania problemu jest sprzeczne z solidnymi zasadami OOP.
koen

1
@koen: to, co mówisz, jest prawdą, ogólnie rzecz biorąc, ALE o ile zrozumiałem jego problem, mówi o pomocnikach dla aplikacji, aw aplikacji jest tylko jedna ... uhm, aplikacja.
Flavius

Uwaga: to, co przedstawiłem, nie jest nawet prawdziwym singletonem. Singleton pozwoliłby tylko na jedno wystąpienie klasy, definiując konstruktor jako prywatny. Jest to tylko zmienna „globalna” dostępna dla wszystkich instancji „Aplikacji”. Dlatego uważam, że jego ważność NIE pomija dobrych zasad OOP. Oczywiście, jak wszystko na świecie, ten „wzorzec” również nie powinien być nadużywany.
Flavius

-1 ode mnie też. Może to tylko połowa Singleton DP, ale jest brzydka: „zapewnij do niej globalny dostęp”.
po prostu ktoś

2
To rzeczywiście sprawia, że ​​jego obecne podejście jest znacznie czystsze.
Daniel Von Fange

15

Podoba mi się koncepcja Dependency Injection:

„Dependency Injection polega na tym, że składniki otrzymują zależności za pośrednictwem swoich konstruktorów, metod lub bezpośrednio w polach. (Z witryny Pico Container )”

Fabien Potencier napisał naprawdę fajną serię artykułów o Dependency Injection i potrzebie ich stosowania. Oferuje również ładny i mały pojemnik na iniekcje Dependency Injection o nazwie Pimple, z którego bardzo lubię korzystać (więcej informacji na github ).

Jak wspomniano powyżej, nie podoba mi się używanie Singletonów. Dobre podsumowanie tego, dlaczego Singletony nie są dobrym projektem, można znaleźć tutaj na blogu Steve'a Yegge'a .


Podoba mi się implementacja poprzez Zamknięcia w PHP, bardzo ciekawa lektura
Juraj Blahunka

Ja też mam
Thomas

2
miejmy nadzieję, że mainstreamowe strony internetowe wkrótce przejdą na PHP 5.3, ponieważ wciąż nie jest powszechne, aby zobaczyć w pełni funkcjonalny serwer php 5.3
Juraj Blahunka

Będą musieli, gdy coraz więcej projektów wymaga PHP 5.3, takich jak Zend Framework 2.0, będzie to framework.zend.com/wiki/display/ZFDEV2/…
Thomas

1
Dependency injection przyjęto również odpowiedź na pytanie dotyczące decupling from GOD object: stackoverflow.com/questions/1580210/… z bardzo ładnym przykładem
Juraj Blahunka

9

Najlepszym podejściem jest posiadanie pewnego rodzaju pojemnika na te zasoby. Niektóre z najczęstszych sposobów implementacji tego kontenera :

Singel

Niezalecane, ponieważ jest trudne do przetestowania i sugeruje stan globalny. (Zapalenie singletonitis)

Rejestr

Eliminuje singletonitis, błąd, którego też nie polecam, ponieważ jest to też rodzaj singletona. (Trudny test jednostkowy)

Dziedzictwo

Szkoda, w PHP nie ma dziedziczenia wielokrotnego, więc ogranicza to wszystko do łańcucha.

Wstrzyknięcie zależności

To lepsze podejście, ale szerszy temat.

Tradycyjny

Najprostszym sposobem na to jest użycie iniekcji konstruktora lub ustawiającego (obiekt zależności pass za pomocą settera lub w konstruktorze klasy).

Ramy

Możesz rzucić swój własny wstrzykiwacz zależności lub użyć niektórych struktur iniekcji zależności, np. Yadif

Zasób aplikacji

Możesz zainicjować każdy z zasobów w aplikacji ładującej (która działa jak kontener) i uzyskać do nich dostęp w dowolnym miejscu aplikacji uzyskując dostęp do obiektu ładowania początkowego.

To jest podejście zaimplementowane w Zend Framework 1.x

Moduł ładujący zasoby

Rodzaj statycznego obiektu, który ładuje (tworzy) potrzebny zasób tylko wtedy, gdy jest potrzebny. To bardzo sprytne podejście. Możesz zobaczyć to w akcji, np. Implementując komponent Dependency Injection w Symfony

Iniekcja do określonej warstwy

Zasoby nie zawsze są potrzebne w aplikacji. Czasami po prostu są potrzebne np. W sterownikach (MV C ). Wtedy możesz wstrzyknąć zasoby tylko tam.

Typowym podejściem do tego jest używanie komentarzy docblock w celu dodania metadanych wstrzykiwania.

Zobacz moje podejście do tego tutaj:

Jak używać wstrzykiwania zależności w Zend Framework? - Przepełnienie stosu

Na koniec chciałbym dodać uwagę o bardzo ważnej rzeczy - buforowaniu.
Ogólnie rzecz biorąc, pomimo wybranej techniki, należy pomyśleć o sposobie buforowania zasobów. Pamięć podręczna będzie sama w sobie zasobem.

Aplikacje mogą być bardzo duże, a ładowanie wszystkich zasobów przy każdym żądaniu jest bardzo kosztowne. Istnieje wiele podejść, w tym ten appserver-in-php - Project Hosting on Google Code .


6

Jeśli chcesz, aby obiekty były dostępne globalnie, wzorzec rejestru może być dla Ciebie interesujący. Aby uzyskać inspirację, zajrzyj do Zend Registry .

Więc także kwestia Rejestr kontra Singleton .


Jeśli nie chcesz używać Zend Framework, oto ładna implementacja wzorca rejestru dla PHP5: phpbar.de/w/Registry
Thomas

Wolę wpisany wzorzec rejestru, taki jak Registry :: GetDatabase („master”); Registry :: GetSession ($ user-> SessionKey ()); Registry :: GetConfig („lokalny”); [...] i zdefiniowanie interfejsu dla każdego typu. W ten sposób upewniasz się, że przypadkowo nie nadpisujesz klucza używanego do czegoś innego (tj. Możesz mieć „główną bazę danych” i „główną konfigurację”. Używając interfejsów, upewniasz się, że używane są tylko prawidłowe obiekty. można również zaimplementować przy użyciu wielu klas rejestru, ale imho jedna jest prostsza i łatwiejsza w użyciu, ale nadal ma zalety.
Morfildur

Lub oczywiście ten wbudowany w PHP - $ _GLOBALS
Gnuffo1

4

Obiekty w PHP zajmują dużo pamięci, jak zapewne zauważyłeś na podstawie testów jednostkowych. Dlatego idealnym rozwiązaniem jest jak najszybsze zniszczenie niepotrzebnych obiektów, aby zaoszczędzić pamięć dla innych procesów. Mając to na uwadze, stwierdzam, że każdy przedmiot pasuje do jednej z dwóch form.

1) Obiekt może mieć wiele przydatnych metod lub musi być wywoływany więcej niż raz, w którym to przypadku implementuję singleton / rejestr:

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

2) Obiekt istnieje tylko przez cały okres istnienia metody / funkcji wywołującej go, w którym to przypadku proste utworzenie jest korzystne, aby zapobiec utrzymywaniu obiektów przy życiu przez długotrwałe odniesienia do obiektów.

$object = new Class();

Przechowywanie tymczasowych obiektów GDZIEKOLWIEK może prowadzić do wycieków pamięci, ponieważ można zapomnieć o odwołaniach do nich o utrzymywaniu obiektu w pamięci przez resztę skryptu.


3

Wybrałbym funkcję zwracającą zainicjalizowane obiekty:

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

W środowisku testowym można zdefiniować zwracanie makiet. Możesz nawet wykryć wewnątrz, kto wywołuje funkcję za pomocą debug_backtrace () i zwraca różne obiekty. Możesz zarejestrować w nim, kto chce uzyskać jakie obiekty, aby uzyskać wgląd w to, co faktycznie dzieje się w twoim programie.


Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.