Pomoc - Szukaj - Użytkownicy - Kalendarz
Pełna wersja: pytanie o PDO i simpletony
Forum PHP.pl > Forum > PHP > Object-oriented programming
dżozef
Ogarnąłem już PDO i używam statycznej zmiennej do połączenia się z bazą. Tutaj przykładowa klasa: https://gist.github.com/mprz/6cce0e6dd51e4796209c
Tworząc kilka klas niejako uzależniam je od klasy odpowiedzialnej za tworzenie połączenia. Czy to na pewno idealne rozwiązanie?
Druga sprawa: gdzie inkludować ową klasę DB? W każdej z klas która jej używa, czy w programie, który korzysta z obu klas (tak robię obecnie)?
vincent vega
Nie wiem czy idealne ale takie rozwiązanie jest poprawne. Nie potrzebujesz w aplikacji webowej kilku połączeń do tej samej bazy danych. Gdybyś przez przypadek tworzył takowe np w jakiejś pętli to mogło by się to skończyć błedem ze strony serwera bazy danych (Too many connections). Dlatego potrzebujesz je sobie zachować i mieć do niego dostęp w całej aplikacji, singleton jak najbardziej tu pasuje. Przyjętym standardem jest używanie autoładowania klas więc zacznij go używać. Jeżeli nie to z formalnego punktu widzenia powinna być includowana w każym pliku który go używa smile.gif
Crozin
@vincent vega: Jak najbardziej można korzystać w jednej aplikacji webowej z wielu połączeń do baz(y) danych, więc nie wprowadzaj w błąd.
@dżozef: Użycie tutaj singletonu to jedno z najgorszych możliwych rozwiązań. Zwyczajnie przekaż obiekt PDO (czy tam DB) jako argument konstruktora.
vincent vega
@crozin Nie napisałem że nie możesz mieć kilku połączeń - przytoczyłem nawet błąd jaki może się z tym wiązać. Napisałem że nie potrzebujesz tworzyć kilku połączeń, zazwyczaj wystarczy jedno. Dlaczego singleton jest tu niby nie na miejscu? Przekazywanie takowego połączenia jest niepraktyczne. Czy zauwazyłeś aby tak zachwywało się Doctrine, Hibernate czy SQLAlchemy ? Całość można podciąnąć nawet wyżej, zazwyaczaj będziesz chciał mieć jedego entity managera na wątek.
Crozin
Cytat
Napisałem że nie potrzebujesz tworzyć kilku połączeń, zazwyczaj wystarczy jedno.
Zazwyczaj... a gdy w końcu przychodzi konieczność skorzystania z kolejnego jest ogromny problem ze zrobieniem tego w cywilizowany sposób. Nie wspominając już o tak trywialnych rzeczach jak chociażby próba napisania testów pod taki kod.
Cytat
Dlaczego singleton jest tu niby nie na miejscu?
Bo Singleton tworzy "twardą", ukrytą zależność pomiędzy kompletnie różnymi obiektami, a to bardzo utrudnia pracę nad kodem i niweczy większość zalet OOP.
Cytat
Czy zauwazyłeś aby tak zachwywało się Doctrine, Hibernate czy SQLAlchemy ?
Co do SQLAlchemy się nie wypowiadam bo nie znam, ale dwa pierwsze robią dokładnie to o czym wspomniałem.
Cytat
Całość można podciąnąć nawet wyżej, zazwyaczaj będziesz chciał mieć jedego entity managera na wątek.
Trochę sobie tutaj pomieszałeś. EntityManager znany z JPA/Hibernate nie jest thread safe dlatego też powinieneś mieć osobnego EM-a dla każdego z wątków* Nie ma natomiast absolutnie żadnego przeciwwskazania by jeden wątek posiadał kilka EM-ów. To jednak już wątek z zagadnienia wielowątkowości z którą bezpośrednio w PHP właściwie nie mamy do czynienia, więc pomińmy ten temat.

* nic nie stoi też na przeszkodzie by używać jednego EM-a równocześnie przez wiele wątków, trzeba tylko samemu zatroszczyć się o odpowiednią synchronizację.
vincent vega
Cytat
Zazwyczaj... a gdy w końcu przychodzi konieczność skorzystania z kolejnego jest ogromny problem ze zrobieniem tego w cywilizowany sposób

Próbujesz rozwiązać problem który jeszcze nie istnieje. Jak się pojawi taka konieczność to trzeba będzie kod przebudować, ot nowa funkcjonalność/wymagania.

https://github.com/doctrine/doctrine1/blob/...anager.php#L262
http://docs.jboss.org/hibernate/orm/3.3/re...irstapp-helpers

Wyglądają na singletony ? Nie przypominam sobie też jakichś większych problemów w pisaniu testów pod te ormy.
Znowu nie pisałem że nie możesz mieć kilku EMów na wątek, tylko że zazwyczaj będziesz potrzebował jednego.
Wygląda jak byś upierał się ze singelton z zasady jest zły, może i jest ale są miejsca w których jego zastosowanie jest praktyczne.
Crozin
Cytat
Próbujesz rozwiązać problem który jeszcze nie istnieje. Jak się pojawi taka konieczność to trzeba będzie kod przebudować, ot nowa funkcjonalność/wymagania.
Masz świadomość istnienia problemu i wiesz że w momencie gdy on wystąpi będziesz musiał mocno ingerować w całą masę kodu uzależnionego od bazy danych. Problemu, który wcale nie jest jakimś skrajnym przypadkiem. Mimo wszystko nie widzisz głupoty w korzystaniu tutaj z tak kulawego rozwiązania, szczególnie gdy wdrożenie normalnego nie stanowi żadnego problemu? Nie mówiąc już o tym, że autor wątku szuka "idealnego" rozwiązania.
Cytat
Pierwsze to bardzo przestarzały Doctrine w wersji pierwszej - słabo napisany. W dodatku to co podałeś to Singleton dla menadżera/fabryki połączeń, a nie bezpośrednio połączenia, więc jeszcze sytuacja nie jest tak tragiczna.
Drugie to również fabryka połączeń (sesji), w dodatku jest to jedynie "śmieciowa" klasa na potrzeby tutoriala o ile mnie pamięć nie myli. W normalnym kodzie (niezależnie czy używanym przez interfejs JPA czy Hibernate'a) nie używa się takiej konstrukcji. Raczej wszędzie spotkasz się z jakąś formą wstrzykiwania EM-a/EMF-a jako zależności.

Cytat
Wygląda jak byś upierał się ze singelton z zasady jest zły, może i jest ale są miejsca w których jego zastosowanie jest praktyczne.
Singleton z zasady nie jest zły, ale bardzo często próbuje się go wykorzystać tam gdzie nie ma to najmniejszego sensu i powoduje jedynie problemy. Akurat użycie Singletonu do uzyskania połączenia z bazą danych jest obok "moja pierwsza klasa - połączenie z bazą danych" najczęściej wałkowanym tematem na forum z tej kategorii, gdzie początkujący w kółko powtarzają te same błędy.
Szymciosek
Crozin możesz coś więcej powiedzieć o błędach powtarzanych w kółko przy stosowaniu Singletonu przy połączeniu z bazą?
vincent vega
Nie ma idealnych rozwiązań. Napisałem że takie rozwiązanie jest poprawne i powszechnie stosowane. Dla purystów OOP wiele rozwiązań może być kulawych ale bezwzględne stosowanie się do wytycznych skutkuje skomplikowanym kodem. Aplikacje są zwyczajnie przeprojektowane bo próbują wyjść na wyższy poziom abstrakcji niż rzeczywisty problem który mają rozwiązywać.

W tych managerach/sesjach zaszyte jest połączenie albo namiar na pule połączeń ale dostęp jest tu statyczny i globalny. Nie poszedłbym też tak daleko i nazwał Doctrine przestarzałym. To bardzo dobry przykład ActiveRecodu, i nie wydaje mi się abyś potrafił to lepiej zaimplementować. Tutorial też śmieciowy, autorzy na starcie zachęcają do stosowania złych praktyk. Piszesz o wstrzykiwaniu zależności, które nie następuje z kosmosu, tu też masz najczęściej statyczny rejestr dostepnych usług i musisz kombinować żeby był dostepny praktycznie z każdego miejsca aplikacji. A potem na dobrą sprawę nie wiadomo gdzie które usługi są wykorzystywane i tu pojawia się problem w testowaniu.

Przyłączam się do powyższego pytania, pokaż nam jak prawidłowo to zakodzić ?
Crozin
Głownie są te błędy wynikające z użycia singletonu samego w sobie:
1. Tworzenie twardych, ukrytych zależności pomiędzy obiektami. To niesie za sobą cały szereg konsekwencji, na czele z utratą korzyści płynących ze stosowania IoC czy interface-driven design.
2. Globalny dostęp do obiektu. Singleton to nic innego jak global w otoczce OOP, a jak złą praktyką jest stosowanie zmiennych globalnych nie trzeba chyba nikomu mówić.

Singleton służy wymuszeniu istnienia co najwyżej jednej instancji danego obiektu, a nie dostępowi do niego zewsząd. Innymi słowy chodzi o uniemożliwienie utworzenia następującej konstrukcji:
  1. $a = new MyObject();
  2. $b = new MyObject();
Sytuacje, gdzie istnienie dwóch obiektów tego samego typu nie miałoby najmniejszego sensu czy wręcz mogłoby prowadzić do błędów i/lub zagrożeń są bardzo rzadkie w środowisku PHP. Ja sam nie pamiętam kiedy musiałem skorzystać z Singletonu w swoim kodzie (czy to w PHP czy Javie) - nie jestem nawet pewien czy kiedykolwiek musiałem.

EDIT:
@vincent vega: Odchodzisz od tematu. Autor pyta konkretnie o przekazywanie surowego połączenia z bazą danych (PDO), nie fabryk czy rejestrów.

Co do tego - jak to prawidłowo zrobić - szału to tutaj nie będzie, ale nie wiem czego się spodziewasz:
  1. class Domains {
  2. /* @var PDO */
  3. private $dbConn;
  4.  
  5. public function __construct(PDO $dbConn) {
  6. $this->dbConn = $dbConn;
  7. }
  8. }
vincent vega
No spodziewałem się całej otoczki OOP smile.gif Chodzi mi o sposób użycia, czy miałby wyglądać mniej więcej tak ?
  1. class DomainsController {
  2. private $config;
  3. private $dbConn;
  4.  
  5. public function getConfig() {
  6. // Czy nie przypomina to w jakis sposob singletona ?
  7. if (!is_null($this->config)) {
  8. return $this->config;
  9. }
  10. // Wczytaj konfiguracje
  11. return $this->config;
  12. }
  13.  
  14. public function getDbConn() {
  15. if (!is_null($this->dbConn)) {
  16. return $this->dbConn;
  17. }
  18. $config = $this->getConfig();
  19. $this->dbConn = new PDO($config['dsn']);
  20. // Konfiguracja polaczenia, w kazdym z kontrolerow osobno ?
  21. $this->dbConn->exec("set names utf8");
  22. return $this->dbConn;
  23. }
  24.  
  25. public function addDomain($req) {
  26. // Ten kod bedzie w kolko powtarzany, co z regula DRY ?
  27. $dao = new Domains($this->getDbConn());
  28.  
  29. $form = new DomainForm($dao);
  30. $form->save($req->getData());
  31. }
  32. }
Crozin
Zacznijmy od tego, że w tym wątku rozmawiamy o Singletonie w sensie wzorca projektowego, nie zasięgu typu singleton (czy jakkolwiek to nazwać) znanego ze wszelkich DIC-ów czy przytoczonego przez Ciebie kodu. W tych dwóch kontekstach "singleton" oznacza coś zupełnie innego.

Przykład demonstrujący "zasięg typu singleton":
  1. class Abc {
  2. private $config;
  3.  
  4. // zasięg typu singleton (singleton scope)
  5. public function getConfigA() {
  6. if ($this->config === null) {
  7. $this->config = new Config(...);
  8. }
  9.  
  10. return $this->config;
  11. }
  12.  
  13. // tzw. prototype scope - każde wywołanie to nowy obiekt
  14. public function getConfigB() {
  15. return new Config(...);
  16. }
  17. }
Jak widać klasa Config nie jest singletonem sama w sobie.

Co do kodu, który podałeś:
1. W metodzie getConfig() tworzysz obiekt-usługę o zasięgu typu "singleton", jednak sam obiekt singletonem nie jest. Takie rozwiązanie jest jak najbardziej poprawne.
2. Zarówno obiekt konfiguracji jak i połączenia z bazą danych powinien zostać utworzony poza obiektem DomainsController i do niego wstrzyknięty. Podstawa pisania poprawnego kodu: IoC. Unikniesz w ten sposób dziwnych problemów typu // Konfiguracja polaczenia, w kazdym z kontrolerow osobno ?.
3. Obiekt typu Domains posiada zwykłą zależność, która powinna być wstrzyknięta. Możesz zrobić to tak jak pokazałeś, tj. ręcznie, możesz też użyć do tego jakiegoś DIC-a.
vincent vega
No właśnie chciałem zobaczyć całą infrastrukturę zwiazaną z wstrzykiwaniem. Mój przykład był w miarę najprostszy, ale już widzimy że nie spełnia paradygmatów IoC. Zastanawiam się ilu finalnie potrzebowałbyś klas i obiektów aby osiągnąć cel, i czy rzeczywiście obszedłbyś się bez singletonów.
Crozin
Przykładowa implementacja dosyć rozbudowanego kontenera zależności: https://github.com/symfony/DependencyInjection - raptem kilka klas od faktycznego kontenera, nieco więcej "śmieci" od kompilatora (XML/YAML/PHP). I tak, można spokojnie odejść od singletonów, co więcej można spokojnie odejść od elementów statycznych, które również najczęściej są złym rozwiązaniem.
vincent vega
Dla mnie to astronautyka, uważam że jeżeli można użyć prostszego rozwiązania singleton/fabryka to nie ma sensu zamieniać wszystkiego na usługi i ich wstrzykiwanie http://www.martinfowler.com/articles/injection.html . Niedługo już nie będzie można z czystym sumieniem użyć funkcji mail czy curl żeby nie narazić sie na krytykę. A może za trzy czy cztery lata gdy symfony zostanie ponownie przepisane tym razem w duchu DDD, zaczną się pojawiać głosy tym razem że nie, że IoC nie przystaje do nowoczesnego świata. Strasznie skomplikowana rzecz to łączenie się z bazą danych smile.gif
destroyerr
Można korzystać z podanych funkcji tylko powstaje pytanie czy łatwo taki kod jest utrzymywać.
Co do przepisywania symfony "w duchu DDD" to kompletnie nie wiem o czym piszesz. DDD dotyczy jak wynika z nazwy domeny a symfony nie ma żadnych aspiracji dotykać tej części aplikacji. Zobacz co dostarcza symfony i zauważ, że nie ma w nim żadnego kodu związanego z modelem czy utrwalaniem danych. Posiada tylko i wyłącznie wsparcie dla innych bibliotek (m. in. za pomocą kontenera DI). IoC nie wyklucza się z DDD. Nie rozumiem jak może to do siebie nie przystawać skoro implementacje DDD wykorzystują IoC.
vincent vega
Może faktycznie trochę się zapędziłem z DDD, sam jeszcze nie miałem okazji w tym duchu aplikacji napisać. jedynie z tego co czytałem to daje się dosłyszeć głosy że IoC prowadzi czasami do anemicznego modelu co dla purystów jest nie do przyjęcia smile.gif Ale jak będzie to nie wiemy. Bardziej chodziło mi o to że wszystko to podlega ciągłej zmianie. W tej chwili forsowany jest DIC jak by miał tu rozwiązać nasze problemy z utrzymaniem kodu. Ale w mojej opinii tak naprawdę tego typu podejście pasuje może do 20% projektów, DDD pewnie do jeszczej mniejszej ich częśći. Rozmawiamy to o banalnym połącznieu z bazą danych w aplikacji, i szybko wylądowaliśmy na wstrzykiwaniu zależności. Naprawdę uważacie że użycie tu singletona jest jakimś karygodnym błędem, jest to niepoprawne ?
Crozin
Cytat
W tej chwili forsowany jest DIC jak by miał tu rozwiązać nasze problemy z utrzymaniem kodu. Ale w mojej opinii tak naprawdę tego typu podejście pasuje może do 20% projektów, DDD pewnie do jeszczej mniejszej ich częśći.
1. DI© ≠ IoC.
2. IoC nie jest złotym środkiem na wszystkie problemy związane z utrzymaniem kodu. Jest to jednak jeden z elementów, który pomaga rozwiązać te problemy.
3. Nie wiem skąd wziąłeś liczbę 20% ale to podejście da się wdrożyć w niemal wszystkim - nie przychodzi mi do głowy nic, gdzie skorzystanie z tego miałoby być niemożliwe albo miało by być problemem.
Cytat
Naprawdę uważacie że użycie tu singletona jest jakimś karygodnym błędem, jest to niepoprawne ?
W momencie kiedy autor wątku pyta o rozwiązanie idealne, tj. takie, które nie będzie rzucało kłód pod nogi w nietrywialnych zastosowaniach, tak jest to karygodnym błędem.
vincent vega
Tak stricte to nie są te same rzeczy, czasami tylko te kwestie są ze sobą powiązane. Pozostaje więc mieć nadzieję że autor wątku nastepną swoją aplikację zamodeluje już z użyciem DI.
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.