Bartosz Ratajczyk

Bartosz Ratajczyk MS SQL Developer

Temat: Modele

Yuriy Kisil:
Do niedawna w ZF były rozwijane elementy Zend_Entity & Zend_Db_Mapper

Dzięki wielkie za link. Po przeklikaniu się przez dyskusję i sprawdzeniu kilku linków trafiłem m.in. na to: http://www.slideshare.net/weierophinney/architecting-y...

Może jeszcze komuś się przyda.
Artur Świerc

Artur Świerc Programista PHP/Java

Temat: Modele

Ja tam znalazłem pewien wpis, w którym gościu poleca FW Flow3, zaciekawiła mnie w nim obsługa IoC:
http://flow3.typo3.org/documentation/tutorials/getting...

Wstrzykiwanie zależności poprzez adnotacje, takie cuda to dotychczas były tylko w frameworkach JEE :)

edit: w przykładach jest pokazany fajny modelArtur Świerc edytował(a) ten post dnia 13.08.10 o godzinie 08:48
Wojciech Soczyński

Wojciech Soczyński Programista
eksplorator -
blog.wsoczynski.pl

Temat: Modele

Artur Świerc:
Ja tam znalazłem pewien wpis, w którym gościu poleca FW Flow3, zaciekawiła mnie w nim obsługa IoC:
http://flow3.typo3.org/documentation/tutorials/getting...

Wstrzykiwanie zależności poprzez adnotacje, takie cuda to dotychczas były tylko w frameworkach JEE :)

edit: w przykładach jest pokazany fajny modelArtur Świerc edytował(a) ten post dnia 13.08.10 o godzinie 08:48
Jeżeli interesuje Cię IOC, to zapraszam do zapoznania się z moją implementacją. Dla odmiany konfigurowany XML-em ;).
Bartosz Ratajczyk

Bartosz Ratajczyk MS SQL Developer

Temat: Modele

To kolejne pytanie. Mam powiedzmy ten blog, żeby się trzymać cały czas tego samego przykładu. Teraz chcę wyszukać wszystkie wpisy na blogu wg zadanych kryteriów (np. z danego miesiąca). Do tego chcę mieć listę wpisów na blogu wraz z listą tagów z nimi skojarzonych.

Mam np.:

class BlogEntry
{
protected $_id;
protected $_date;
...
protected $_tags;

(gettery/settery, wyciąganie tagów itd)
}

class BlogEntryMapper
{

protected $dbTable;
...
(data mapper jak z tutoriala ZF)

public function getEntries($where)
{
$result = $this->getDbTable()->fetchAll($where);
$entries = array();

// teraz mam kolekcję wpisów na blogu

foreach($result as $row) {
$entry = new BlogPost();
$entry->setId($row->id)
->setDate($row->date)
// (...) dalsze przypisywanie kolumn z tabeli na właściwości obiektu BlogEntry
}
}
}


Pytanie brzmi: jak najwygodniej wyciągnąć i przypisać tagi do każdego wpisu? Wyciąganie tagów dla każdej iteracji foreach($result as $row) jest oczywiście do zrobienia, ale może są inne ciekawsze metody?
Bo teraz to jest rzeźbienie w stylu

(...)
$tags = new TagsMapper();
$entry->setTags($tags->getTagsByEntryId($row->id)))
(...)


Raczej odpada widok - tagów może być wiele, a nie chciałbym ich łączyć do jednego pola i potem obrabiać na poziomie obiektu żeby rozbić na pojedyncze tagi.
Wojciech Soczyński

Wojciech Soczyński Programista
eksplorator -
blog.wsoczynski.pl

Temat: Modele

Bartosz Ratajczyk:
To kolejne pytanie. Mam powiedzmy ten blog, żeby się trzymać cały czas tego samego przykładu. Teraz chcę wyszukać wszystkie wpisy na blogu wg zadanych kryteriów (np. z danego miesiąca). Do tego chcę mieć listę wpisów na blogu wraz z listą tagów z nimi skojarzonych.

Mam np.:

class BlogEntry
{
protected $_id;
protected $_date;
...
protected $_tags;

(gettery/settery, wyciąganie tagów itd)
}

class BlogEntryMapper
{

protected $dbTable;
...
(data mapper jak z tutoriala ZF)

public function getEntries($where)
{
$result = $this->getDbTable()->fetchAll($where);
$entries = array();

// teraz mam kolekcję wpisów na blogu

foreach($result as $row) {
$entry = new BlogPost();
$entry->setId($row->id)
->setDate($row->date)
// (...) dalsze przypisywanie kolumn z tabeli na właściwości obiektu BlogEntry
}
}
}


Pytanie brzmi: jak najwygodniej wyciągnąć i przypisać tagi do każdego wpisu? Wyciąganie tagów dla każdej iteracji foreach($result as $row) jest oczywiście do zrobienia, ale może są inne ciekawsze metody?
Bo teraz to jest rzeźbienie w stylu

(...)
$tags = new TagsMapper();
$entry->setTags($tags->getTagsByEntryId($row->id)))
(...)


Raczej odpada widok - tagów może być wiele, a nie chciałbym ich łączyć do jednego pola i potem obrabiać na poziomie obiektu żeby rozbić na pojedyncze tagi.

Jeżeli chcesz dodać do swojego bloga funkcjonalność szukania (czyli użytkownik wpisuje w pole jakiś string lub ustawia kryteria i w odpowiedzi dostaje listę artykułów), to ja bym stworzył do tego klasę usługi, nazwijmy ją SearchService. Usługa ta miała by powiedzmy dwie metody 'searchSimple($text)' i 'searchByCondition(array $condition)'. Metody obie wykonywały by jakieś operacje na bazie (z pominięciem datamapera/repozytorium) i zwracały listę pasujących id-ków, na podstawie tych id-ków mógłbyś wyciągnąć potem z DataMapper'a/Repozytorium pasujące artykuły. Natomiast do innych celów myślę, że pokazane "rzeźbienie" będzie ok.
Alan Gabriel B.

Alan Gabriel B. Software Engineer,
IFX

Temat: Modele

Wojciech Soczyński:
Jeżeli chcesz dodać do swojego bloga [...]

W moim projekcie DDD, też się natknąłem na ten problem - dokładnie wyszukiwanie tasków (mój projekt to prosty Project Manager), tak jak jest to zrobione np. w TRACu.


Obrazek


Po pierwsze uważam, że za "szukajkę" jest odpowiedzialne repozytorium - a nie serwis - jako, że generalnie ono odpowiada za komunikacje ze źródłem danych (którym jest baza danych, ale też np. Lucene) i wyciąganie encji. Trzeba tylko pomyśleć jak odwzorować w repo skomplikowany proces jakim jest wyszukiwanie - proste metody typu findById($id) już nie wystarczą.

Sięgnąłem po Fowlera (W DDD nie chodzi o encje, serwisy i repozytoria, tylko o sprawne posługiwanie się wzorcami) i wzorzec Query Object - ten sam co w Doctrine.

Implementacja to już prosta sprawa.
1. Każde repozytorium u mnie ma swój interfejs:
interface TaskRepository
{
// inne metody

public function findByQuery(ITaskQuery $query);
}

2. ITaskQuery też jest tylko interfejsem, którego metody odpowiadają mniej więcej w działaniu filtrowi ze zdjęcia powyżej, [b]czyli trzymają się dziedziny/domeny[b].
3. Jako, że moje repozytoria korzystają z Doctrine2 zrobiłem najprostszą rzecz z możliwych - implementacja ITaskQuery (Doctrine2TaskQuery) opakowuje obiekt Doctrine\ORM\Query (dobrym pomysłem, było by rozszerzenie Doctrinowego buildera, ale wtedy o tym nie pomyślałem, a teraz mi się nie chce zmieniać :D) z Doctrine2 i zwraca go w implementacji Doctrine2TaskRepository.

W przyszłości mógłbym użyć Lucene (chociaż tutaj nie ma takiej potrzeby - zwracam uwagę tylko na fakt) i dzięki takiej architekturze zrobię to zupełnie przezroczyście dla aplikacji i jednocześnie mogę w pełni korzystać z mocy użytych narzędzi mimo, że są schowane.Alan Gabriel B. edytował(a) ten post dnia 14.08.10 o godzinie 23:19
Łukasz Woźniak

Łukasz Woźniak Starszy programista,
Asseco Business
Solutions S.A.

Temat: Modele

Wojciech Soczyński:
Jeżeli chcesz dodać do swojego bloga funkcjonalność szukania (czyli użytkownik wpisuje w pole jakiś string lub ustawia kryteria i w odpowiedzi dostaje listę artykułów), to ja bym stworzył do tego klasę usługi, nazwijmy ją SearchService. Usługa ta miała by powiedzmy dwie metody 'searchSimple($text)' i 'searchByCondition(array $condition)'. Metody obie wykonywały by jakieś operacje na bazie (z pominięciem datamapera/repozytorium) i zwracały listę pasujących id-ków, na podstawie tych id-ków mógłbyś wyciągnąć potem z DataMapper'a/Repozytorium pasujące artykuły. Natomiast do innych celów myślę, że pokazane "rzeźbienie" będzie ok.


Witam, zaciekawił mnie ten temat, więc postanowiłem się dołączyć. Głównie programuję w Symfony, ale Zenda też mam opanowanego. Od roku używam techniki opisanej przez Wojciecha Soczyńskiego. Wiele osób może się czepiać co do rezultatów tej metody, ale jest jeszcze jeden aspekt, który mało kto zauważa dzisiaj. Czytelność kodu, jak to pewien autor podkreślił (Robert C. Martin - "Clean code" - polecam książkę odskocznia od czego innego związanego z programowaniem) jesteśmy autorami swoich utworów. A tych nie czytelnych utworów nikt nie czyta.

W mojej grupie projektowej przyjmowaliśmy kilka możliwości jakie są proponowane przy tego typu problemie (piszemy w Doctrine). Metoda nazewnictwa klas dla przykładu


class UsersServices
{

}


Jest najczytelniejsza. Obiekt i czynności z nim związane mają swoje miejsce. Jeśli przykładowy User "kopie" to nikt inny tego nie zrobi. Jeśli wykonuje więcej czynności, to wiemy że to on robi dla przykładu "pije piwo gdy nie widzi Admin".

Jeśli chcemy otrzymywać informacje na temat grupy userów, przecież nie stworzymy metody dla Usera "powiedz co robią inni". Jego to nie obchodzi on ma swoje czynności. Dzięki temu mamy przejrzysty kod, i wiemy że w UsersServices, są przechowywane zapytanie dotyczące grupy użytkowników, co jest łatwe w dalszym wykorzystywaniu zwartych w nim funkcji.

Bo gdy zajdzie potrzeba zwiększenia funkcjonalności w Twoim Blogu. Będzie to tego czytelne miejsce. Bo załóżmy, że będziesz chciał dopisać powiązane posty, albo listę postów z innych blogów, już trzeba komplikować sprawę w klasie Users i dodawać mase nie potrzebnych pętli. Po roku czytania nie wiadomo co z czym jest.

A gdy widzisz linie kodu:


$usersServicesObject = new UsersServices();
$posts = $usersServicesObject->getPostFromLastMonth();


Dokładnie chodzi mi o ideologie. Mama nadzieje że wszyscy mnie zrozumieli :)

Dziękuję za wysłuchnie. ;D
Wojciech Soczyński

Wojciech Soczyński Programista
eksplorator -
blog.wsoczynski.pl

Temat: Modele

Alan Gabriel B.:
Wojciech Soczyński:
Jeżeli chcesz dodać do swojego bloga [...]

W moim projekcie DDD, też się natknąłem na ten problem - dokładnie wyszukiwanie tasków (mój projekt to prosty Project Manager), tak jak jest to zrobione np. w TRACu.


Obrazek


Po pierwsze uważam, że za "szukajkę" jest odpowiedzialne repozytorium - a nie serwis - jako, że generalnie ono odpowiada za komunikacje ze źródłem danych (którym jest baza danych, ale też np. Lucene) i wyciąganie encji. Trzeba tylko pomyśleć jak odwzorować w repo skomplikowany proces jakim jest wyszukiwanie - proste metody typu findById($id) już nie wystarczą.

Sięgnąłem po Fowlera (W DDD nie chodzi o encje, serwisy i repozytoria, tylko o sprawne posługiwanie się wzorcami) i wzorzec Query Object - ten sam co w Doctrine.

Implementacja to już prosta sprawa.
1. Każde repozytorium u mnie ma swój interfejs:
interface TaskRepository
{
// inne metody

public function findByQuery(ITaskQuery $query);
}

2. ITaskQuery też jest tylko interfejsem, którego metody odpowiadają mniej więcej w działaniu filtrowi ze zdjęcia powyżej, [b]czyli trzymają się dziedziny/domeny[b].
3. Jako, że moje repozytoria korzystają z Doctrine2 zrobiłem najprostszą rzecz z możliwych - implementacja ITaskQuery (Doctrine2TaskQuery) opakowuje obiekt Doctrine\ORM\Query (dobrym pomysłem, było by rozszerzenie Doctrinowego buildera, ale wtedy o tym nie pomyślałem, a teraz mi się nie chce zmieniać :D) z Doctrine2 i zwraca go w implementacji Doctrine2TaskRepository.

W przyszłości mógłbym użyć Lucene (chociaż tutaj nie ma takiej potrzeby - zwracam uwagę tylko na fakt) i dzięki takiej architekturze zrobię to zupełnie przezroczyście dla aplikacji i jednocześnie mogę w pełni korzystać z mocy użytych narzędzi mimo, że są schowane.Alan Gabriel B. edytował(a) ten post dnia 14.08.10 o godzinie 23:19
Ja dlatego zaproponowałem klasę usługową, że po pierwsze, mogą być różne kryteria szukania: po postach, po tagach po czymkolwiek innym, więc do którego repozytorium to dać ? Moim zdaniem klasa usługowa do szukania powinna mieć zależność z repozytoriami i używać odpowiedniego z nich dla odpowiednich kryteriów :>
Artur Świerc

Artur Świerc Programista PHP/Java

Temat: Modele

Bartosz Ratajczyk:

class BlogEntry
{
protected $_id;
protected $_date;
...
protected $_tags;

(gettery/settery, wyciąganie tagów itd)
}

class BlogEntryMapper
{

protected $dbTable;
...
(data mapper jak z tutoriala ZF)

public function getEntries($where)
{
$result = $this->getDbTable()->fetchAll($where);
$entries = array();

// teraz mam kolekcję wpisów na blogu

foreach($result as $row) {
$entry = new BlogPost();
$entry->setId($row->id)
->setDate($row->date)
// (...) dalsze przypisywanie kolumn z tabeli na właściwości obiektu BlogEntry
}
}
}

Witam, muszę odkopać trochę wątek ;)

Mam projekt, który jest już zaczęty na Zend_Db_Table, chciałbym to dalej ciągnąć bez zmiany np na Doctrine.

Chciałbym się 'luźno' trzymać DDD - czyli serwisy, repozytoria etc. Jednakże wkurza mnie, że wszędzie trzeba ciągnąć za sobą obiekt dbTable, a do tego jeszcze muszę tworzyć osobne encje. Wg mnie jest to pewne dublowanie kodu.

W Doctrine mamy encje i entityManager'a do utrwalania. Przy Zend_Db_Table powinna być encja i mapper do utrwalania. Czy kogoś też to drażni tak jak mnie, czy może moje myślenie idzie w złą stronę?

Bartku, myślę, że od czasu stworzenia tego wątku, Twój projekt poszedł do przodu, jak sobie poradziłeś z dbTable?
Wojciech Soczyński

Wojciech Soczyński Programista
eksplorator -
blog.wsoczynski.pl

Temat: Modele

Artur Świerc:
Bartosz Ratajczyk:

class BlogEntry
{
protected $_id;
protected $_date;
...
protected $_tags;

(gettery/settery, wyciąganie tagów itd)
}

class BlogEntryMapper
{

protected $dbTable;
...
(data mapper jak z tutoriala ZF)

public function getEntries($where)
{
$result = $this->getDbTable()->fetchAll($where);
$entries = array();

// teraz mam kolekcję wpisów na blogu

foreach($result as $row) {
$entry = new BlogPost();
$entry->setId($row->id)
->setDate($row->date)
// (...) dalsze przypisywanie kolumn z tabeli na właściwości obiektu BlogEntry
}
}
}

Witam, muszę odkopać trochę wątek ;)

Mam projekt, który jest już zaczęty na Zend_Db_Table, chciałbym to dalej ciągnąć bez zmiany np na Doctrine.

Chciałbym się 'luźno' trzymać DDD - czyli serwisy, repozytoria etc. Jednakże wkurza mnie, że wszędzie trzeba ciągnąć za sobą obiekt dbTable, a do tego jeszcze muszę tworzyć osobne encje. Wg mnie jest to pewne dublowanie kodu.

W Doctrine mamy encje i entityManager'a do utrwalania. Przy Zend_Db_Table powinna być encja i mapper do utrwalania. Czy kogoś też to drażni tak jak mnie, czy może moje myślenie idzie w złą stronę?

Bartku, myślę, że od czasu stworzenia tego wątku, Twój projekt poszedł do przodu, jak sobie poradziłeś z dbTable?

Zanim coś zaproponuje, chciałbym zapytać jaką wersje Doctrine rozważasz ? 1 czy 2 ?
Bartosz Ratajczyk

Bartosz Ratajczyk MS SQL Developer

Temat: Modele

Artur Świerc:
Chciałbym się 'luźno' trzymać DDD - czyli serwisy, repozytoria etc. Jednakże wkurza mnie, że wszędzie trzeba ciągnąć za sobą obiekt dbTable, a do tego jeszcze muszę tworzyć osobne encje. Wg mnie jest to pewne dublowanie kodu.

Dlaczego wszędzie? Ja dbTable daję tylko w mapperach. Masz rację, że może to wyglądać na dublowanie. Bo weźmy ten wpis na blogu:
class BlogEntry
{
protected $_id;
protected $_date;
...
protected $_tags;

(gettery/settery, wyciąganie tagów itd)
}


W mapperze, żeby go zapisać za pomocą Zend_Db_Table trzeba przekazać tablicę (taki parametr przyjmują metody insert() czy update(). Czyli musisz wykonać przepisanie obiektu (tu: BlogEntry) na tablicę. W wielu przypadkach kolumny w tabeli bazy danych nazywają się jak właściwości obiektu, stąd wrażenie przepisywania jeszcze raz tego samego, tylko w innej postaci. W wielkim uproszczeniu:

class BlogEntryMapper
{
...
public function save(BlogEntry $oBlogEntry)
{
$aDane = array(
'date' => $oBlogEntry->getDate(),
'tytul' => $oBlogEntry->getTytul(),
// inne pola
);

$this->getDbTable()->insert($aDane);
}
}


Co jeśli właściwości obiektu nie nazywają się tak samo jak kolumny w tabelach, albo chcesz rozrzucić dane z obiektu po kilku tabelach? Procedura zapisu się będzie komplikować i nie jest to już prosty przykład powyżej. Do tego ja przy zapisie do metody save() przekazuję obiekt przed filtrowaniem i przy tworzeniu tablicy dopiero stosuję filtry.
W Doctrine mamy encje i entityManager'a do utrwalania. Przy Zend_Db_Table powinna być encja i mapper do utrwalania. Czy kogoś też to drażni tak jak mnie, czy może moje myślenie idzie w złą stronę?

Mapper służy jako warstwa pośrednia do utrwalania obiektu. W tym przypadku odbywa się to za pomocą dbTable.
Bartku, myślę, że od czasu stworzenia tego wątku, Twój projekt poszedł do przodu, jak sobie poradziłeś z dbTable?

Za każdym razem przekazując go w mapperze. Tylko żeby nie pisać za każdym razem getTable/setTable wydzieliłem do globalnego mappera, po którym inne dziedziczą i tylko przekazuję o jaką nazwę modelu DbTable chodzi.
Artur Świerc

Artur Świerc Programista PHP/Java

Temat: Modele

Wojciech Soczyński:
Zanim coś zaproponuje, chciałbym zapytać jaką wersje Doctrine rozważasz ? 1 czy 2 ?

Ja bym chętnie tam podpiął Doctrine2, jako że mam doświadczenie w JPA to by było dla mnie intuicyjne. Ale niestety na serwerze PHP5.2 jeszcze stoi, a ten serwis to powinna być w sumie "szybka piłka" bez większej armaty :)
Bartosz Ratajczyk:
W mapperze, żeby go zapisać za pomocą Zend_Db_Table trzeba przekazać tablicę (taki parametr przyjmują metody insert() czy update(). Czyli musisz wykonać przepisanie obiektu (tu: BlogEntry) na tablicę. W wielu przypadkach kolumny w tabeli bazy danych nazywają się jak właściwości obiektu, stąd wrażenie przepisywania jeszcze raz tego samego, tylko w innej postaci. W wielkim uproszczeniu:
Co jeśli właściwości obiektu nie nazywają się tak samo jak kolumny w tabelach, albo chcesz rozrzucić dane z obiektu po kilku tabelach? Procedura zapisu się będzie komplikować i nie jest to już prosty przykład powyżej. Do tego ja przy zapisie do metody save() przekazuję obiekt przed filtrowaniem i przy tworzeniu tablicy dopiero stosuję filtry.

Właśnie tu teraz mam problem - co jeśli chciałbym z poziomu encji, czyli w BlogEntry utrwalić obiekt? Żeby nie były tam tylko akcesory, ale również proste metody np aktualizujące, zmieniające jakieś flagi. Czy w tym przypadku mogę z metody klasy BlogEntry odwoływać się do Mapper'a i robić update?

Wtedy by to wyglądało mniej więcej tak, pytanie tylko czy jest to prawidłowo zaprojektowane?

class BlogEntry
{
public function activateEntry()
{
$entryMapper = new BlogEntryMapper();
$entryMapper->update($this);
}
}


Co jeśli chciałbym robić operacje na większej liczbie tabel, np klient, który nie ma konta w serwisie składa zamówienie - zapisujemy klienta, koszyk a jeszcze parę innych rzeczy. Czy dobrze rozumiem, że takie rzeczy powinienem umieszczać np w serwisie?

Jeśli w grę by wchodził serwis, z dziwnym przypadkiem wymieszanych operacji insert/update/select przy której mappery by tylko zaśmiecały kod, to czy wtedy mogę odwoływać się bezpośrednio do Zend_Db? Artur Świerc edytował(a) ten post dnia 08.02.11 o godzinie 19:31
Wojciech Soczyński

Wojciech Soczyński Programista
eksplorator -
blog.wsoczynski.pl

Temat: Modele

Moim zdaniem, w encji nie powinieneś mieć żadnych metod, które na mapperze wywołują update czy jakieś inne operacje. Wg. mnie to powinno wyglądać tak:


class BlogEntry
{
public function activate()
{
$this->_active = true;
}
}

class BlogFacade {
public function activateBlogEntry($entryId){
$oEntry = $this->blogEntryRepository->find($entryId);
$oEntry->activate();
$this->blogEntryRepository->save($oEntry);
}

}

class SomeController extends Zend_ControllerAction {

public function activateAction(){
$entryId = $this->_request->getParam('entry_id');
$this->blogFacade->activateBlogEntry($entryId);
//inny kod

}
}


Tutaj w tym kodzie "blogEntryRepository" to jest twój mapper.

Btw. co do reszty wpisu, wyobraź sobie, że nie ma tabel, są tylko obiekty. Ich utrwalanie jest tylko czynnością stricte techniczną i nie ma żadnego znaczenia z punktu widzenia użytkownika (jego interesuje zakup w sklepie a nie zapisanie czegoś w tabeli bazy danych). Jak widziałbyś taki serwis o którym mówiłeś ?Wojciech Soczyński edytował(a) ten post dnia 09.02.11 o godzinie 10:13
Bartosz Ratajczyk

Bartosz Ratajczyk MS SQL Developer

Temat: Modele

Wojciech Soczyński:
Moim zdaniem, w encji nie powinieneś mieć żadnych metod, które na mapperze wywołują update czy jakieś inne operacje.

Też tak myślę. Mimo uproszczenia kodu encja sama nie powinna nic wiedzieć o sposobie jej utrwalania.
Btw. co do reszty wpisu, wyobraź sobie, że nie ma tabel, są tylko obiekty. Ich utrwalanie jest tylko czynnością stricte techniczną.

Dokładnie. Pomyśl np. o zapisie do pliku, do bazy NoSQL, albo późniejszą przesiadkę na Doctrine - pisanie pod konkretny model z pominięciem warstwy mapowania pomoże szybciej napisać kod. Jeśli jednak ma on być rozwojowy, to dodaj tego mappera.

Temat: Modele

ewentualnie można zrobić na observerze:

interface Observable {
public function attach(Observer $observer);
public function detach(Observer $observer);
public function notify();
}

interface Observer {
public function update(Observable $subject);
}

class BlogEntry /* impletents Observable */
{
public function notify() {
foreach($this->observers as $value) {
$value->update($this);
}
}

public function activateEntry()
{
$this->notify();
}
}

class BlogEntryMapper /* implements Observer */
{
public function register(Observable $subject) {
if($subject instanceof BlogEntry) {
$this->subject = $subject;
$this->subject->attach($this);
}
}

public function update(Observable $subject) {
echo "zaktualizowano ". get_class($subject);
}
}

kwestia dostosowania żeby to miało ręce i nogi
Wojciech Soczyński

Wojciech Soczyński Programista
eksplorator -
blog.wsoczynski.pl

Temat: Modele

Krzysztof Korzeniewski:
ewentualnie można zrobić na observerze:

interface Observable {
public function attach(Observer $observer);
public function detach(Observer $observer);
public function notify();
}

interface Observer {
public function update(Observable $subject);
}

class BlogEntry /* impletents Observable */
{
public function notify() {
foreach($this->observers as $value) {
$value->update($this);
}
}

public function activateEntry()
{
$this->notify();
}
}

class BlogEntryMapper /* implements Observer */
{
public function register(Observable $subject) {
if($subject instanceof BlogEntry) {
$this->subject = $subject;
$this->subject->attach($this);
}
}

public function update(Observable $subject) {
echo "zaktualizowano ". get_class($subject);
}
}

kwestia dostosowania żeby to miało ręce i nogi

Czytałem o takim podejściu i w większej skali nazywa się chyba CQRS.
http://simon-says-architecture.com/2010/02/19/cqrs-w-p...
http://blog.fossmo.net/post/Command-and-Query-Responsi...
Artur Świerc

Artur Świerc Programista PHP/Java

Temat: Modele

Wojciech Soczyński:
Moim zdaniem, w encji nie powinieneś mieć żadnych metod, które na mapperze wywołują update czy jakieś inne operacje. Wg. mnie to powinno wyglądać tak:


class BlogEntry
{
public function activate()
{
$this->_active = true;
}
}

class BlogFacade {
public function activateBlogEntry($entryId){
$oEntry = $this->blogEntryRepository->find($entryId);
$oEntry->activate();
$this->blogEntryRepository->save($oEntry);
}

}

class SomeController extends Zend_ControllerAction {

public function activateAction(){
$entryId = $this->_request->getParam('entry_id');
$this->blogFacade->activateBlogEntry($entryId);
//inny kod

}
}


Tutaj w tym kodzie "blogEntryRepository" to jest twój mapper.

Btw. co do reszty wpisu, wyobraź sobie, że nie ma tabel, są tylko obiekty. Ich utrwalanie jest tylko czynnością stricte techniczną i nie ma żadnego znaczenia z punktu widzenia użytkownika (jego interesuje zakup w sklepie a nie zapisanie czegoś w tabeli bazy danych). Jak widziałbyś taki serwis o którym mówiłeś ?Wojciech Soczyński edytował(a) ten post dnia 09.02.11 o godzinie 10:13

Chyba zaczynam rozumieć.

Pracuję z obiektami, zmieniam ich stan na różne sposoby, ale jeśli chciałbym utrwalić moją pracę to muszę prosić o pomoc Repozytorium.

Czyli, jeśli mam encję Koszyk, czy też Zamówienie, to nie powinna ona mieć w sobie typowo technicznych metod typu "zapiszZamowienie()" - bo to już podlegało by pod utrwalanie w bazie, a metodę taką powinienem mieć w servisie, w którym to zaś wykonujemy szereg operacji save/update przy pomocy repozytoriów?

Ciekawi mnie też, czy w serwisie muszę za każdym razem odwoływać się do repozytoriów - czasami trzeba coś przeliczyć, np koszt transportu na podstawie wartości produktów w zamówieniu i np na podstawie wybranej formy płatności, do tego zwykle dochodzą jakieś dodatkowe usługi i ich koszty. Korci żeby wykonać selecta z joinami ;) Jak sobie radzicie z takimi operacjami?

Teraz też widzę, jak wiele by mi pomógł entityManager :)
Bartosz Ratajczyk

Bartosz Ratajczyk MS SQL Developer

Temat: Modele

Artur Świerc:
Czyli, jeśli mam encję Koszyk, czy też Zamówienie, to nie powinna ona mieć w sobie typowo technicznych metod typu "zapiszZamowienie()" - bo to już podlegało by pod utrwalanie w bazie, a metodę taką powinienem mieć w servisie, w którym to zaś wykonujemy szereg operacji save/update przy pomocy repozytoriów?

Zależy co rozumiesz przez Serwis. Dla mnie to klasa, która nie jest powiązana z modelem danych. Np. wysyłka maili, generowanie PDFów. Myślę, że spokojnie możesz to dać do repozytorium.

Mnie sporo rozjaśnił ten artykuł: http://blog.fedecarg.com/2009/09/19/zend-framework-dal... (choć tutaj rolę mappera z quickstartu ZF pełni Repository, a mapper przekłada właściwości obiektu na nazwy elementów warstwy utrwalającej)
Wojciech Soczyński

Wojciech Soczyński Programista
eksplorator -
blog.wsoczynski.pl

Temat: Modele

Artur Świerc:
Wojciech Soczyński:
Moim zdaniem, w encji nie powinieneś mieć żadnych metod, które na mapperze wywołują update czy jakieś inne operacje. Wg. mnie to powinno wyglądać tak:


class BlogEntry
{
public function activate()
{
$this->_active = true;
}
}

class BlogFacade {
public function activateBlogEntry($entryId){
$oEntry = $this->blogEntryRepository->find($entryId);
$oEntry->activate();
$this->blogEntryRepository->save($oEntry);
}

}

class SomeController extends Zend_ControllerAction {

public function activateAction(){
$entryId = $this->_request->getParam('entry_id');
$this->blogFacade->activateBlogEntry($entryId);
//inny kod

}
}


Tutaj w tym kodzie "blogEntryRepository" to jest twój mapper.

Btw. co do reszty wpisu, wyobraź sobie, że nie ma tabel, są tylko obiekty. Ich utrwalanie jest tylko czynnością stricte techniczną i nie ma żadnego znaczenia z punktu widzenia użytkownika (jego interesuje zakup w sklepie a nie zapisanie czegoś w tabeli bazy danych). Jak widziałbyś taki serwis o którym mówiłeś ?

Chyba zaczynam rozumieć.

Pracuję z obiektami, zmieniam ich stan na różne sposoby, ale jeśli chciałbym utrwalić moją pracę to muszę prosić o pomoc Repozytorium.

Czyli, jeśli mam encję Koszyk, czy też Zamówienie, to nie powinna ona mieć w sobie typowo technicznych metod typu "zapiszZamowienie()" - bo to już podlegało by pod utrwalanie w bazie, a metodę taką powinienem mieć w servisie, w którym to zaś wykonujemy szereg operacji save/update przy pomocy repozytoriów?

Ciekawi mnie też, czy w serwisie muszę za każdym razem odwoływać się do repozytoriów - czasami trzeba coś przeliczyć, np koszt transportu na podstawie wartości produktów w zamówieniu i np na podstawie wybranej formy płatności, do tego zwykle dochodzą jakieś dodatkowe usługi i ich koszty. Korci żeby wykonać selecta z joinami ;) Jak sobie radzicie z takimi operacjami?

Teraz też widzę, jak wiele by mi pomógł entityManager :)

Generalnie wygląda to mniej więcej tak jak powiedziałeś. Żeby być precyzyjnym napisze może mały pseudokod jak widziałbym składanie zamówienia:


//encja
class Koszyk {
public function dodajProdukt(Produkt $produkt);
public function usunProdukt($idProduktu);
public function wartosc();
public function oznaczJakoZrealizowany();
public function listaProduktow();
}

//encja
class Zamowienie {
public function __construct(Uzytkownik $uzytkownik, Koszyk $koszyk, SposobWysylki $wysylka, SposobPlatnosci $platnosc);
public function oznaczJakoPrzyjete();
public function oznaczJakoZrealizowane();
public function oznaczJakoWyslane();
public function wartosc();
public function numer();
public function listaProduktow();
}

//klasa serwisu
class Sprzedawca {
public function przyjmijZamowienie(Zamowienie $zamowienie){
$zamowienie->oznaczJakoPrzyjete();
$this->zamowienia->save($zamowienie);
}
public function sprawdzCzyOplacono(Zamowienie $zamowienie){
return $this->ksiegowy->czyWplynelaOplataOTytule($zamowienie->numer());
}
public function zrealizujZamowienie(Zamowienie $zamowienie){
$this->magazyn->zmniejszStan($zamowienie->listaProduktow());
$zamowienie->oznaczJakoZrealizowane();
$this->zamowienia->save($zamowienie);
}
public function wyslijZamowienie(Zamowienie $zamowienie){
$zamowienie->oznaczJakoWyslane();
$this->zamowienia->save($zamowienie);
}
}


IMHO, entity manager jest świetny, sam też pisałem mappera i jest to trochę udręka ;)
Btw. "sprzedawca" jest singletonem, chyba, że chcemy przypisywać do konkretnych ludzi realizację jakiś zadań.Wojciech Soczyński edytował(a) ten post dnia 10.02.11 o godzinie 09:15
Artur Świerc

Artur Świerc Programista PHP/Java

Temat: Modele

Bartosz Ratajczyk:
Mnie sporo rozjaśnił ten artykuł: http://blog.fedecarg.com/2009/09/19/zend-framework-dal... (choć tutaj rolę mappera z quickstartu ZF pełni Repository, a mapper przekłada właściwości obiektu na nazwy elementów warstwy utrwalającej)

A! Federico. Dawno nie zaglądałem na tego bloga. Fajnie że dał wgląd do kodu.
Rzuciło mi się w oczy, że Federico nie używa w ogóle Zend_Db_Table:


$dataSource = Zf_Orm_Manager::getInstance()->getDataSource();
$db = $dataSource->getConnection('master');


Wydaje mi się to lepszym rozwiązaniem, nie tworzymy zbędnych klas.
Wojciech Soczyński:
Generalnie wygląda to mniej więcej tak jak powiedziałeś. Żeby być precyzyjnym napisze może mały pseudokod jak widziałbym składanie zamówienia:


//encja
class Koszyk {
public function dodajProdukt(Produkt $produkt);
public function usunProdukt($idProduktu);
public function wartosc();
public function oznaczJakoZrealizowany();
public function listaProduktow();
}

//encja
class Zamowienie {
public function __construct(Uzytkownik $uzytkownik, Koszyk $koszyk, SposobWysylki $wysylka, SposobPlatnosci $platnosc);
public function oznaczJakoPrzyjete();
public function oznaczJakoZrealizowane();
public function oznaczJakoWyslane();
public function wartosc();
public function numer();
public function listaProduktow();
}

//klasa serwisu
class Sprzedawca {
public function przyjmijZamowienie(Zamowienie $zamowienie){
$zamowienie->oznaczJakoPrzyjete();
$this->zamowienia->save($zamowienie);
}
public function sprawdzCzyOplacono(Zamowienie $zamowienie){
return $this->ksiegowy->czyWplynelaOplataOTytule($zamowienie->numer());
}
public function zrealizujZamowienie(Zamowienie $zamowienie){
$this->magazyn->zmniejszStan($zamowienie->listaProduktow());
$zamowienie->oznaczJakoZrealizowane();
$this->zamowienia->save($zamowienie);
}
public function wyslijZamowienie(Zamowienie $zamowienie){
$zamowienie->oznaczJakoWyslane();
$this->zamowienia->save($zamowienie);
}
}


IMHO, entity manager jest świetny, sam też pisałem mappera i jest to trochę udręka ;)
Btw. "sprzedawca" jest singletonem, chyba, że chcemy przypisywać do konkretnych ludzi realizację jakiś zadań.

Dziękuję bardzo za przykład :)

Nurtuje mnie bardzo zagłębianie się obiektów.
Np metoda Zamowienie::listaProduktow() - czy to ma wyglądać mniej więcej w taki sposób:

- pobieram ZamowienieEntity poprzez ZamowienieRepository::findById(10)
- w ZamowienieRepository::findById mapuje wynik na encję i zwracam ZamowienieEntity
- teraz wykonuje listaProduktow() - czy w tej metodzie odwołuję się do ProduktRepository::findByZamowienieId(10) i zwracam listę?

Albo przykład: User()->getAdres()->getTelefon() może tworzyć 3 odwołania do bazy, kiedy normalnie wystarczył by jeden skonkretyzowany join.Artur Świerc edytował(a) ten post dnia 10.02.11 o godzinie 23:02



Wyślij zaproszenie do