Pomoc - Szukaj - Użytkownicy - Kalendarz
Pełna wersja: Sens stosowania singleton w php
Forum PHP.pl > Forum > PHP
Jarod
Piszę sobie klasę do obsługi bazy danych i przyglądam się tak bardzo popularnemu wzorcowi singleton. Wszyscy jako przykład użyteczności tego wzorca podają przykład tworzenia instancji klasy do obsługi bazy, tzn połączenia z bazą. Zacząłem się zastanawiać gdzie jest sens, oczywiście w języku php. Zakładam, że mam taką klasę i w skrypcie łącze się z bazą danych, jeśli nie ma nawiązanego połączenia to tworzę obiekt tej klasy a jeśli jest to nie tworzę.

Wszystko jasne. Tylko gdzie jest sens stosowania tego w php. Przecież w skrypcie zazwyczaj tworzy się połączenie z bazą, wykonuje potrzebne operacje na bazie a po zakończeniu wywoływania skryptu, połączenie i tak jest zamykane automatycznie.. Więc jaki jest sens stosowania tego w php?
dr_bonzo
To nie chodzi o to masz obiekt polaczenia dostepny miedzy kolejnymi wykonaniami skrytpu, tylko gdy potrzebujesz w wyswietleni jednej strony w kilku miejscach wykonac zapytania do bazy to masz opcje:
- wszedie pisac (np.) mysql_connect(...)
- ciagle przekazywac w argumentach funckji raz utworzony obiekt polaczenia
- uzyc singletona -- wystarczy DB::getInstance() i masz polaczenie, z kazdego poziomu zagniezdzenia funkcji, bez koniecznosci podawani paranetrow polaczenia
- uzyc rejestru, tworzysz obiekt DB, wrzucasz go do rejestru (ktory moze przechowywac mnostwo innych wszedzie-potrzebnych-obiektow), i pobierasz gdy potrzebujesz; od singletona rozni sie tym ze mozesz wymienic polaczenie na inne; jest bardziej OOP gdyz singleton to taki obiekt globalny, a przeciez globale sa BE smile.gif
Jarod
Przecież można raz wywołać mysql_connect a zwrócone uchwyt przypisać do zmiennej statycznej.. Wtedy nie trzeba drugi raz tworzyć połączenia. Ale czy to nie będzie mało OOP?
Cysiaczek
Tak, ale bardzo często w bardziej rozbudowanych aplikacjach odwołanie do obiektu DB jest przezroczyste, tzn, że nie podajesz konkretnego połączenia, tylko "prosisz" warstwę danych o konkretne rekordy z konkretnych tabel. Ba! Nie interesuje Cię nawet, z jaką bazą masz do czynienia. Twoja warstwa danych z kolei jak nie znajdzie bazy danych, lub nie zdoła się połączyć, może pobrać dane z pliku .txt, lub .xml. Zaloguje przy tym kto i w jaki sposób dobierał się do danych. Teraz przy tak skomplikowanym systemie spróbuj używać wszędzie DB::query($sql, "mysql") itp. Połączeń zresztą może być wiele (różne bazy danych np.). Trzeba je upchać zatem w tablicę... a może lepiej stworzyć kolekcję obiektów połączenia? Jak to zrobisz na korzystając ze składowych statycznych? Możliwości są setki, a użycie statycznej składowej to rozwiązanie najmniej skomplikowane.

Pozdrawiam.
Jarod
Cytat(Cysiaczek @ 15.01.2007, 11:53:13 ) *
Tak, ale bardzo często w bardziej rozbudowanych aplikacjach odwołanie do obiektu DB jest przezroczyste, tzn, że nie podajesz konkretnego połączenia, tylko "prosisz" warstwę danych o konkretne rekordy z konkretnych tabel.

No ale gdzieś musi być wskazane z jakiego połączenia chcesz skorzystać, bo w przeciwnym przypadku można się przejechać (np w skrypcie gdzieś po drodze nastąpiło połączenie z inną bazą i wtedy zostanie użyte ostatnie nawiązane połączenie, a my chcemy inne).

Cytat(Cysiaczek @ 15.01.2007, 11:53:13 ) *
Teraz przy tak skomplikowanym systemie spróbuj używać wszędzie DB::query($sql, "mysql")

Typ bazy mam zapisany w pliku konfiguracyjnym i w nim zmieniam tyb bazy z której chcę korzystać.
Nie widzę też problemu, żeby do pobrania wyników korzystać z $oDatabase->query($handle, $query). Przecież to nic złego.

Cytat(Cysiaczek @ 15.01.2007, 11:53:13 ) *
itp. Połączeń zresztą może być wiele (różne bazy danych np.). Trzeba je upchać zatem w tablicę... a może lepiej stworzyć kolekcję obiektów połączenia? Jak to zrobisz na korzystając ze składowych statycznych? Możliwości są setki, a użycie statycznej składowej to rozwiązanie najmniej skomplikowane.


Z tą zmienną statyczną to chodziło mi o to, że zamiast pisać całego singletona definiujesz tylko zmienną statyczną i jej przypisujesz uchwyt połączenia. I zamiast pisać kilka linijek singletona piszesz jeden warunek, oraz możesz używać konstruktora do sprawdzania parametrów..
Cysiaczek
Ale nie o to chodzi w Singletonie. To jest wzorzec do rozprowadzania i udostępniania tego samego obiektu wielu innym klasom i obiektom. To, że wiele osób zaleca go do udostępniania obiektu DB, to nie znaczy, że nie można stosować alternatywnych rozwiązań. Jeśli chcesz udostępniać tylko wskaźnik połączenia, to nie musisz robić do tego singletona. Możesz tak jak mówisz zrobić static $zmienna, albo wogóle global $zmienna. Wartości Singletona nie zobaczysz w małym skrypcie, albo w systemie www pisanym strona po stronie. Zobaczysz za to w bardzo rozproszonej, polimorficznej aplikacji, gdzie 20 klas operuje na jednym i tym samym obiekcie (np mapa pliku konfiguracyjnego) wierząc, że nie może być inaczej, bo gdyby było - system by padł.

Najważniejsza jest zasada: Nie będziesz tego potrzebował oraz Jeśli nie widzisz problemu, nie korzystaj ze wzorca na siłę

Pozdrawiam.
Jarod
Cytat(Cysiaczek @ 15.01.2007, 13:19:17 ) *
Jeśli chcesz udostępniać tylko wskaźnik połączenia, to nie musisz robić do tego singletona.

A co innego udostępnia singleton? Przecież chodzi właśnie o uchwyt połączenia..

Nigdy nie stosuję czegoś na siłę. Pytam się bo chcę rozważyć na ile może być to przydatne.
ActivePlayer
Singleton pozwala Ci uzyskać dostęp do obiektu w kazdym miejscu Twojej aplikacji. Nie wazne czy to uchwyt bazy danych, czy obiekt config czy cokolwiek co sobie wymyślisz.

Jeśli chcesz zrobić obiektową otoczkę Twojego korzystania z bazy danych najlepiej narysuj sobie schemat jaki obiekt ma za co odpowiadać. Ja generalnie stosowałem 3 obiekty.
1. DB_Connection - to był obiekt oparty właśnie o singleton, wewnątrz niego trzymałem uchwyt do db. Były tez tam 'logi' zapytań i inne pierdoły.
2. DB_Query - obiekt zapytania. Robiłem wtedy tak:
  1. <?php
  2. $query = new DB_Query("SELECT *...", DB_Connection::getInstance());
  3. $query->execute();
  4. ?>

3. DB_Result - to co zwróciła funkcja execute, w zaleznosci od zapytania wiersze albo inne bajery.

Singletona uzywalem w tym przypadku po to zeby mieć łatwy dostęp do połączenia z bazą. Dochodzi jeszcze kolejna kwestia, czy będziesz uzywać kilku baz danych na raz? bo jeśli tak - ja założyłem ze nie będę - to musisz inaczej zaprojektować swój system np.
  1. <?php
  2. $conn1 = new DB_Connection('localhost',...);
  3. $conn2 = new DB_Connectiion('otherhost', ...);
  4. registry::register('conn1', $conn1); 
  5. registry::register('conn2', $conn2); 
  6. // i potem gdzieś w kodzie
  7. $query = new DB_Query('...', registry::registered('conn1'));
  8. // lub jesli odwolujesz sie do drugiej bazy
  9. $query = new DB_Query('...', registry::registered('conn2'));
  10. ?>

Do tego powinienneś przemyśleć dodatkowo czy będziesz korzystał tylko z baz typu mysql? Jeśli nie wtedy DB_Connection powinno być interfejsem, a implementować i używać powinienneś DB_Connection_Mysql, DB_Connection_Pgsql itd...

Jest bardzo duzo zależności, musisz sam wszystko przemyśleć, a wzorce to tylko sposób implementacji. Radze Ci lepiej zacznij od poznania problemu, a dopiero potem szukaj rozwiązania. Bo tutaj pytasz o pewien sposób użycia singletonu, a nie do końca jestem pewny czy wiesz do czego chcesz go użyć.
Jarod
Cytat(ActivePlayer @ 15.01.2007, 19:27:39 ) *
Jeśli chcesz zrobić obiektową otoczkę Twojego korzystania z bazy danych najlepiej narysuj sobie schemat jaki obiekt ma za co odpowiadać. Ja generalnie stosowałem 3 obiekty.
1. DB_Connection - to był obiekt oparty właśnie o singleton, wewnątrz niego trzymałem uchwyt do db. Były tez tam 'logi' zapytań i inne pierdoły.
2. DB_Query - obiekt zapytania. Robiłem wtedy tak:
  1. <?php
  2. $query = new DB_Query("SELECT *...", DB_Connection::getInstance());
  3. $query->execute();
  4. ?>

3. DB_Result - to co zwróciła funkcja execute, w zaleznosci od zapytania wiersze albo inne bajery.

Singletona uzywalem w tym przypadku po to zeby mieć łatwy dostęp do połączenia z bazą.

Moim zdaniem niepotrzebnie rozbijałeś to na 3 obiekty. Ja chcę zamknąć wszystko w jednej klasie.

Cytat(ActivePlayer @ 15.01.2007, 19:27:39 ) *
Dochodzi jeszcze kolejna kwestia, czy będziesz uzywać kilku baz danych na raz? bo jeśli tak - ja założyłem ze nie będę - to musisz inaczej zaprojektować swój system np.
  1. <?php
  2. $conn1 = new DB_Connection('localhost',...);
  3. $conn2 = new DB_Connectiion('otherhost', ...);
  4. registry::register('conn1', $conn1); 
  5. registry::register('conn2', $conn2); 
  6. // i potem gdzieś w kodzie
  7. $query = new DB_Query('...', registry::registered('conn1'));
  8. // lub jesli odwolujesz sie do drugiej bazy
  9. $query = new DB_Query('...', registry::registered('conn2'));
  10. ?>

Do tego powinienneś przemyśleć dodatkowo czy będziesz korzystał tylko z baz typu mysql? Jeśli nie wtedy DB_Connection powinno być interfejsem, a implementować i używać powinienneś DB_Connection_Mysql, DB_Connection_Pgsql itd...

Ja wymyśliłem to trochę w inny sposób. Najwyżej podczas testów klasy stwierdzę, że trzeba coś zmienić. Mam interfejs który zawiera wymagane metody. Teraz każda klasa (osobna dla każdego rodzaju bazy) implementuje ten interfejs. W pliku konfiguracyjnym mam zapisane z jakiej bazy korzystam i na podstawie tego zapisu będzie wykorzystywana odpowiednia klasa do połączenia. Ale zacząłem się zastanawiać czy nie będę chciał korzystać z kilku różnych baz, np w jednej aplikacji pobierać dane z bazy pgsql na serwerze xxx.xxx.xxx.xxx i zapisywać w bazie mysql na localhost (to tylko przykład).. To mój sposób z plikiem konfiguracyjnym nie jest za dobry. Oczywiście można by w pliku konfiguracyjnym definiować konfiguracje dla dwóch baz, ale co jeśli będę chciał korzystać z 10 różnych (hardcorowy przykład ale staram się nie ograniczać).. Jeszcze się nad tym zastanowię. Ale tutaj całkiem rozsądne jak nie jedyne słuszne rozwiązanie to wzorzec registry.

Cytat(ActivePlayer @ 15.01.2007, 19:27:39 ) *
Jest bardzo duzo zależności, musisz sam wszystko przemyśleć, a wzorce to tylko sposób implementacji. Radze Ci lepiej zacznij od poznania problemu, a dopiero potem szukaj rozwiązania. Bo tutaj pytasz o pewien sposób użycia singletonu, a nie do końca jestem pewny czy wiesz do czego chcesz go użyć.

Już się nad tym zastanawiałem i wiem co chcę osiągnąć. Natomiast pytam się o singletona bo wbrew opiniom innym nie widzę sensu stosowania go w php. CHYBA, że w jednym skrypcie (pojedynczym bo nie ma co ukrywać ale każda aplikacja w php to kolejne skrypty) korzystamy z kilku różnych baz - wtedy moim zdaniem singleton się sprawdzi. Ale jeśli korzystamy tylko z jednej bazy, to stosowanie singletona moim zdaniem jest przerostem formy nad treścią.
ActivePlayer
mozesz przedstawic interfejs swoich przemyśleń?
Jarod
Cytat(ActivePlayer @ 15.01.2007, 20:26:00 ) *
mozesz przedstawic interfejs swoich przemyśleń?

Bardzo chętnie ale na razie mam go w głowie. Jak to napiszę to udostępnię kod. Może coś doradzicie.
ActivePlayer
a mozesz w jakis sposob przedstawic Twoje przemyslenia?
Jarod
Na razie mój pomysł dotyczy korzystania w aplikacji z jednej bazy.

Przykładowy plik konfiguracyjny:
Kod
[Db]
Type        = mysql
Host        = 127.0.0.1
Port        = 3306
User        = user
Password    = pass
Base        = test2


Interfejs:
  1. <?php
  2. interface Cube_DatabaseInterface
  3. {
  4. public function __construct();
  5. public function connect();
  6. public function query();
  7. public function update()
  8. // i inne potrzebne metody
  9. }
  10. ?>


Klasa do obsługi Mysql:
  1. <?php
  2. class Cube_Mysql implements Cube_DatabaseInterface
  3. {
  4.  //bedzię zawierać metody wymagane przez interfejs
  5. }
  6. ?>



  1. <?php
  2. class Cube_Postgresql implements Cube_DatabaseInterface
  3. {
  4.  //bedzię zawierać metody wymagane przez interfejs
  5. }
  6. ?>


Wszystkie metody będę w ten sam sposób wywoływane (te same parametry) aby zachować możliwość zmiany klasy beż konieczności przerabiania kodu aplikacji.

Sposób tworzenia obiektu zależeć będzie od sposobu implementacji klasy. Mam klasę Config (wzorowałem się na sposobie hwao - uprościłem ją i zmieniłem), która parsuje plik ini i tworzy klasę z właściwościmi statycznymi, która jest dołączana do aplikacji.

Wywołanie będzie się odbywać mniej więcej w taki sposób.

  1. <?php
  2. if ($Db::Type == 'mysql') require_once('Cube_Mysql.class.php');
  3. else require_once('Cube_Pgsql.class.php');
  4. ?>

Jeśli napiszę więcej klas do obsługi innych baz przerobi się to na switch'a

W ten sposób to widze. Problemem może być tak jak pisałem wyżej, jeśli zechcemy korzystać w jednej aplikacji z kilku różnych baz. Można też zamiast if lub switch dołączać klasę w ten sposób:
  1. <?php
  2. require_once('Cube_'.$DbType.'class.php');
  3. ?>

ale to już drobnostka..
ActivePlayer
czyli bedzie mniejwięcej tak:
  1. <?php
  2. class Cube_Mysql implements Cube_DatabaseInterface
  3. {
  4.  //bedzię zawierać metody wymagane przez interfejs
  5.  // ...
  6.  public function query($sql){
  7. // mniej więcej
  8. return mysql_query($sql);
  9.  }
  10. }
  11. ?>

? moze to glupie pytanie ale co jeśli będziesz chciał zrobić zapytanie w pętli? Stworzysz drugą instancję Cube_Mysql? (wiem ze tego się nie uzywa ale teoretyzuję).
Jarod
Cytat(ActivePlayer @ 15.01.2007, 21:17:18 ) *
? moze to glupie pytanie ale co jeśli będziesz chciał zrobić zapytanie w pętli? Stworzysz drugą instancję Cube_Mysql? (wiem ze tego się nie uzywa ale teoretyzuję).

Stosowanie zapytań w pętli jest tak samo ble jak global. Ale skoro teoretyzujemy to.. hmm.. nie widzę problemu (być może nie widzę problemu) ale ja chcę to rozwiązać tak.

  1. <?php
  2. $oDB = new Cube_Mysql($aParameters); //aParameters to parametry połączenia
  3.  
  4. while (true)
  5. {
  6.  $oDB->query($squery);
  7. }
  8. ?>
ActivePlayer
co innego mialem na mysli:
  1. <?php
  2. $oDB = new Cube_Mysql($aParameters); //aParameters to parametry połączenia
  3. $res = $oDB->query($squery);
  4. while ($row = $oDB->fetch($res))// tak chcesz fetchowac wiersze po kolei?
  5. {
  6. // tu wstaw drugie zapytanie,
  7.  }
  8. ?>
Jarod
Ale w czym widzisz problem?

  1. <?php
  2. $oDB = new Cube_Mysql($aParameters); //aParameters to parametry połączenia
  3. $res = $oDB->query($squery);
  4. while ($row = $oDB->fetch($res))// tak chcesz fetchowac wiersze po kolei?
  5. {
  6. $res2 = $oDB->query($squery);
  7.  }
  8. ?>
To jest wersja lo-fi głównej zawartości. Aby zobaczyć pełną wersję z większą zawartością, obrazkami i formatowaniem proszę kliknij tutaj.
Invision Power Board © 2001-2025 Invision Power Services, Inc.