W projekcie PHP, jakie wzorce istnieją do przechowywania, dostępu i organizowania obiektów pomocniczych? [zamknięte]

Jak organizować i zarządzać obiektami pomocniczymi, takimi jak silnik bazy danych, powiadomienia użytkowników, obsługa błędów i tak dalej w projekcie opartym na PHP?

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

  • obiekt bazy danych
  • Zarządzanie użytkownikami
  • API do tworzenia/modyfikowania / usuwania elementów
  • obiekt do wyświetlania wiadomości do użytkownika końcowego
  • obsługa kontekstu, która przenosi cię w prawo Strona
  • Klasa paska nawigacyjnego pokazująca przyciski
  • a logging object
  • możliwe, niestandardowa obsługa błędów

Itd.

Mam do czynienia z odwiecznym pytaniem, jak najlepiej uczynić te obiekty dostępnymi dla każdej części systemu, która ich potrzebuje.

Moim pierwszym apporach, wiele lat temu, było posiadanie $application global, która zawierała zainicjalizowane instancje tych klas.

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

Potem zmieniłem na wzór Singletona i fabrykę Funkcja:

$mh =&factory("messageHandler");
$mh->addMessage("Item successfully inserted");
Ale z tego też nie jestem zadowolony. Testy jednostkowe i hermetyzacja stają się dla mnie coraz ważniejsze, a w moim rozumieniu logika stojąca za globalami/singletonami niszczy podstawową ideę OOP.

Wtedy jest oczywiście możliwość podania każdemu obiektowi kilku wskaźników do potrzebnego obiektu pomocniczego, prawdopodobnie najczystszego, oszczędzającego zasoby i przyjaznego dla testowania sposobu, ale mam wątpliwości co do tego, czy jest to możliwe w dłuższej perspektywie.

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

Chciałbym poszerzyć horyzonty na temat tego, jakie są tu wspólne wzorce. Szukam przykładów, dodatkowych pomysłów i wskazówek do zasobów, które omawiają to z {39]} długoterminowej, świat rzeczywisty perspektywa.

Również, jestem zainteresowany, aby usłyszeć o specialized, niszowy lub zwykły dziwny podejścia do problemu.

Author: Pekka 웃, 2009-11-28

8 answers

Unikałbym podejścia Singletona sugerowanego przez Flawiusza. Istnieje wiele powodów, aby uniknąć tego podejścia. Narusza dobre zasady OOP. Blog google testing ma kilka dobrych artykułów na temat Singleton i jak uniknąć it:

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ługodawca

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

  2. Zależność wstrzyknięcie

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

    Oraz 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

Implementacja dependency injection (DI):

  • Uważam, że powinieneś zapytać, co jest potrzebne. w konstruktorze obiektu do funkcji: new YourObject($dependencyA, $dependencyB);

  • Możesz ręcznie podać potrzebne obiekty (zależności) ($application = new Application(new MessageHandler()). Ale można również użyć framework DI (strona wikipedia zawiera linki do PHP framework DI).

    Ważne jest to, że przekazujesz tylko to, czego faktycznie używasz( wywołujesz akcję), a nie to, co po prostu przekazujesz innym obiektom, ponieważ tego potrzebują. Oto ostatni post z "wujek Bob" (Robert Martin) manual DI vs using framework.

Jeszcze kilka przemyśleń na temat rozwiązania Flawiusza. Nie chcę, aby ten post był anty-postem, ale myślę, że ważne jest, aby zobaczyć, dlaczego dependency injection jest, przynajmniej dla mnie, lepsze niż globals.

Mimo, że nie jest to implementacja "prawdziwa" Singleton , wciąż myślę, że Flawiusz pomylił się. Stan Globalny jest zły . Należy zauważyć, że takie rozwiązania wykorzystują również trudne do przetestowania metody statyczne .

I know a wielu ludzi to robi, zatwierdza i używa. Ale czytanie artykułów na blogu Misko Heverys (ekspert google testability ), ponowne przeczytanie go i powolne trawienie tego, co mówi, zmieniło sposób, w jaki postrzegam projektowanie.

Jeśli chcesz mieć możliwość przetestowania aplikacji, musisz przyjąć inne podejście do projektowania aplikacji. Po pierwszym testowaniu będziesz miał problemy z takimi rzeczami: "następnie chcę zaimplementować logowanie tego kawałka kodu; napiszmy najpierw test, który rejestruje podstawową wiadomość, a następnie wymyślić test, który zmusza cię do pisania i używania globalnego rejestratora, którego nie można zastąpić.

Wciąż zmagam się z 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 mowy, żebym mógł wrócić do tego, co robiłem wcześniej (tak, globalny stan i Singletony (Duże S)) po tym, jak zrozumiałem, co Misko mówił: -)

 68
Author: koen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-06-22 02:41:40
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ę, szanuje zasady OOP,jest mniej kodu niż to, co robisz teraz, a obiekt jest tworzony tylko wtedy, gdy kod potrzebuje go po raz pierwszy.

Uwaga: to, co przedstawiłem, nie jest nawet prawdziwym wzorem Singletona. Singleton pozwala tylko na jedną instancję, definiując konstruktor (Foo::_ _ constructor ()) jako prywatny. To tylko " globalny" zmienna 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 cokolwiek na świecie, ten "wzór" nie powinien być nadużywany!

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

Dodatek Dla tych spośród was, którzy martwią się o TDD, możecie jeszcze nadrobić trochę okablowanie do dependency-inject it. Może 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 poprawę. To tylko PoC, użyj wyobraźni.

Dlaczego tak to robisz? Cóż, przez większość czasu aplikacja nie będzie testowana jednostkowo, będzie uruchamiana, , miejmy nadzieję, że w środowisku produkcyjnym. Siłą PHP jest jego szybkość. PHP nie jest i nigdy nie będzie "czystym językiem OOP", jak Java.

Wewnątrz aplikacji istnieje tylko jedna klasa aplikacji i tylko jedna instancja każdego z jego pomocników, co najwyżej (jak na leniwe ładowanie jak powyżej). Jasne, singletony są złe, ale z drugiej strony, tylko jeśli nie przylegają do prawdziwego świata. w moim przykładzie tak.

Stereotypowe "zasady", takie jak "singletony są złe", są źródłem zła, są dla leniwych ludzi, którzy nie chcą myśleć za siebie.

Tak, wiem, Manifest PHP jest zły, technicznie rzecz biorąc. Jednak jest to język udany, w swoim hackish sposób.

Dodatek

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();
 16
Author: Flavius,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-05-19 19:22:44

Podoba mi się pojęcie iniekcji zależności:

"Dependency Injection to miejsce, w którym komponenty otrzymują swoje zależności za pomocą konstruktorów, metod lub bezpośrednio do pól. (From Pico Container Website)"

Fabien Potenier napisał naprawdę ładną serię artykułów na temat iniekcji zależności i potrzeby ich użycia. Oferuje również ładny i mały pojemnik do wstrzykiwania zależności 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 dlaczego Singletony nie są dobrym projektem można znaleźć tutaj na blogu Steve 'a Yegge' a.

 15
Author: Thomas,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-01-23 15:27:04

Najlepszym podejściem jest posiadanie pewnego rodzaju kontenera dla tych zasobów. Niektóre z najczęstszych sposobów implementacji tego kontenera :

Singleton

Nie zaleca się, ponieważ jest trudny do przetestowania i implikuje stan globalny. (Singletonitis)

Rejestr

Eliminuje singletonitis, bug też nie polecam, bo to też rodzaj Singletona. (Trudny do jednostkowego testu)

Dziedziczenie

Szkoda, nie ma wielokrotności dziedziczenie w PHP, więc ogranicza to wszystko do łańcucha.

Dependency injection

To lepsze podejście, ale większy temat.

Tradycyjny

Najprostszym sposobem jest użycie konstruktora lub iniekcji settera (przekazać obiekt zależności za pomocą settera lub w konstruktorze klasy).

Ramy

Możesz uruchomić własny dependency injector lub użyć niektórych frameworków dependency Injector, np. Yadif

Zastosowanie resource

Możesz zainicjować każdy ze swoich zasobów w aplikacji bootstrap (która działa jak kontener) i uzyskać do nich dostęp w dowolnym miejscu w aplikacji uzyskującej dostęp do obiektu bootstrap.

Jest to podejście zaimplementowane w Zend Framework 1.x

Resource loader

Rodzaj obiektu statycznego, który ładuje (tworzy) potrzebny zasób tylko wtedy, gdy jest potrzebny. To bardzo mądre podejście. Możesz to zobaczyć w akcji np. implementacja zastrzyk zależności Symfony komponent

Wstrzyknięcie do określonej warstwy

Zasoby nie zawsze są potrzebne w dowolnym miejscu aplikacji. Czasami po prostu potrzebujesz ich np. w kontrolerach (MV C ). Następnie możesz wstrzyknąć zasoby tylko tam.

Powszechnym podejściem jest używanie komentarzy docblock do dodawania metadanych iniekcji.

Zobacz moje podejście do tego tutaj:

Jak używać dependency injection w Zend Framework? - Przepełnienie Stosu

In the end, Chciałbym tutaj dodać notatkę o bardzo ważnej rzeczy - buforowaniu.
Ogólnie rzecz biorąc, pomimo wybranej techniki, powinieneś pomyśleć, w jaki sposób zasoby będą buforowane. Pamięć podręczna będzie samym zasobem.

Aplikacje mogą być bardzo duże, a ładowanie wszystkich zasobów na każde żądanie jest bardzo kosztowne. Istnieje wiele podejść, w tym ten appserver-in-PHP - Project Hosting na Google Code.

 9
Author: takeshin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:16:29

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

Więc równieżRegistry vs. Singleton pytanie.

 6
Author: Felix Kling,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:33:52

Obiekty w PHP zajmują sporą ilość pamięci, co zapewne widać po testach jednostkowych. Dlatego jest idealnym rozwiązaniem, aby zniszczyć niepotrzebne obiekty jak najszybciej , Aby zapisać 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 użytecznych metod lub musi być wywołany więcej niż raz w tym przypadku zaimplementuję singleton / registry:

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

2) obiekt istnieje tylko dla życia wywołanie metody/funkcji w takim przypadku proste tworzenie jest korzystne, aby zapobiec utrzymywaniu obiektów zbyt długo przy życiu.

$object = new Class();

Przechowywanie obiektów tymczasowych w dowolnym miejscu może prowadzić do wycieków pamięci, ponieważ odniesienia do nich mogą zostać zapomniane o przechowywaniu obiektu w pamięci przez resztę skryptu.

 4
Author: Xeoncross,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-01-28 04:22:54

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

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

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

 3
Author: Kamil Szot,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-03-22 10:57:56

Dlaczego nie przeczytać dobrego podręcznika?

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

 -1
Author: gcb,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-01-29 15:40:26