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).
$articleService = ...;
$userService = ...;
$user = $userService->getUserRepository()->findOne(123);
$article = $articleService->createArticle();
$article->setTitle('My Title');
$article->setAuthor($user);
$articleService->persistArticle($article);
Do tego dochodzą jeszcze interfejsy, które są implementowane przez poszczególne klasy modelu:
interface ArticleServiceInterface {
// Tworzy nowy obiekt implementujący ArticleInterface
public function createArticle();
// Zwraca jakiś obiekt pozwalający na wyszukiwanie artykułów (zawierający przykładowo metody find(), findOne(), findRecentlyRated() itp.)
public function getRepository();
// Metody pozwalające zapisać/uaktualnić bądź usunąć dany artykuł (np. w bazie danych)
public function persistArticle(ArticleInterface $article);
public function removeArticle(ArticleInterface $article);
}
interface ArticleInterface {
// jakieś gettery/settery dla id, tytułu, treści oraz relacji tego obiektu (artykuł ma przecież swojego autora) przykładowo:
public function getAuthor();
public function setAuthor(UserInterface $author);
// oraz oczywiście inne metody przeznaczone do operowania na właściwościach tego obiektu
}
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:
[php]$user = UserQuery::create()->findOneById(123);
$article = new Article();
$article->setTitle('My Title');
$article->setAuthor($user);
$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.

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ś:
class Article {
protected $author;
public function __construct() {
$this->author = User::getInstance();
}
}
$article = new Article();
Tylko normalne wykorzystywanie obiektówki:
class Article {
protected $author;
public function __construct(UserInterface $user) {
$this->author = $user;
}
}
$user = User::getInstance();
$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.