Witaj Gościu! ( Zaloguj | Rejestruj )

Forum PHP.pl

 
Reply to this topicStart new topic
> Ocena podejścia do Dependency Injection, PDO + OOP + Dependency injection
marcinq123
post 5.01.2015, 20:54:55
Post #1





Grupa: Zarejestrowani
Postów: 6
Pomógł: 0
Dołączył: 21.01.2013

Ostrzeżenie: (0%)
-----


Witam, uczę się OOP, zacząłem dla testów pisać klasę obsługująca bazę na podstawie PDO ( Trudno to nazwać DIC ). I proszę Was o ocenę czy w dobrą stronę to zmierza czy może podejście mam złe i później sobie skomplikuje życie ?

"Klasa DIC"

  1. class DataBaseConnection
  2. {
  3. protected $pdo;
  4. protected $mysqlHost = 'Host';
  5. protected $mysqlLogin = 'Login';
  6. protected $mysqlPassword = 'Haslo do bazy';
  7. protected $mysqlDatabase = 'Baza';
  8. public function __construct()
  9. {
  10.  
  11. try
  12. {
  13. $this->pdo = new PDO('mysql:dbname='.$this->mysqlDatabase.';host='.$this->mysqlHost.';', $this->mysqlLogin, $this->mysqlPassword);
  14. $this->pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
  15.  
  16. }
  17. catch(PDOException $exception)
  18. {
  19. echo $exception->getMessage();
  20. }
  21. }
  22.  
  23. public function prepare($sql)
  24. {
  25. return $this->pdo->prepare($sql);
  26. }
  27.  
  28. }


Pomijając kwestię iż trzymam dane do połączenia w klasie, czy ma mniej więcej tak to wyglądać ? Ciekawi mnie dlaczego musiałem zdefiniować funkcję prepare ... jak jej nie dopiszę to wywala mi później że nie istnieje ;x

Klasa User:

  1. class User
  2. {
  3. private $login;
  4. private $query;
  5. public function __construct($baza)
  6. {
  7.  
  8. $this->pdo = $baza;
  9.  
  10. }
  11.  
  12. public function selectUser($user)
  13. {
  14. $this->login = $user;
  15.  
  16. $this->query = $this->pdo->prepare('SELECT * FROM users WHERE login =:login');
  17. $this->query->bindParam(':login', $this->login, PDO::PARAM_STR);
  18. $this->query->execute();
  19.  
  20. if($this->query->rowCount() > 0)
  21. {
  22.  
  23. $row=$this->query->fetch();
  24. $this->nazwa = $row['data'];
  25. }else{echo ' dupa ';}
  26. }
  27.  
  28.  
  29.  
  30. public function getUserData()
  31. {
  32.  
  33. return $this->nazwa;
  34.  
  35. }
  36.  
  37. }


Kod napisany tylko żeby sprawdzić czy się uruchamia.

A wywołuje metody tak:

  1. $user = new User(new DataBaseConnection());
  2.  
  3. $user->selectUser('jakisUser');
  4. echo $user->getUserData();


Ogólnie działa, no chyba że skasuje metode prepare w klasie DataBaseConnection to wtedy już nie ;p Tylko pytanie jak już wcześniej napisałem czy dobrą drogą idę ?
Go to the top of the page
+Quote Post
Turson
post 5.01.2015, 21:38:56
Post #2





Grupa: Zarejestrowani
Postów: 4 291
Pomógł: 829
Dołączył: 14.02.2009
Skąd: łódź

Ostrzeżenie: (0%)
-----


Pokaż var_dump($baza) w konstruktorze klasy User. Siłą rzeczy nie powinieneś mieć dostępu do własności $pdo klasy DataBaseConnection bo jest ona chroniona
Go to the top of the page
+Quote Post
Damonsson
post 5.01.2015, 21:45:58
Post #3





Grupa: Zarejestrowani
Postów: 2 355
Pomógł: 533
Dołączył: 15.01.2010
Skąd: Bydgoszcz

Ostrzeżenie: (0%)
-----


Takie trochę na siłę to DI. Do każdego obiektu będziesz musiał przekazywać to połączenie, wg mnie bez sensu. A to raczej sam model sobie powinien dziedziczyć po DataBaseConnection i tyle.
Go to the top of the page
+Quote Post
marcinq123
post 5.01.2015, 22:00:21
Post #4





Grupa: Zarejestrowani
Postów: 6
Pomógł: 0
Dołączył: 21.01.2013

Ostrzeżenie: (0%)
-----


Cytat(Turson @ 5.01.2015, 21:38:56 ) *
Pokaż var_dump($baza) w konstruktorze klasy User. Siłą rzeczy nie powinieneś mieć dostępu do własności $pdo klasy DataBaseConnection bo jest ona chroniona


  1. object(DataBaseConnection)#2 (5) { ["pdo":protected]=> object(PDO)#3 (0) { } ["mysqlHost":protected]=> string(13) "Host" ["mysqlLogin":protected]=> string(8) "Login" ["mysqlPassword":protected]=> string(16) "Haslo do bazy" ["mysqlDatabase":protected]=> string(8) "Baza" }


Cytat(Damonsson @ 5.01.2015, 21:45:58 ) *
Takie trochę na siłę to DI. Do każdego obiektu będziesz musiał przekazywać to połączenie, wg mnie bez sensu. A to raczej sam model sobie powinien dziedziczyć po DataBaseConnection i tyle.


Chciałem sobie uprościć... W tym przypadku extend rozumiem. Ale dajmy na to, chciałbym stworzyć klasę rejestracyjną, logowanie, obsługa profilu klienta, panel admina ... etc. Miałbym je po prostu dziedziczyć po klasie obsługującej bazę ?
Bo chyba nie Sigleton'em miałbym to rozwiązać ?

Broń Boże się nie wymądrzam, po prostu nie jestem pewien jak najlepiej rozwiązać operacje na bazie ... Albo inaczej, jak najszybciej załapać.

Ten post edytował marcinq123 5.01.2015, 22:01:49
Go to the top of the page
+Quote Post
Turson
post 5.01.2015, 22:02:23
Post #5





Grupa: Zarejestrowani
Postów: 4 291
Pomógł: 829
Dołączył: 14.02.2009
Skąd: łódź

Ostrzeżenie: (0%)
-----


Właśnie Singleton się do tego nadaje.
Go to the top of the page
+Quote Post
marcinq123
post 5.01.2015, 22:08:22
Post #6





Grupa: Zarejestrowani
Postów: 6
Pomógł: 0
Dołączył: 21.01.2013

Ostrzeżenie: (0%)
-----


No właśnie, ogólnie sobie poczytałem ... i w sumie zdania są podzielone ... sporo osób odradza singletona ... albo odradza używania źle napisanego singletona ;p
Pomyślałem nad właśnie DI ...
Go to the top of the page
+Quote Post
Damonsson
post 5.01.2015, 22:22:01
Post #7





Grupa: Zarejestrowani
Postów: 2 355
Pomógł: 533
Dołączył: 15.01.2010
Skąd: Bydgoszcz

Ostrzeżenie: (0%)
-----


Jeżeli to jest po prostu zwykły model, to musisz mieć połączenie z bazą, więc po wymuszać przekazywanie czegoś, bez czego jak nie przekażesz to nie będzie działać? A skoro musisz to przekazać to takie użycie DI, jest wg mnie średnim pomysłem.
Dokładnie, miałbyś je po prostu dziedziczyć, użyć Singleton i tyle. Czasami ludzie próbują na siłę doszukiwać się problemów, z tymi wadami Singleton.

Ten post edytował Damonsson 5.01.2015, 22:23:45
Go to the top of the page
+Quote Post
Crozin
post 6.01.2015, 12:41:19
Post #8





Grupa: Zarejestrowani
Postów: 6 476
Pomógł: 1306
Dołączył: 6.08.2006
Skąd: Kraków

Ostrzeżenie: (0%)
-----


Z Twoim kodem wszystko jest źle i nie ma to wiele wspólnego z Dependency Injection - niestety podobnie jest z późniejszymi postami w tym wątku.

1. Po co istnieje klasa DataBaseConnection skoro nie robi ona absolutnie nic poza obcięciem możliwości "surowego" PDO? Przecież możesz bezpośrednio korzystać z obiektu PDO w swoim kodzie - nie musisz tego robić przez obiekt pośredniczący.
2. Jak już, to konstruktor klasy DataBaseConnection powinien przyjąć obiekt PDO w swoim argumencie, a nie tworzyć go samemu - wtedy mielibyśmy do czynienia właśnie z DI.
3. Jeżeli nie jesteś wstanie zrobić nic z wyjątkiem, to go nie wyłapuj.
4. Musiałeś zdefiniować dla klasy DataBaseConnection metodę prepare bo dlaczego niby miałaby ona istnieć sama z siebie? Obiekt PDO udostępnia prepare(), query(), beginTransaction() i masę innych, nie DataBaseConnection.
5. W klasie User już poprawnie przekazałeś obiekt połączenia z zewnątrz. Jedynie niepotrzebnie na siłę wykorzystujesz właściwości login czy query bo tam możesz skorzystać ze zwykłych zmiennych lokalnych.

Czyli generalnie powinno to wyglądać to mniej-więcej tak:
  1. // Klasa DataBaseConnection w ogóle do kosza.
  2.  
  3. class User
  4. {
  5. private $pdo;
  6.  
  7. private $name;
  8.  
  9. public function __construct(PDO $pdo)
  10. {
  11. $this->pdo = $pdo;
  12. }
  13.  
  14. public function select($login)
  15. {
  16. $stmt = $this->pdo->prepare('SELECT ... ');
  17. $stmt->bind(..., $login);
  18. $stmt->execute();
  19.  
  20. if ($stmt->rowCount() === 0) {
  21. throw new ...Exception('...');
  22. }
  23.  
  24. $data = $stmt->fetch();
  25.  
  26. $this->name = $data['name'];
  27. }
  28.  
  29. public function getName()
  30. {
  31. return $this->name;
  32. }
  33. }
Pomijam tutaj już fakt, że wybieranie danych z bazy i ich reprezentacja w formie jednego obiektu klasy User jest bardzo złym podejściem. Powinieneś mieć raczej jeden obiekt przeznaczony do reprezentowania użytkownika (User) i drugi do wybierania takowych np. z bazy danych, czyli tzw. "model" (UserRepository).

Jeszcze co do bzdur wypisanych w postach:
1. W żadnym wypadku klasa typu user nie powinna dziedziczyć po połączeniu z bazą danych, bo: (a) nie jest takowym, (cool.gif dojdzie do tego że będziesz miał otwartych po kilka połączeń na raz - kompletnie niepotrzebnie.
2. Jeżeli w jakiejś klasie potrzebujesz mieć dostęp do bazy danych to obiekt PDO powinieneś właśnie przekazać w konstruktorze.
3. Użycie Singetonu dla "klasy od bazy danych" to idiotyzm: (a) przecież może istnieć wiele równoległych połączeń do różnych baz danych(exclamation.gif!), (cool.gif niweczysz tym wszelkie zalety płynące z IoC (DI)
4. Model nie jest synonimem operacji na bazie danych. Ta ostatnia jest w ogóle nie potrzebna. Przecież dane mogą pochodzić z dziesiątek innych źródeł, a tak w ogóle to model nie oznacza tylko wybierania danych, ale również i ich przetwarzanie.
Go to the top of the page
+Quote Post
pyro
post 6.01.2015, 13:16:13
Post #9





Grupa: Zarejestrowani
Postów: 2 148
Pomógł: 230
Dołączył: 26.03.2008

Ostrzeżenie: (0%)
-----


@Crozin, +1 . Miałem to samo wymienić, ale nie chciało mi się tyle pisać biggrin.gif


--------------------
ET LINGUA EIUS LOQUETUR IUDICIUM
Go to the top of the page
+Quote Post
marcinq123
post 6.01.2015, 14:01:38
Post #10





Grupa: Zarejestrowani
Postów: 6
Pomógł: 0
Dołączył: 21.01.2013

Ostrzeżenie: (0%)
-----


Prawdę mówiąc, po cichu liczyłem na Twoją odpowiedź Crozin ;]

Z racji tego iż chcę nabrać dobrych nawyków, spróbuje podejść kompleksowo.

Cytat
4. Musiałeś zdefiniować dla klasy DataBaseConnection metodę prepare bo dlaczego niby miałaby ona istnieć sama z siebie? Obiekt PDO udostępnia prepare(), query(), beginTransaction() i masę innych, nie DataBaseConnection.

No właśnie zdefiniowałem tylko prepare() a np. execute(), bindParam() już nie musiałem bo działało ... i dlatego nie rozumiem czemu.


Cytat
Pomijam tutaj już fakt, że wybieranie danych z bazy i ich reprezentacja w formie jednego obiektu klasy User jest bardzo złym podejściem. Powinieneś mieć raczej jeden obiekt przeznaczony do reprezentowania użytkownika (User) i drugi do wybierania takowych np. z bazy danych, czyli tzw. "model" (UserRepository).


Cenna uwaga, ale napisałem przykład z pierwszego postu tylko żeby sprawdzić czy w ogóle to działa.

  1. // Klasa DataBaseConnection w ogóle do kosza.
  2.  
  3. class User
  4. {
  5. private $pdo;
  6.  
  7. private $name;
  8.  
  9. public function __construct(PDO $pdo)
  10. {
  11. $this->pdo = $pdo;
  12. }
  13.  
  14. public function select($login)
  15. {
  16. $stmt = $this->pdo->prepare('SELECT ... ');
  17. $stmt->bind(..., $login);
  18. $stmt->execute();
  19.  
  20. if ($stmt->rowCount() === 0) {
  21. throw new ...Exception('...');
  22. }
  23.  
  24. $data = $stmt->fetch();
  25.  
  26. $this->name = $data['name'];
  27. }
  28.  
  29. public function getName()
  30. {
  31. return $this->name;
  32. }
  33.  
  34. //oraz inne metody
  35. }


Czyli teraz odwołuje się tak ?:


  1. $user = new User(new PDO('mysql:dbname=base;host=host;', 'user', 'password'));
  2. $user->select('user');
  3. echo $user->getName();


Moje pytanie brzmi, dajmy na to miałbym klasy User ... Admin, Auth, etc. ( nie wiem co tam jeszcze wymyśle tongue.gif)
Każdy obiekt generuje ( w razie potrzeby połączenia z bazą) poprzez

  1. $admin= new Admin(new PDO('mysql:dbname=base;host=host;', 'user', 'password'));
  2. //jakieś wywoływane metody


A czy takie podejście żeby się nie powtarzać ma sens?
  1. $connection = new PDO('mysql:dbname=base;host=host;', 'user', 'password'));
  2. $admin= new Admin($connection);





A do reszty się nie ustosunkuje bo ze wstydu nie wiem nawet co powiedzieć oneeyedsmiley02.png
Go to the top of the page
+Quote Post
Crozin
post 6.01.2015, 14:14:06
Post #11





Grupa: Zarejestrowani
Postów: 6 476
Pomógł: 1306
Dołączył: 6.08.2006
Skąd: Kraków

Ostrzeżenie: (0%)
-----


Cytat
No właśnie zdefiniowałem tylko prepare() a np. execute(), bindParam() już nie musiałem bo działało ... i dlatego nie rozumiem czemu.
Twoja metoda DataBaseConnection::prepare() zwraca na dobrą sprawę to co zwraca PDO::prepare() czyli nowy obiekt, klasy PDOStatement - to on zawiera metody execute(), bindParam() i inne. Nie ma on już kompletnie nic wspólnego z Twoim kodem.

Cytat
A czy takie podejście żeby się nie powtarzać ma sens?
Tak, ma to sens - tak właśnie powinno to wyglądać. Bo tutaj nie chodzi nawet o powtarzanie się, a o jednokrotne nawiązanie połączenia i wykorzystywanie go w wielu różnych miejscach, czyli:
  1. $conn = new PDO(....);
  2.  
  3. $user = new User($conn);
  4. $sth = new Sth($conn);
  5. $userSth = new UserSth($conn, $user);
  6. // itp.
Go to the top of the page
+Quote Post
marcinq123
post 6.01.2015, 14:33:51
Post #12





Grupa: Zarejestrowani
Postów: 6
Pomógł: 0
Dołączył: 21.01.2013

Ostrzeżenie: (0%)
-----


Na tą chwile zostały rozwiane moje wątpliwości ;]

Dziękuje wielkie za pomoc panowie.
Go to the top of the page
+Quote Post
Damonsson
post 6.01.2015, 15:54:44
Post #13





Grupa: Zarejestrowani
Postów: 2 355
Pomógł: 533
Dołączył: 15.01.2010
Skąd: Bydgoszcz

Ostrzeżenie: (0%)
-----


Cytat(Crozin @ 6.01.2015, 12:41:19 ) *
1. W żadnym wypadku klasa typu user nie powinna dziedziczyć po połączeniu z bazą danych, bo: (a) nie jest takowym, (cool.gif dojdzie do tego że będziesz miał otwartych po kilka połączeń na raz - kompletnie niepotrzebnie.
2. Jeżeli w jakiejś klasie potrzebujesz mieć dostęp do bazy danych to obiekt PDO powinieneś właśnie przekazać w konstruktorze.
3. Użycie Singetonu dla "klasy od bazy danych" to idiotyzm: (a) przecież może istnieć wiele równoległych połączeń do różnych baz danych(exclamation.gif!), (cool.gif niweczysz tym wszelkie zalety płynące z IoC (DI)
4. Model nie jest synonimem operacji na bazie danych. Ta ostatnia jest w ogóle nie potrzebna. Przecież dane mogą pochodzić z dziesiątek innych źródeł, a tak w ogóle to model nie oznacza tylko wybierania danych, ale również i ich przetwarzanie.

1. Jeżeli to jest zwykły model do połączenia z bazą danych to jest takowym i w każdym obiekcie i tak będzie się łączył z bazą danych i duplikował przekazywanie połączenia w konstruktorze przecież. Po to ma użyć Singleton, żeby nie miał kilku otwartych połączeń.
2. Jasne.
3. Tak sobie przeglądam klasy Doctrine zawierające własnie Singleton np w getInstance i nie widzę idiotyzmu.
4. Jasne, jeżeli zakładamy, że to ma być uniwersalny model, zgadzam się. Tutaj było pytanie o PDO.

Nie piszę po złości, masz wiedzę większą ode mnie i chętnie poczytam, dlaczego się mylę.

Choć te różnice nie są duże, głównie chodzi o to, że moje założenie jest takie, że zawsze będzie się łączył z tą bazą danych, więc może dziedziczyć, a Ty dajesz większą uniwersalność, w zamian za duplikowanie kodu (przekazywanie połączenia za każdy razem, przy tworzeniu obiektu). Pytanie, co jest poprawniejsze?
Go to the top of the page
+Quote Post
Crozin
post 6.01.2015, 19:29:22
Post #14





Grupa: Zarejestrowani
Postów: 6 476
Pomógł: 1306
Dołączył: 6.08.2006
Skąd: Kraków

Ostrzeżenie: (0%)
-----


Cytat
1. Jeżeli to jest zwykły model do połączenia z bazą danych to jest takowym i w każdym obiekcie i tak będzie się łączył z bazą danych i duplikował przekazywanie połączenia w konstruktorze przecież. Po to ma użyć Singleton, żeby nie miał kilku otwartych połączeń.
Co to jest "zwykły model"? Jeżeli przekazuje za każdym razem to samo połączenie to nie nawiąże mu się nagle kilka.
Cytat
3. Tak sobie przeglądam klasy Doctrine zawierające własnie Singleton np w getInstance i nie widzę idiotyzmu.
A jakoś dokładniej? Bo na pewno nie jest to obiekt połączenia z bazą danych.
Cytat
[...] w zamian za duplikowanie kodu (przekazywanie połączenia za każdy razem, przy tworzeniu obiektu)
Ale tu nie ma żadnego duplikowania kodu.
Go to the top of the page
+Quote Post
Damonsson
post 6.01.2015, 20:46:10
Post #15





Grupa: Zarejestrowani
Postów: 2 355
Pomógł: 533
Dołączył: 15.01.2010
Skąd: Bydgoszcz

Ostrzeżenie: (0%)
-----


1. Metody, które i tak zawsze pobierają coś z bazy danych, nie wyobrażam tam sobie nagle czegoś niezwiązanego z bazą danych. Wtedy trzeba by to rozdzielić. No tak, a jeżeli będę dziedziczył (wg mojej teorii), to muszę sprawdzić czy już nie istnieje połączenie dlatego Singleton.

2. Dalej w związku z tym jest tworzenie połączenia itd. ale właśnie samo getInstance to zwykły Singleton.
  1. /**
  2.   * Returns an instance of this class
  3.   * (this class uses the singleton pattern)
  4.   *
  5.   * @return Doctrine_Manager
  6.   */
  7. public static function getInstance()
  8. {
  9. if ( ! isset(self::$_instance)) {
  10. self::$_instance = new self();
  11. }
  12. return self::$_instance;
  13. }


3. Za każdym razem musisz się martwić, żeby przekazać to połączenie do obiektu. Tworząc odpowiednio skonkretyzowany model, wiem, że będzie on korzystał z bazy danych i w konstruktorze, tworzę sobie to połączenie od razu. Tworząc obiekt nie muszę pamiętać, aby przekazać tam połączenie z bazą.
Go to the top of the page
+Quote Post
Crozin
post 7.01.2015, 00:20:07
Post #16





Grupa: Zarejestrowani
Postów: 6 476
Pomógł: 1306
Dołączył: 6.08.2006
Skąd: Kraków

Ostrzeżenie: (0%)
-----


Doctrine 1? Ale mamy 2015 rok, środowisko PHP już znormalniało...
O niczym nie musisz pamiętać, IDE/PHP samo Ci podpowie, że robisz coś nieprawidłowego.
Go to the top of the page
+Quote Post

Reply to this topicStart new topic
1 Użytkowników czyta ten temat (1 Gości i 0 Anonimowych użytkowników)
0 Zarejestrowanych:

 



RSS Wersja Lo-Fi Aktualny czas: 27.04.2024 - 11:07