Pomoc - Szukaj - Użytkownicy - Kalendarz
Pełna wersja: [PHP]Dziedziczenie obiektów? Z klasy "matki"
Forum PHP.pl > Forum > Przedszkole
zielu001
Witam, tworzę zalążek aplikacji, która będzie obsługiwać inne aplikacje.

Stanąłem nad jednym zagadnieniem, możliwe że moje ubytki wiedzy wiążą się z małym niedoinformowaniem.

Otóż. Tworzę sobie loader całej aplikacji.

Plik index.php wygląda miej więcej tak że wywołuje klasę EngineInit, a w niej funkcję EngineStart.

W klasie EngineInit, w func EngineStart ładuję odpowiednie klasy, które własnie tworzę.
Dopowiednio jest to obsługa sesji, obsługa debugowania aplikacji, obsługa aplikacji, obsługa baz danch i inne

Problem polega na tym że chcę kożystać z tych obiektów tak:

  1. class EngineInit
  2. {
  3. public function EngineStart()
  4. {
  5. // [...]
  6. // Ładowanie obiektów najbardziej potrzebnych
  7. $conf = new ConfigurationInit;
  8. $db = new DataBaseLoader;
  9. $session = new SessionsLoader;
  10. // [...]
  11. }// end EngineStart
  12.  
  13. }
  14.  




Problem polega na tym że chcę użyć np. obiektu $conf w $db lub w $session i np. $db w $session..
Global nie działa ohno-smiley.gif
Ma ktoś pomysł jak to rozwiązać?

Proszę o naprowadzenie smile.gif
lobopol
Najprościej zastosować singletona http://phpedia.pl/wiki/Singleton tworzysz obiekt używając metody statycznej
zielu001
Czyli musiał bym to zrobić w taki sposób?

  1. class Singleton {
  2. private static $instance;
  3. private function __construct() {}
  4. private function __clone(){}
  5. public static function getInstance ()
  6. { if (self::$instance === null) { self::$instance = new Singleton(); } return self::$instance; } }
  7. $obiekt = Singleton::getInstance(); // questionmark.gif?
lobopol
Tak, w tym momencie gdy obiekt już był gdzieś stworzony Singleton::getInstance(); zwraca ci ten stworzony obiekt. Teraz tylko jedna instancja klasy będzie
zielu001
Niestety nie działa;

  1. $db = Singleton::getInstance();
  2. $db->query("UPDATE users, sessions SET users.user_last_visit = sessions.session_time WHERE users.id=sessions.session_user AND sessions.session_time < ".(time() - SESSION_TIMES));


I wywala: Call to undefined method Singleton::query();

Jakiś inny pomysł ?
Sephirus
A masz w ogóle tą metody query w Singleton? smile.gif

Nie może nie działać...

najprostszy singleton wygląda mniej więcej tak:
  1. class Singleton {
  2. private static $instance = null;
  3.  
  4. private static __construct() {}
  5. private static __clone() {}
  6.  
  7. public static function getInstance() {
  8. return self::$instance === null ? self::$instance = new self: self::$instance;
  9. }
  10.  
  11. // metody dalsze...
  12.  
  13. }


Singleton do takich celów został stworzony - postaraj się go zastosować poprawnie - opłaci się wink.gif
mortus
Koledze nie o to chodzi, czy i gdzie wykorzystać singleton. Jeśli nawet wykorzysta ten wzorzec, to nadal jego problem nie będzie rozwiązany.

@zielu001: Z Twojego opisu wynika, że klasa EngineInit jest niejako klasą główną, a metoda EngineStart ma zainicjować konfigurację, bazę danych i sesje, z których później będziesz korzystał. Jednak w kodzie, który nam pokazałeś tworzysz tylko zmienne lokalne, działające w obrębie metody EngineStart. Nie będziesz miał zatem możliwości posługiwania się nimi później. Dlatego klasa mogłaby/powinna wyglądać nieco inaczej:
  1. class EngineInit {
  2. private $_configuration = null;
  3. private $_database = null;
  4. private $_session = null;
  5. public function EngineStart() {
  6. $this->_configuration = new ConfigurationInit;
  7. $this->_database = new DataBaseLoader;
  8. $this->_session = new SessionsLoader;
  9. }
  10. }

Teraz przejdźmy do meritum. Jeśli obiekt klasy DatabaseLoader (i tutaj nieważne, czy jest to singleton, czy nie), czy też klasy SessionLoader musi skorzystać z danych zawartych w obiekcie klasy ConfigurationInit, to trzeba te informacje przekazać. Jak to zrobić? Otóż można posłużyć się "mechanizmem" DI (dependency injection, wstrzykiwanie zależności). Metoda EngineStart wyglądałaby zatem trochę inaczej:
  1. class EngineInit {
  2. // ...
  3. public function EngineStart() {
  4. $this->_configuration = new ConfigurationInit;
  5. $this->_database = new DataBaseLoader($this->_configuration);
  6. $this->_database = DataBaseLoader::getInstance($this->_configuration); // jeśli klasa DataBaseLoader będzie singletonem
  7. $this->_session = new SessionLoader($this->_configuration);
  8. }
  9. }

Teraz konstruktory klas DataBaseLoader i SessionLoader mogą skorzystać z informacji, jakie "posiada" obiekt klasy ConfigurationInit. Gwoli dopełnienia formalności uzupełnijmy dla przykładu klasę DataBaseLoader:
  1. // tutaj zwykła (nie będąca Singletonem) klasa DataBaseLoader
  2. class DataBaseLoader {
  3. // ...
  4. public function __construct($configuration) {
  5. $this->_connection = $this->connect($configuration->getDbHost(), $configuration->getDbUser(), $configuration->getDbPassword()); // przykład
  6. }
  7. }

Innym "mechanizmem" jest Dependency Non-Injection (uzależnianie bez wstrzykiwania). Mechanizm ten polega na zainicjowaniu "na sztywno" obiektu klasy, z której potrzebujemy skorzystać. Tak mogłaby wyglądać klasa DataBaseLoader wykorzystująca Dependency Non-Injection
  1. class DataBaseLoader {
  2. // ...
  3. private $_configuration = null
  4. public function __construct() {
  5. $this->_configuration = new ConfigurationInit;
  6. $this->_connection = $this->connect($this->_configuration->getDbHost(), $this->_configuration->getDbUser(), $this->_configuration->getDbPassword()); // przykład
  7. }
  8. }

O tym, który z przedstawionych mechanizmów wybrać musisz zdecydować sam. Dependency Injection daje nam sporą elastyczność, ponieważ przekazywane obiekty nie muszą być obiektami dokładnie jednej klasy, choć muszą spełniać założenia pewnych wzorców, które nazywamy interfejsami. Dependency Non-Injection samo w sobie nie zapewnia nam żadnej elastyczności i wymusza na nas korzystnie z dokładnie takiej, a nie innej klasy (czasami jednak właśnie tego potrzebujemy).

Wszystkie napisane wyżej klasy, to tylko przykłady. W rzeczywistości konstruktor klasy obsługującej połączenie z bazą danych nie potrzebuje wszystkich danych konfiguracyjnych, a tylko niektórych (tzn. nazwa/adres serwera, nazwa i hasło użytkownika bazy danych, czy kodowanie połączenia). Wystarczyłoby zatem przekazać konstruktorowi klasy DataBaseLoader tablicę zawierającą wymagane dane, a nie cały obiekt klasy ConfigurtionInit.

EDIT1: Poprawiłem parę literówek, ale chyba jeszcze jakieś są.
EDIT2: Problem i jego rozwiązanie z dziedziczeniem nie mają nic wspólnego.
zielu001
Można powiedzieć że o to mi chodziło, ale nie jestem do końca przekonany.

Jak widać chcę załadować wszystko w klasie głownej jak to nazwałeś i to jest racja. A potem w tych "pod klasach" ( czyli np. SessionLoader )
kożystać z tych innych "pod klas" załadowanych w głowej klasie "EngineInit";

Czyli coś takiego:

  1. class EngineInit {
  2. private $_configuration = null;
  3. private $_database = null;
  4. private $_session = null;
  5. public function EngineStart() {
  6. $this->_configuration = new ConfigurationInit;
  7. $this->_database = new DataBaseLoader;
  8. $this->_session = new SessionsLoader;
  9. }
  10. }
  11.  
  12. class SessionLoader
  13. {
  14. function NpSessionClearCzyCos__() {
  15.  
  16. // ? - chcę się tutaj odwołać do klasy głównej i skorzystać z zasobów _database ,
  17. // lecz nie wiem jak to zrobić, znaczy wiem, ale nie chcę tworzyć kolejnego obiektu
  18. //tylko skorzystać z tych co uruchomiłem wcześniej w EngineLoader
  19. $thisquestionmark.gif?->_database->query("|Zapytanie SQL|"); //
  20.  
  21. # To z tym $this?questionmark.gif wiem że to nie ma być this ale dałem abyście zrozumieli o co mi chodzi bo nie umiem tłumaczyć
  22.  
  23. }
  24. }
mortus
No przecież opisałem Ci dwa mechanizmy, przy czym jeśli chcesz korzystać z obiektów utworzonych w klasie "głównej" to nie masz wyboru i pozostaje Ci jedynie Dependency Injection:
  1. class EngineInit {
  2. private $_configuration = null;
  3. private $_database = null;
  4. private $_session = null;
  5. public function EngineStart() {
  6. $this->_configuration = new ConfigurationInit;
  7. $this->_database = new DataBaseLoader;
  8. $this->_session = new SessionsLoader($this->_database);
  9. }
  10. }
  11. class SessionLoader {
  12. private $_database = null;
  13. public function __construct($database) {
  14. $this->_database = $database;
  15. }
  16. function NpSessionClearCzyCos__() {
  17. $this->_database->query("|Zapytanie SQL|");
  18. }
  19. }
zielu001
Wiem, wiem, ale ja bym chciał bez podawania obiektu, ponieważ np. w jednej klasie będę potrzebował obsługi użytkownika, templatek, bazy danych, a w innej tylko bazy danych.. A tu musze podawać jako parametr, który tworzę. Będzie to też uciążliwe jeśli zrobię obsługę aplikacji, gdzie klasa aplikacji będzie ładowana automatycznie i nie będzie możliwości podania parametrów do konstruktora ponieważ będzie używana tylko funkcja startująca tą aplikację czyli jedna dla wszystkich.


// EDIT:
Chyba się źle za to zabrałem..
Wymyślę coś innego. Ma ktoś jakiś pomysł? Jak napisać coś co będzie funkconalne ? biggrin.gif
mortus
Cytat(zielu001 @ 29.03.2012, 21:21:48 ) *
Wiem, wiem, ale ja bym chciał bez podawania obiektu.

Pytałeś, jak przekazywać obiekty jednej klasy obiektom innej klasy i jak korzystać z obiektów jednej klasy wewnątrz innej klasy, gdy obiekty obu klas są tworzone w metodzie jeszcze innej klasy. Tutaj nie masz wyjścia i musisz skorzystać z DI.
Alternatywą jest Dependency Non-Injection, ale tam nie przekazujesz obiektów wcześniej utworzonych, a tworzysz zupełnie odrębne instancje tych obiektów. Dla przykładu klasa SessionLoader i EngineInit:
  1. class SessionLoader {
  2. private $_database = null;
  3. public function __construct() {
  4. $this->_database = new DataBaseLoader; // tworzysz nową instancję obiektu, nie korzystasz z instancji utworzonej w metodzie EngineStart klasy EngineInit
  5. }
  6. function NpSessionClearCzyCos__() {
  7. $this->_database->query("|Zapytanie SQL|");
  8. }
  9. }
  10. class EngineInit {
  11. private $_configuration = null;
  12. private $_database = null;
  13. private $_session = null;
  14. public function EngineStart() {
  15. $this->_configuration = new ConfigurationInit;
  16. $this->_database = new DataBaseLoader; // obiekt $this->_database nie ma nic wspólnego z obiektem $this->_database w klasie SessionLoader
  17. $this->_session = new SessionsLoader;
  18. }
  19. }

Opisane przeze mnie "mechanizmy" zastępują niejako słówko global, którego chciałeś na początku użyć.

Cytat(mortus @ 28.03.2012, 17:12:19 ) *
O tym, który z przedstawionych mechanizmów wybrać musisz zdecydować sam. Dependency Injection daje nam sporą elastyczność, ponieważ przekazywane obiekty nie muszą być obiektami dokładnie jednej klasy, choć muszą spełniać założenia pewnych wzorców, które nazywamy interfejsami. Dependency Non-Injection samo w sobie nie zapewnia nam żadnej elastyczności i wymusza na nas korzystnie z dokładnie takiej, a nie innej klasy (czasami jednak właśnie tego potrzebujemy).
zielu001
No tak, a dało by się zrobić coś w rodzaju globalnego ( wszystkiego ) obiektu który przechowywał by wszystkie moje obiekty?
Albo stworzyć klasę która poprzez funkcję wskazywała by na inną klasę czyli użycie np.
  1. $this->rejestr->pobierzKlasę("nazwa klasy")->funkcja_z_pobranej_klasy(params)


Wtedy DI był by w sumie idealny.

Ale jak zrobić w tym "pobierzKlasę("nazwa klasy")" aby nie tworzyło nowego obiektu tylko korzystało z już utworzonego ?
mortus
Cytat(zielu001 @ 29.03.2012, 21:21:48 ) *
// EDIT:
Chyba się źle za to zabrałem..
Wymyślę coś innego. Ma ktoś jakiś pomysł? Jak napisać coś co będzie funkconalne ? biggrin.gif

Dependency Injection dzisiaj jest wykorzystywane praktycznie wszędzie. Natomiast Twój pomysł nie jest zły i jest podobny np. do Zend-owskich Bootstraperów, które na podstawie danych konfiguracyjnych (zazwyczaj z pliku .ini) "przygotowują" odpowiednie klasy do współdziałania z całą aplikacją.

Cytat(zielu001 @ 29.03.2012, 21:56:19 ) *
No tak, a dało by się zrobić coś w rodzaju globalnego ( wszystkiego ) obiektu który przechowywał by wszystkie moje obiekty?
Albo stworzyć klasę która poprzez funkcję wskazywała by na inną klasę czyli użycie np.
  1. $this->rejestr->pobierzKlasę("nazwa klasy")->funkcja_z_pobranej_klasy(params)


Wtedy DI był by w sumie idealny.

Ale jak zrobić w tym "pobierzKlasę("nazwa klasy")" aby nie tworzyło nowego obiektu tylko korzystało z już utworzonego ?

To zależy od założeń Twojego projektu. Poczytaj o wzorcu FrontController, który poniekąd temu właśnie służy.
zielu001
Chcę stworzyć szybki, dobrze zabezpieczony framework.

Mógł byś mnie naprowadzić jak najlepiej było by to napisać?
Oczywiście robię to aby się czegoś ciekawego nauczyć.
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.