Pomoc - Szukaj - Użytkownicy - Kalendarz
Pełna wersja: MVC
Forum PHP.pl > Forum > PHP > Object-oriented programming
Kuziu
Witam,

znów mam problem z zaplanowaniem przepływu wszystkich danych w aplikacji. Wedle poprzednich podpowiedzi, które uzyskałem na forum zrobiłem tak (na przykładzie dodawania newsa):

NewsModel.php
  1. public function addChild($title, $content)
  2. {
  3. $child = new News();
  4. $child->setName($name);
  5. $child->setContent($content);
  6. $child->save();
  7. }


NewsController.php
  1. public function addNews()
  2. {
  3. try{
  4. $this->newsModel->addChild($this->input->getPostData('title'), $this->input->getPostData('content'));
  5. $this->redirect('index.php/news');
  6. }catch (Exception $e){
  7. echo 'Nie moglem dodac newsa';
  8. }
  9. }


No i niby wszystko dziala ladnie, mam kontroler ktory przekazuje dane do modelu, model te dane zapisuje i wszystko jest good.

Ale teraz gdy planuję dodać nową kolumnę w bazie danych, muszę modyfikować Model, wszystkie kontrolery które uzyły danego Modelu. Dodatkowo jeśli gdzieś nie zmienię kontrolera to np. content zapisze mi sie jako title, bo zmieni sie kolejnosc zmiennych wchodzących do funkcji i wszystko sie pokrzaczy.

Dobrze to robię, twrząc dla każdego zadania nową funkcję w modelu i przekazując do niej dane jako parametry funkcji?

I też pytanie czy to nie jest bez sensu takie duplikowanie wielu funkcji które dostarcza mi ORM Propel. Np. żeby pobrać newsa o danym id, napisąłem funkcję w modelu:
  1. /**
  2.  * @return News - instancja klasy News
  3.  */
  4. public function getChildById($id)
  5. {
  6. $child = NewsQuery::create()->findOneById($id);
  7.  
  8. if(!$child)
  9. throw new Exception('Nie ma takiego newsa.', 666);
  10.  
  11. return $child;
  12. }


Ale przecież równie dobrze mogłem od razu w kontrolerze pobrać newsa za pomocą funkcji dostarczonych przez ORM:

  1. $news = NewsQuery::create()->findOneById($this->input->getParam('id'));


Jakie mam korzyści z przerzucania zadań do modelu. Nie dodaję sobie tym zbędnej pracy, pisząc masę metod które w sumie już posiadam?
darko
Gdyby się tak czepić, to - zgodnie z zasadą single responsibility - dla każdego zadania powinieneś tworzyć nowy model. Ogólnie Twój problem związany ze zmianą struktury bazy i wpływem tej zmiany na modele i/lub kontrolery nie jest tylko Twoją bolączką. Możesz spróbować napisać jakieś proste rozwiązanie, które na podstawie struktury tabeli wygeneruje Ci odpowiedni model(e) lub skorzystać z gotowych rozwiązań ORM (mapowania obiektowo-relacyjnego np. Doctrine lub Propel).
Kuziu
Korzystam z Propela. A modele dzielę na te zajmujące sie danym tematem, czyli np. newsy, uzytkownicy itp. Pytanie moje jest czy takie podejscie jest w miare sensowne.

Co do generowania Modeli dynamicznie, to planuje to w przyszlosci i rozwiaze to problem zmiany struktury bazy.
darko
Ciężko tak jednoznacznie nie znając specyfiki projektu odpisać Ci że robisz dobrze lub źle. Generalnie model powinien być jak najbardziej samodzielnym "klockiem", który pewne powtarzalne czynności dziedziczy ze wspólnej klasy bazowej. Odwzorowanie bytów (klas) powinno odzwierciedlać w idealnym przypadku relacje rzeczywiste, jakie zachodzą pomiędzy prawdziwymi "obiektami" w rzeczywistości. Myślę, że najlepiej dla Ciebie w tej sytuacji byłoby gdybyś poczytał o programowaniu kontraktowym i używaniu interfejsów tak często lekceważonych. Na etapie projektowym jest to bardzo pożyteczna metodyka programowania. Pamiętaj przy tym o zasadzie programowania pod interfejs, nigdy pod implementację. Następnie jeśli chcesz pisać dobre API poczytaj o TDD i wykorzystaniu PHPUnit. Piszę to po to, byś wiedział, że są pewne narzędzia, które w znacznym stopniu ułatwiają zarówno modelowanie obiektowe jak też uczą dobrych nawyków i pozwalają na tworzenie wydajnych, i "czytelnie napisanych" aplikacji, którymi w późniejszym czasie jest łatwiej zarządzać, a przez to obniża się późniejsze koszty konserwacji kodu, co w praktyce jest bardzo ważne.
Kuziu
Dzieki za linki, biore sie za lekture smile.gif
Crozin
1. Problem jest z Twoją metodą NewsModel::addChild(). Zamiast przekazywać jej konkretne parametry potrzebne do stworzenia encji (tutaj aktualności) przez encję samą w sobie. Czyli coś w stylu:
  1. $news = $newsModel->createEntity(); // W najprostszym przypadku ograniczy się to do zwrócenia nowego, czystego obiektu
  2. $news->setTitle($this->input->getPostData('title'));
  3. ...
  4.  
  5. $newsModel->addChild($news); // Swoją drogą nazwa "addChild" jest kompletnie nietrafiona - "save" czy "persist" znacznie lepiej oddaje charakter tej metody
2. Pamiętaj, że model to nie ORM. Idealnie by było jakbyś z poziomu kontrolera nie był nawet w stanie stwierdzić czy dany model wykorzystuje ORMa, że dane w ogóle pochodzą z bazy danych. Opracowanie dobrych interfejsów pozwala na osiągnięcie czegoś takiego.
Kuziu
Ale jeśli zrobię tak jak piszesz:

  1. $news = $newsModel->createEntity(); // W najprostszym przypadku ograniczy się to do zwrócenia nowego, czystego obiektu
  2. $news->setTitle($this->input->getPostData('title'));


to otrzymuję coś identycznego jak używając samego propela:

  1. $news = new News();
  2. $news->setTitle($this->input->getPostData('title'));


A co wtedy z walidacją przy zapisie itp. przecież w takim podejsciu przekazuje wpisywanie danych do Kontrolera.

No chyba ze metoda ->save(); zwracała by wyjątek przed zapisem, a wcześniej obiekt by się nie przejmował czy dane są poprawne czy nie, tylko przy zapisie miałbym wyjątek. Dobrze kombinuję?

Co do addChild() to za dużo we flashu siedze i z przywyczajenia palnąłem biggrin.gif

A jeszcze jedno. W przypadku np. błędnych danych Model zwraca wyjątek do kontrolera że nie mógł np. stworzyć nowego użytkownika bo taki o podanym loginie już istnieje. A czy uprawnienia Admina itp. również powinien sprawdzać model? Wtedy musiałby wiedzieć w jakim trybie jest. Ewentualnie czy funkcja dodaj użytkownika a dodaj użytkownika z poziomu Admina powinna być drugą kopią, ewentualnie osobną metodą z wywołaniem tej poprzedniej?
rzymek01
Cytat(Kuziu @ 16.02.2011, 08:42:05 ) *
No chyba ze metoda ->save(); zwracała by wyjątek przed zapisem, a wcześniej obiekt by się nie przejmował czy dane są poprawne czy nie, tylko przy zapisie miałbym wyjątek. Dobrze kombinuję?

osobiście stosuję coś takiego, że klasa typu News ma swój stan poprawna/niepoprawna, stan klasy jest uaktualniany przy każdej zmianie pól obiektu, więc w zasadzie sprawdzenie poprawności danych odbywa się w klasie News, natomiast to klasa NewsModel sprawdza poprawność klasy News (np. przy edycji/dodawaniu) i wówczas w przypadku niepoprawności klasy News NewsModel rzuca wyjątkiem, który jest łapany przez kontroler

edit:
uprawnienia sprawdzane są przez kontroler (lub klasę do tego przeznaczoną, ale wywoływaną z poziomu kontrolera), gdyż badanie uprawnień nie nalezy w ogóle do zadań modelu news
Crozin
Cytat
to otrzymuję coś identycznego jak używając samego propela:
I co w związku z tym? smile.gif Musisz liczyć się z tym, że modyfikacja struktury obiektu (np. przez dodanie nowej właściwości) wiąże się z reguły z koniecznością jakiś tam modyfikacji kodu. Oczywiście zawsze możesz proces tworzenia obiektu nieco zautomatyzować, taki prosty przykład:
  1. $news = $newsBuilder->fromArray($this->input->getPostData('news')); // POST['news'] to tablica

Cytat
A co wtedy z walidacją przy zapisie itp. przecież w takim podejsciu przekazuje wpisywanie danych do Kontrolera.
W przypadku stron internetowych właściwie nie da się tego rozwiązać inaczej. Taka specyfika środowiska webowego.

Sama walidacja jest dosyć sporym problemem... Na początku musisz zadecydować czy encja (tutaj będą to m.in. obiekty News czy Article) może posiadać błędny stan wewnętrzny czy nie. Innymi słowy czy da się zrobić coś takiego:
  1. $news = new News();
  2. $news->setTitle('...');
Czy metoda News::setTitle() powinna wywalić wyjątek InvalidArgumentException z komunikatem "podaj normalny tytuł aktualności, a nie jakieś śmieci".
Raczej na pewno będziesz musiał zrezygnować z takiego podejścia, ponieważ walidacja danych jest procesem zbyt skomplikowanym by względnie prosty obiekt News był w stanie sobie z nim poradzić. Prawdopodobnie skończysz z czymś w stylu:
  1. $news = ...;
  2. $newsManager->persistNews($news); // Metoda newsPersist przed jakimkolwiek zapisem wywołuje walidator i sprawa czy ten aby przypadkiem nie zwrócił jakiś błędów


Cytat
Wtedy musiałby wiedzieć w jakim trybie jest. Ewentualnie czy funkcja dodaj użytkownika a dodaj użytkownika z poziomu Admina powinna być drugą kopią, ewentualnie osobną metodą z wywołaniem tej poprzedniej?
IMO raczej nie powinieneś po prostu dopuścić do możliwości wywołania metody NewsManager::persistNews() przez użytkownika bez odpowiednich uprawnień.
Kuziu
Dzieki. Czyli rozumiem że to kontroler powinien sprawdzic uprawnienia uzytkownika i ewentualnie nie dopuscic w ogole do wywolania metody.

Co do walidacji, to zrzucę to na Propela i przed save'em po prostu sprawdzę czy dane są poprawne i ewentualnie zwrócę listę błędów, które Propel wskazał.

--------- EDIT -----------

A jeszcze taka kwestia. Mam np. liste ulubionych zdjęć, które użytkownik może sobie dodawać. (czyli mamy galerię i ktoś sobie dodaje do własnej listy ulubionych zdjęć, odnośnik do jakiejś fotki z galerii).

Pytanie czy tym powinien zająć się model
- użytkownika, bo nie jako to on chce to wykonać, więc:
  1. UserModel::addFavouritePhoto($user, $photo);

- model galerii
- model ulubionych zdjec
- a moze np. model uzytkownika za pomocą modelu galerii lub ulubionych zdjęć?

I czy powinienem przekazywać zawsze $user, a w kontrolerze pobierać aktualnie zalogowanego, czy Model sam powinien pobierać aktualnie zalogowanego user'a i domyslnie do niego przypisywać dane.
erix
Cytat
I czy powinienem przekazywać zawsze $user, a w kontrolerze pobierać aktualnie zalogowanego, czy Model sam powinien pobierać aktualnie zalogowanego user'a i domyslnie do niego przypisywać dane.

Uhm, czegoś tu nie kumam - po co wywołujesz to statycznie...?

Nie wiem, czym jest u Ciebie User_Model, ale bardziej sensowne jest IMO używanie obiektu miast klasy do czegoś takiego; wtedy odpada problem, o którym mówisz. [;
Crozin
Cytat
I czy powinienem przekazywać zawsze $user, a w kontrolerze pobierać aktualnie zalogowanego, czy Model sam powinien pobierać aktualnie zalogowanego user'a i domyslnie do niego przypisywać dane.
Model powinien jawnie otrzymać referencję do użytkownika jeżeli ta potrzebna jest mu do pracy. Generalnie to taka złota zasada OOP: nigdy nie wytwarzaj "z dupy" zależności. Innymi słowy - obiekt nie powinien korzystać z jakiś globalnych danych (chyba, że jest to naprawdę nieuniknione).

Dodatkowo powinieneś bardziej uważnie projektować publiczny interfejs Twoich obiektów.
UserModel::addFavouritePhoto($user, $photo); - Pomijając fakt błędnego użycia metody statycznej (powinieneś operować na obiekcie tutaj, nie na klasie) dodanie ulubionego zdjęcia raczej powinno odbywać się na poziomie obiektu użytkownika:
  1. $user = ...;
  2. $photo = ...;
  3.  
  4. $user->addFavouritePhoto($photo);
  5.  
  6. $userManager->persistUser($user); // utrwala dane np. poprzez zapisanie ich w bazie danych
Kuziu
Kolejny raz dzięki za odpowiedzi. Błędnie zapisałem ten kod.

Oczywiście mam instancję klasy UserModel która jest singletonem i odwołuję się do niej jako do obiektu nie przez statyczną metodę.

Ok czyli z odpowiedzi Crozin'a wynika że powinienem wywołać z kontrolera metodę Modelu i przekazać jej instancję user'a pobraną również z modelu, ale zostawiając sobie furtkę na wypadek chęci pobrania danych innego user'a.

Na razie chyba wszystko kumam. Wielkie dzieki za rozjaśnienie sytuacji.

------------ EDIT -----------

A więc jednak trochę źle myślę bo ja przekazuję do modelu instancję User'a i zdjęcia zamiast pobrać aktualnego user'a i jemu zapisać fotkę.

Ale to znów trochę się kłóci z tym o czym wcześniej mówiliśmy, by wszystko przechodziło przez model. Teraz wykonując:

  1. $user->addFavouritePhoto($photo);

Znów nie operuję na modelu tylko na instancji klasy będącej reprezentacją wpisu w MySQLu którą reprezentuje Propel swoją klasą User. :|

To już nie kumam czy robić wszystko przez model czy nie :|
Crozin
Cytat
[...] która jest singletonem [...]
Po co Ci tam singleton?
Cytat
Znów nie operuję na modelu tylko na instancji klasy będącej reprezentacją wpisu w MySQLu którą reprezentuje Propel swoją klasą User. :|
Encja (mająca odzwierciedlenie w postaci rekordu w bazie danych) to jest właśnie model. Model to bardzo rozbudowana część aplikacji i w jej skład wchodzi wiele "typów" obiektów.

Propel nawet obsługuje taką składnię (o ile kojarzę nie jest on pod tym względem zbyt elastyczny, ale to co udostępnia spełnia nasze oczekiwania): http://www.propelorm.org/wiki/Documentatio...5/Relationships
Kuziu
Cytat
Po co CI tam singleton?


np. po to by wszystkie kontrolery korzystały z tej samej instancji userModel, i np. gdy wywolam $this->userModel->login($userName, $password), to wszystkie kontrolery mogą pobrać instancję zalogowanego użytkownika poprzez UserModel::getInstance()->getLoggedUser(), a tak to każdy controller musiałby logowac usera osobno. Chyba że źle myślę.

Cytat
Encja (mająca odzwierciedlenie w postaci rekordu w bazie danych) to jest właśnie model. Model to bardzo rozbudowana część aplikacji i w jej skład wchodzi wiele "typów" obiektów.
- też tak myślałem i czytałem artykuły ludzi z propela, a potem ktoś na forum.php.pl pisał mi żebym w żaden sposób nie kojarzył klas propela z modelem bo to jest zupełnie co innego :|


Powiedz mi pls, dlaczego powidzienem logować user'a przez Model ale już dodawać jego ulubioną fotkę przez klasę propel'a. Dlaczego nie mogę zalogować go od razu w kontrolerze przez klasę propela?
Crozin
Cytat
Chyba że źle myślę.
Źle myślisz. Przecież raz utworzony obiekt możesz przekazywać w różne miejsca. Poza tym zalogowany użytkownik powinien być osobnym modelem (który może korzystać z modelu "User" w celu przechowywania informacji o konkretnym użytkowniku). A nazwą tego modelu może być... sesja.

Cytat
a potem ktoś na forum.php.pl pisał mi żebym w żaden sposób nie kojarzył klas propela z modelem bo to jest zupełnie co innego :|
I bardzo mądrze Ci napisał - być może byłem to nawet ja, bo sam takie zdanie pisałem. Model to warstwa aplikacji odpowiedzialna za - w ogromnym uproszczeniu - dane. ORM (Propel, Doctrine* itp.) to jedynie narzędzie, które model może wykorzystywać w swojej pracy. Ów ORM powinien być sprawą wyłącznie modelu, kontroler czy widok nie powinny w ogóle zdawać sobie sprawy z jego istnienia.

Cytat
Powiedz mi pls, dlaczego powidzienem logować user'a przez Model ale już dodawać jego ulubioną fotkę przez klasę propel'a. Dlaczego nie mogę zalogować go od razu w kontrolerze przez klasę propela?
Ale obiekt Propela to jest model**.

* IMO Doctrine2 ma architekturę zdecydowanie bardziej przyjazną do wykorzystania w projekcie opartym o architekturę MVC - nie dotyczy to wersji 1.2 tego ORMa.
** To jest właśnie problem z Propelem - wymusza na Tobie korzystanie z obiektów swoich klas, które nie są zbyt dobrze przygotowane do współpracy z architekturą MVC.
Kuziu
No i właśnie znów widzę że "a potem ktoś na forum.php.pl pisał mi żebym w żaden sposób nie kojarzył klas propela z modelem bo to jest zupełnie co innego :|" i piszesz że mądrze. A zaraz potem "Ale obiekt Propela to jest model".

To ja już nic nie rozumiem.



A co do singletonu z klasą user, to czy nie lepiej go mieć, zamiast przekazywać z kontrolera, do masy innych modeli czy innych kontrolerów? Nie lepiej jak każdy kontroler czy model który go potrzebuje po prostu pobierze go za pomocą getInstance?
Crozin
Cytat
No i właśnie znów widzę że "a potem ktoś na forum.php.pl pisał mi żebym w żaden sposób nie kojarzył klas propela z modelem bo to jest zupełnie co innego :|" i piszesz że mądrze. A zaraz potem "Ale obiekt Propela to jest model".

To ja już nic nie rozumiem.
Jej, rzeczywiście namieszałem. Już prostuję... jedną z podstawowych cech MVC czy raczej nawet samego OOP jest komunikacja przy użyciu interfejsów. Dzięki takiemu podejściu możemy niemalże w każdym momencie zmienić implementację dowolnej klasy nie wpływając na działanie reszty aplikacji. Dobrze zaprojektowany interfejs nie ujawnia szczegółów implementacji klasy (np. faktu, że korzysta ona z bazy danych do zapisywania danych).

Czym jest model? Model to warstwa aplikacji odpowiedzialna głównie za obsługę obiektów biznesowych (dziwnie brzmi polskie tłumaczenie :]) czyli obiektów reprezentujących dane w aplikacji. Oczywiście w skład modelu wchodzi jeszcze cały wachlarz obiektów potrzebnych do zarządzania tym wszystkim.

Tak więc bardzo uproszczona warstwa modelu może składać się z dwóch typów klas - encji reprezentujących obiekty biznesowe oraz usług/menadżerów pozwalających wykonywać na nich pewne operacje (np. ich zapisanie, skasowanie czy pobranie - to ostatnie może być realizowane przez inny obiekt).
  1. $articleService = ...;
  2. $userService = ...;
  3.  
  4. $user = $userService->getUserRepository()->findOne(123);
  5.  
  6. $article = $articleService->createArticle();
  7. $article->setTitle('My Title');
  8. $article->setAuthor($user);
  9.  
  10. $articleService->persistArticle($article);
Do tego dochodzą jeszcze interfejsy, które są implementowane przez poszczególne klasy modelu:
  1. interface ArticleServiceInterface {
  2. // Tworzy nowy obiekt implementujący ArticleInterface
  3. public function createArticle();
  4.  
  5. // Zwraca jakiś obiekt pozwalający na wyszukiwanie artykułów (zawierający przykładowo metody find(), findOne(), findRecentlyRated() itp.)
  6. public function getRepository();
  7.  
  8. // Metody pozwalające zapisać/uaktualnić bądź usunąć dany artykuł (np. w bazie danych)
  9. public function persistArticle(ArticleInterface $article);
  10. public function removeArticle(ArticleInterface $article);
  11. }
  12.  
  13. interface ArticleInterface {
  14. // jakieś gettery/settery dla id, tytułu, treści oraz relacji tego obiektu (artykuł ma przecież swojego autora) przykładowo:
  15. public function getAuthor();
  16. public function setAuthor(UserInterface $author);
  17.  
  18. // oraz oczywiście inne metody przeznaczone do operowania na właściwościach tego obiektu
  19. }
Jak widzisz taki model ma na tyle dobrze zaprojektowany interfejs, że nie ujawnia on żadnych informacji n/t implementacji. Z punktu widzenia kontrolera nie ma najmniejszego znaczenia czy model wykorzystuje ORM-a typu Propel do zapisu danych w bazie czy może dane te zapisywane są w pliku XML albo i przesyłane są przez Internet do Facebooka.
Dodatkowo jak widzisz unikałem bezpośredniego nawiązania do klas - zamiast tego wszystko opiera się na interfejsach. Metoda ArticleInterface::setUser() czy UserServiceInterface::persistUser() oczekują, że jako pierwszy parametr zostanie podany obiekt implementujący interfejs UserInterface, a nie konkretna klasa (User). Utworzenie nowego użytkownika (np. dla celów jego zarejestrowania) również nie wymaga jawnego podania nazwy klasy (new User()), a jedynie odwołania do UserServiceInterface::createUser() które zwróci jakiś obiekt implementujący UserInterface. Po co to wszystko? Po to, że w każdej chwili możesz do reprezentacji użytkownika zamiast klasy User wykorzystać MySuperNewUser - klasa ta musi jedynie implementować interfejs UserInterface i już jest zdatna do bezproblemowej pracy w aplikacji.

W przypadku małych/prostych aplikacji takie podejście może wydać Ci się niepotrzebnie przekombinowane. Rzeczywiście, jeżeli piszesz bloga może to być zbędne, ale już w średniorozbudowanych projektach albo w projektach które będą rozwijane przez kilka lat powinno zacząć to procentować. Poza tym jeżeli wejdzie Ci to w nawyk nawet nie zauważysz dodatkowego narzutu pracy związanego z utworzeniem i projektowaniem interfejsów.



A teraz wracając do Propela. Powinieneś zauważyć, że klasa Article wygenerowana przez Propela to właśnie klasa która powinna implementować ArticleInterface, a ArticleQuery to klasa która powinna implementować ArticleRepositoryInterface (obiekt zwracany przez metode getRepository()). Propel zbudowany jest w oparciu o wzorzec ActiveRecord, który przewiduje że to encja (obiekt Article) odpowiedzialna jest za reprezentowanie danych (setTitle(), getContent()) i ich zarządzanie (save(), delete()) - to już jest pierwszy błąd bo nagle obiekt odpowiedzialny jest za więcej niż jedno zadanie. W moim przykładzie to ostatnie zadanie realizowane jest przez osobny obiekt - UserService. Ale pomińmy tą moim zdaniem dosyć poważną niedogodność i przejdźmy do przykładowego kodu:
  1. [php]$user = UserQuery::create()->findOneById(123);
  2.  
  3. $article = new Article();
  4. $article->setTitle('My Title');
  5. $article->setAuthor($user);
  6.  
  7. $article->save();
Jak widzisz są tu dwa problemy. Po pierwsze model przestał opierać się na interfejsach, a zaczął na klasach Propela. Dodatkowo ujawniona została implementacja, a interfejs został wymuszony. Widać, że Propel nie może być być utożsamiany z modelem - nie nadaje się on do tego. Co zatem począć? Propela można co najwyżej wykorzystać jako narzędzie używane w naszym modelu. Innymi słowy - trzeba sprawić by Propel był tylko częścią implementacji, a nie interfejsu. By to osiągnąć konieczne by było utworzenie własny obiektów, które w swoim wnętrzu (w implementacji) korzystałyby z Propela.

Dlatego też napisałem post wcześniej (pierwsza gwiazdka), że nie uważam Propela za najlepszy ORM do użycia w aplikacji MVC. Co prawda używałem go w wersji 1.2 lata temu, a nowa 1.5 znacząco się zmieniła z tego co widzę w dokumentacji nadal pozostały stare problemy. To jest dobry projekt, ale ciężko będzie go zmusić do pracy w tak mocno nastawionej na interfejsy architekturze.

Cytat
A co do singletonu z klasą user, to czy nie lepiej go mieć, zamiast przekazywać z kontrolera, do masy innych modeli czy innych kontrolerów? Nie lepiej jak każdy kontroler czy model który go potrzebuje po prostu pobierze go za pomocą getInstance?
Pojawiają się dwa problemy:
1. Tracisz właściwie wszystko to co zyskujesz dzięki interfejsom - patrz dosyć długi wywód powyżej. wink.gif
2. Tworzysz "z dupy" zależności dla obiektów, ponieważ implementacja obiektów zaczyna korzystać z danych globalnych. Innymi słowy musisz liczyć się z tym, że chcąc przykładowo zmienić by użytkownik korzystał z klasy MyUserUser zamiast User będziesz musiał szukać miliona User::getInstance() tylko po to by zmienić to na MyUserUser::getInstance(), znacznie ciężej będzie Ci ponownie wykorzystywać dany kod, a tak podstawowe zadania jak przykładowo przeprowadzanie testów stanie się znacznie trudniejsze.

Swoją drogą zadaniem singletona nie jest udostępnienie globalnego dostępu do danego obiektu, a zagwarantowanie istnienia tylko jednej instancji danej klasy, czyli nie takie coś:
  1. class Article {
  2. protected $author;
  3.  
  4. public function __construct() {
  5. $this->author = User::getInstance();
  6. }
  7. }
  8.  
  9. $article = new Article();
Tylko normalne wykorzystywanie obiektówki:
  1. class Article {
  2. protected $author;
  3.  
  4. public function __construct(UserInterface $user) {
  5. $this->author = $user;
  6. }
  7. }
  8.  
  9. $user = User::getInstance();
  10. $article = new Article($user);


W ogóle używanie właściwości/metod statycznych (jaką jest getInstance() w implementacji singletona) powinno być bardzo dobrze uargumentowane i "obronione" przed użyciem. Statyczność jest wypaczeniem obiektowości, ale jest "złem" koniecznym.
darko
offtopic.gif toś się rozpisał biggrin.gif (delete this post)
Kuziu
Thx Crozin.

Znów jest jaśniej smile.gif Rozumiem, że np. mój model nie musi posiadać metody:
  1. createUser($login, $password, $costam, $costam)


Tylko prostą:
  1. createUser()

oddającą nową instancję User'a a kontroler może jego dane uzupełnić:
  1. $user = $this->userModel->createUser();
  2. $user->setLogin('dane pobrane z jakiejs klasy odpowiadajacej za GET/POST/itp.');
  3. $user->setPassword('j/w');


A dopiero potem powinienem zapis przekazać znów do modelu, zamiast wykorzystać funkcję save() propela.
  1. $this->userModel->persistUser($user);

I ta metoda może zwrócić wyjątek gdy np. dane będą błędne. Tak?


Zaczynam też rozumieć o co Ci chodzi z minusami propela i to mnie wkurza bo się do niego już strasznie przywyczaiłem i na prawdę dobrze mi się nim operuje.

-------------------

Co do singletonów, to zazwyczaj w konstruktorze pobieram sobie instancję za pomocą:
  1. $this->userModel = UserModel::getInstance()

Ale pomyślę o tym co napisałeś i spróbuję korzystać z singletonów tak jak mówileś, tylko w racjonalnych przypadkach.
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.