Globalny czy singleton do połączenia z bazą danych?

81

Jaka jest korzyść z używania singletona zamiast globalnego do połączeń z bazą danych w PHP? Czuję, że używanie singletona zamiast globalnego sprawia, że ​​kod jest niepotrzebnie skomplikowany.

Kod z Global

$conn = new PDO(...);

function getSomething()
{
    global $conn;
    .
    .
    .
}

Kodowanie za pomocą Singletona

class DB_Instance
{
    private static $db;

    public static function getDBO()
    {
        if (!self::$db)
            self::$db = new PDO(...);

        return self::$db;
    }
}

function getSomething()
{
    $conn = DB_Instance::getDBO();
    .
    .
    .
}

Jeśli istnieje lepszy sposób na zainicjowanie połączenia z bazą danych inny niż globalny lub pojedynczy, wspomnij o tym i opisz zalety, jakie ma w porównaniu z globalnym lub pojedynczym.

Imran
źródło
Jeśli planujesz używać PDO w niestandardowych
programach

Odpowiedzi:

105

Wiem, że to stare, ale odpowiedź Dr8k była prawie gotowa .

Kiedy rozważasz napisanie fragmentu kodu, załóż, że to się zmieni. Nie oznacza to, że zakładasz rodzaje zmian, które w pewnym momencie przyniesie to w przyszłości, ale raczej, że nastąpi jakaś zmiana.

Niech to będzie cel, złagodzenie bólu związanego z wprowadzaniem zmian w przyszłości: globalny jest niebezpieczny, ponieważ trudno nim zarządzać w jednym miejscu. Co się stanie, jeśli w przyszłości chcę uwrażliwić kontekst połączenia z bazą danych? Co jeśli chcę, aby zamykał się i otwierał ponownie co piąty raz, gdy był używany. Co się stanie, jeśli zdecyduję, że w celu skalowania mojej aplikacji chcę użyć puli 10 połączeń? A może konfigurowalna liczba połączeń?

Fabryka Singleton daje taką elastyczność. Skonfigurowałem to z bardzo małą dodatkową złożonością i zyskuję więcej niż tylko dostęp do tego samego połączenia; Uzyskuję możliwość zmiany sposobu, w jaki to połączenie jest później do mnie przekazywane w prosty sposób.

Zauważ, że mówię o pojedynczej fabryce, a nie po prostu o singletonie . To prawda, że ​​istnieje niewielka różnica między singletonem a globalnym. Z tego powodu nie ma powodu, aby mieć pojedyncze połączenie: dlaczego miałbyś spędzać czas na konfigurowaniu tego, skoro zamiast tego możesz utworzyć zwykły globalny?

To, co daje ci fabryka, to powód, dla którego chcesz uzyskać połączenia, i oddzielne miejsce, w którym możesz zdecydować, jakie połączenia (lub połączenie) otrzymasz.

Przykład

class ConnectionFactory
{
    private static $factory;
    private $db;

    public static function getFactory()
    {
        if (!self::$factory)
            self::$factory = new ConnectionFactory(...);
        return self::$factory;
    }

    public function getConnection() {
        if (!$this->db)
            $this->db = new PDO(...);
        return $this->db;
    }
}

function getSomething()
{
    $conn = ConnectionFactory::getFactory()->getConnection();
    .
    .
    .
}

Następnie, w ciągu 6 miesięcy, gdy Twoja aplikacja jest bardzo znana, zostaje dugg and slashdotted, a Ty zdecydujesz, że potrzebujesz więcej niż jednego połączenia, wszystko, co musisz zrobić, to zaimplementować kilka pul w metodzie getConnection (). Lub jeśli zdecydujesz, że potrzebujesz opakowania, które implementuje rejestrowanie SQL, możesz przekazać podklasę PDO. Lub jeśli zdecydujesz, że chcesz mieć nowe połączenie przy każdym wywołaniu, możesz to zrobić. Jest elastyczny zamiast sztywnego.

16 linii kodu, w tym nawiasy klamrowe, co pozwoli Ci zaoszczędzić wiele godzin na refaktoryzacji do czegoś niesamowicie podobnego w przyszłości.

Zauważ, że nie uważam tego „Pełzania funkcji”, ponieważ nie wykonuję żadnej implementacji funkcji w pierwszej turze. To pogranicze „Future Creep”, ale w pewnym momencie pomysł, że „kodowanie na jutro dzisiaj” jest zawsze złą rzeczą, nie podoba mi się.

Jon Raphaelson
źródło
3
Nie jestem pewien, ale myślę, że miałeś na myśli: funkcja publiczna getConnection () {if (! $ This-> db) $ this-> db = new PDO (...); return $ this-> db; }
Dycey
Dzięki! Czy straciłbym jakiekolwiek korzyści ze stosowania tej metody, używając return self::$factory->getConnection();zamiast return self::$factory;?
Nico Burns
3
Chciałbym użyć tego kodu w projekcie, który robię. Czy mogę cytować tę stronę, a jeśli tak, na jakiej licencji jest ten tekst? Czy to CC-BY, BSD czy coś innego? Obecnie określam to jako „Nieznane - uważam, że domena publiczna”, ale chciałbym przypisać do niego prawidłowe warunki licencji.
JonTheNiceGuy
2
Myślę, że w metodzie getConnection () powinniśmy zrobić "$ db" "$ this-> db", w przeciwnym razie "zmienna prywatna $ db" nie jest używana ", do której nie ma oficjalnego odniesienia.
deweloper10
Cześć, Twoje rozwiązanie wygląda świetnie i jest skalowalne. Daj mi znać, co należy zaimplementować tutaj self :: $ factory = new ConnectionFactory (...);
Ananda
16

Nie jestem pewien, czy potrafię odpowiedzieć na twoje konkretne pytanie, ale chciałem zasugerować, że obiekty połączeń globalnych / pojedynczych mogą nie być najlepszym pomysłem, jeśli tak jest w przypadku systemu internetowego. Systemy DBMS są generalnie zaprojektowane do zarządzania dużą liczbą unikalnych połączeń w efektywny sposób. Jeśli używasz globalnego obiektu połączenia, robisz kilka rzeczy:

  1. Zmuszanie stron do wykonywania wszystkich połączeń z bazą danych po kolei i zabijanie wszelkich prób asynchronicznego ładowania stron.

  2. Potencjalne utrzymywanie otwartych blokad na elementach bazy danych dłużej niż to konieczne, spowalniając ogólną wydajność bazy danych.

  3. Maksymalizacja łącznej liczby jednoczesnych połączeń, które Twoja baza danych może obsługiwać, i blokowanie dostępu do zasobów nowym użytkownikom.

Jestem pewien, że są też inne potencjalne konsekwencje. Pamiętaj, że ta metoda będzie próbować utrzymać połączenie z bazą danych dla każdego użytkownika uzyskującego dostęp do serwisu. Jeśli masz tylko jednego lub dwóch użytkowników, nie ma problemu. Jeśli jest to publiczna witryna internetowa i chcesz mieć ruch, skalowalność stanie się problemem.

[EDYTOWAĆ]

W sytuacjach o większej skali tworzenie nowych połączeń za każdym razem, gdy trafisz na bazę danych, może być złe. Jednak odpowiedzią nie jest tworzenie połączenia globalnego i ponowne wykorzystywanie go do wszystkiego. Odpowiedzią jest pule połączeń.

Dzięki buforowaniu połączeń obsługiwanych jest wiele różnych połączeń. Gdy aplikacja wymaga połączenia, pierwsze dostępne połączenie z puli jest pobierane, a następnie zwracane do puli po wykonaniu zadania. Jeśli zażądano połączenia i żadne nie jest dostępne, nastąpi jedna z dwóch rzeczy: a) jeśli maksymalna liczba dozwolonych połączeń nie zostanie osiągnięta, zostanie otwarte nowe połączenie lub b) aplikacja będzie zmuszona czekać, aż połączenie stanie się dostępne .

Uwaga: W językach .Net pule połączeń są domyślnie obsługiwane przez obiekty ADO.Net (parametry połączenia ustawiają wszystkie wymagane informacje).

Dzięki Crad za skomentowanie tego.

Dr8k
źródło
Wydaje mi się, że użycie singletona daje mi przewagę w inicjowaniu połączenia tak późno, jak to możliwe, podczas gdy gdy używam global, prawdopodobnie zainicjowałbym prawie na początku skryptu. Mam rację?
Imran,
Prawidłowo, gdybyś poszedł tą drogą, singleton zapewniłby tę przewagę.
Dr8k
Właściwie to wszystko zależy od skali. W przypadku wdrożeń internetowych na dużą skalę ogromne ilości połączeń z bazą danych są złe. Właśnie dlatego istnieją aplikacje takie jak pgBouncer dla PostgreSQL, a Java umożliwia łączenie zasobów.
Gavin M. Roy,
To prawda, ale myślę, że prawidłowe buforowanie połączeń różni się od zwykłego używania globalnych obiektów połączenia. Pule połączeń nadal korzystają z wielu połączeń, po prostu ograniczają maksymalną liczbę i używają ich ponownie w czasie, aby zmniejszyć narzut konfiguracji.
Dr8k
5
Zwróć uwagę, że „global” w przypadku PHP nie powoduje, że zmienna jest globalna na wszystkich stronach PHP. Oznacza to po prostu, że można uzyskać do niego dostęp z poziomu funkcji.
Ates Goral
7

Metoda singleton została utworzona, aby upewnić się, że istnieje tylko jedno wystąpienie dowolnej klasy. Ale ponieważ ludzie używają go jako sposobu na skrócenie globalizacji, staje się znane jako leniwe i / lub złe programowanie.

Dlatego zignorowałbym global i Singleton, ponieważ oba nie są tak naprawdę OOP.

To, czego szukałeś, to zastrzyk zależności .

Możesz sprawdzić łatwe do odczytania informacje oparte na PHP związane z wstrzykiwaniem zależności (z przykładami) na http://components.symfony-project.org/dependency-injection/trunk/book/01-Dependency-Injection


źródło
3

Oba wzorce osiągają ten sam efekt netto, zapewniając jeden punkt dostępu do wywołań bazy danych.

Jeśli chodzi o konkretną implementację, singleton ma niewielką zaletę polegającą na tym, że nie inicjuje połączenia z bazą danych, dopóki co najmniej jedna z innych metod nie zażąda tego. W praktyce w większości napisanych przeze mnie aplikacji nie robi to dużej różnicy, ale jest to potencjalna zaleta, jeśli masz strony / ścieżki wykonywania, które w ogóle nie wykonują żadnych wywołań bazy danych, ponieważ te strony nie kiedykolwiek zażądać połączenia z bazą danych.

Jeszcze jedna drobna różnica polega na tym, że globalna implementacja może nieumyślnie podeptać inne nazwy zmiennych w aplikacji. Jest mało prawdopodobne, że kiedykolwiek przypadkowo zadeklarujesz inne globalne odniesienie $ db, chociaż możliwe jest, że możesz je przypadkowo nadpisać (powiedzmy, piszesz if ($ db = null), gdy zamierzałeś napisać if ($ db == null). Pojedynczy obiekt zapobiega temu.

Adam Ness
źródło
2

Jeśli nie zamierzasz używać trwałego połączenia, a są przypadki, w których tego nie robisz, uważam, że singleton jest koncepcyjnie bardziej akceptowalny niż globalny w projektowaniu obiektów obiektowych.

W prawdziwej architekturze OO singleton jest bardziej efektywny niż tworzenie nowej instancji obiektu za każdym razem.

Gavin M. Roy
źródło
2

W podanym przykładzie nie widzę powodu, aby używać singletonów. Z reguły, jeśli moim jedynym zmartwieniem jest zezwolenie na pojedyncze wystąpienie obiektu, jeśli pozwala na to język, wolę używać globali

Dprado
źródło
1

Generalnie użyłbym singletona do połączenia z bazą danych ... Nie chcesz tworzyć nowego połączenia za każdym razem, gdy potrzebujesz interakcji z bazą danych ... Może to zaszkodzić wydajności i przepustowości twojej sieci ... Po co tworzyć nowy, jeśli jest dostępny ... Tylko moje 2 centy ...

RWendi

RWendi
źródło
Inicjując połączenie w zakresie globalnym, inicjuję połączenie raz na stronę i używam tej zmiennej globalnej w funkcjach, które wymagają interakcji z bazą danych.
Imran,
Używanie singletona do połączenia z bazą danych to nie to samo, co brak ponownego tworzenia połączenia po każdej interakcji z DBMS. Singleton jest właśnie tym; jedna instancja danej klasy i jedyna, która może istnieć globalnie. Może być konieczne jednoczesne połączenie się z różnymi bazami danych.
Rob
odnosiłem się do klasy pojedynczej, która zarządza połączeniami z bazą danych. Nie widzę sensu tworzenia nowego obiektu połączenia za każdym razem, gdy chcesz wejść w interakcję z dbms. Oczywiście, jeśli chcesz jednocześnie połączyć się z inną bazą danych, może być konieczne utworzenie innego obiektu połączenia.
RWendi,
0

To całkiem proste. Nigdy nie używaj global OR Singleton.

1800 INFORMACJE
źródło
4
Jeśli chodzi o wzór Singleton, zawsze mów nigdy
1800 INFORMACJA
3
A jeśli chcesz mieć więcej niż jednego dostawcę dzienników? Kto powiedział, że nie mogę zalogować się do pliku i do konsoli?
1800 INFORMACJA
2
Więc możesz wtedy połączyć jeden anty-wzór (Singleton) z innym (God Object)
1800 INFORMACJA
3
Tak. Podobnie robią projektanci wszystkich głównych języków OOP, zezwalając na globalne obiekty klas. Jeśli jesteś monoteistą i chcesz, aby obiekt reprezentował Boga, to czego szukasz to pojedynczy obiekt boga. Wszystko inne jest nieprawidłowe.
Steve Jessop,
4
Gdybym był monteistą i chciałbym stworzyć obiekt Boga, mógłbym określić użycie wzorca MonotheistAbstractFactory do stworzenia mojego obiektu Boga? Pozwoliłoby to użytkownikom politeistycznym na użycie mojego programu również przez określenie PoliteistAbstractfactory.
1800 INFORMACJA
0

Zgodnie z radą, zarówno singleton, jak i globalne są ważne i można je łączyć w ramach tego samego systemu, projektu, wtyczki, produktu itp ... W moim przypadku tworzę produkty cyfrowe dla sieci (wtyczki).

Używam tylko singletona w głównej klasie i używam go z zasady. Prawie go nie używam, ponieważ wiem, że klasa główna nie utworzy jej ponownie

<?php // file0.php

final class Main_Class
{
    private static $instance;
    private $time;

    private final function __construct()
    {
        $this->time = 0;
    }
    public final static function getInstance() : self
    {
        if (self::$instance instanceof self) {
            return self::$instance;
        }

        return self::$instance = new self();
    }
    public final function __clone()
    {
        throw new LogicException("Cloning timer is prohibited");
    }
    public final function __sleep()
    {
        throw new LogicException("Serializing timer is prohibited");
    }
    public final function __wakeup()
    {
        throw new LogicException("UnSerializing timer is prohibited");
    }
}

Globalne zastosowanie dla prawie wszystkich klas drugorzędnych, na przykład:

<?php // file1.php
global $YUZO;
$YUZO = new YUZO; // YUZO is name class

podczas gdy w czasie wykonywania mogę użyć Global do wywołania ich metod i atrybutów w tej samej instancji, ponieważ nie potrzebuję innej instancji mojej głównej klasy produktu.

<?php // file2.php
global $YUZO;
$YUZO->method1()->run();
$YUZO->method2( 'parameter' )->html()->print();

Dostaję z globalnym to użycie tej samej instancji, aby produkt działał, ponieważ nie potrzebuję fabryki dla instancji tej samej klasy, zwykle fabryka instancji jest przeznaczona dla dużych systemów lub do bardzo rzadkich celów.

In conclusion:, musisz, jeśli już dobrze rozumiesz, że jest to Singleton anty-wzorzec i rozumiesz Global , możesz użyć jednej z 2 opcji lub je mieszać, ale jeśli nie zalecam nadużywania, ponieważ jest wielu programistów, którzy są bardzo wyjątkowi i wierni programowania OOP, używaj go do klas głównych i drugorzędnych, których często używasz w czasie wykonywania. (Oszczędza dużo procesora). 😉

Lenin Zapata
źródło