Witaj Gościu! ( Zaloguj | Rejestruj )

Forum PHP.pl

> [ZF][ZendFramework] Bodowa Modelu
markus12
post
Post #1





Grupa: Zarejestrowani
Postów: 2
Pomógł: 0
Dołączył: 18.02.2011

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


Witam,

większość z was na pewno zna poradnik Quickstart w dokumentacji ZendFrameworka. Jest tam przykład budowy prostej książki gości.

I teraz chciałem zrobić katalog książek w ZF. Mam w bazie tabelki: Book, Author, Book_Author, Book_Genre, itd. Bazując na przykładzie modelu z dokumentacji Quickstart mogę zrobić coś w tym stylu:

  1. class Application_Model_Book
  2. {
  3. protected $_title;
  4. protected $_id;
  5.  
  6. public function __construct(array $options = null)
  7. {
  8. if (is_array($options)) {
  9. $this->setOptions($options);
  10. }
  11. }
  12.  
  13. public function __set($name, $value)
  14. {
  15. $method = 'set' . $name;
  16. if (('mapper' == $name) || !method_exists($this, $method)) {
  17. throw new Exception('Invalid book property');
  18. }
  19. $this->$method($value);
  20. }
  21.  
  22. public function __get($name)
  23. {
  24. $method = 'get' . $name;
  25. if (('mapper' == $name) || !method_exists($this, $method)) {
  26. throw new Exception('Invalid book property');
  27. }
  28. return $this->$method();
  29. }
  30.  
  31. public function setOptions(array $options)
  32. {
  33. $methods = get_class_methods($this);
  34. foreach ($options as $key => $value) {
  35. $method = 'set' . ucfirst($key);
  36. if (in_array($method, $methods)) {
  37. $this->$method($value);
  38. }
  39. }
  40. return $this;
  41. }
  42.  
  43. public function setTitle($text)
  44. {
  45. $this->_title = (string) $text;
  46. return $this;
  47. }
  48.  
  49. public function getTitle()
  50. {
  51. return $this->_title;
  52. }
  53.  
  54. public function setId($id)
  55. {
  56. $this->_id = (int) $id;
  57. return $this;
  58. }
  59.  
  60. public function getId()
  61. {
  62. return $this->_id;
  63. }
  64. }


  1. class Application_Model_BookMapper
  2. {
  3. protected $_dbTable;
  4.  
  5. public function setDbTable($dbTable)
  6. {
  7. if (is_string($dbTable)) {
  8. $dbTable = new $dbTable();
  9. }
  10. if (!$dbTable instanceof Zend_Db_Table_Abstract) {
  11. throw new Exception('Invalid table data gateway provided');
  12. }
  13. $this->_dbTable = $dbTable;
  14. return $this;
  15. }
  16.  
  17. public function getDbTable()
  18. {
  19. if (null === $this->_dbTable) {
  20. $this->setDbTable('Application_Model_DbTable_Book');
  21. }
  22. return $this->_dbTable;
  23. }
  24.  
  25. public function save(Application_Model_Book $book)
  26. {
  27. $data = array(
  28. 'title' => $book->getTitle(),
  29. );
  30.  
  31. if (null === ($id = $book->getId())) {
  32. unset($data['id']);
  33. $this->getDbTable()->insert($data);
  34. } else {
  35. $this->getDbTable()->update($data, array('id = ?' => $id));
  36. }
  37. }
  38.  
  39. public function find($id, Application_Model_Book $book)
  40. {
  41. $result = $this->getDbTable()->find($id);
  42. if (0 == count($result)) {
  43. return;
  44. }
  45. $row = $result->current();
  46. $book->setId($row->id)
  47. ->setTitle($row->title);
  48. }
  49.  
  50. public function fetchAll()
  51. {
  52. $resultSet = $this->getDbTable()->fetchAll();
  53. $entries = array();
  54. foreach ($resultSet as $row) {
  55. $entry = new Application_Model_Book();
  56. $entry->setId($row->id)
  57. ->setTitle($row->title);
  58. $entries[] = $entry;
  59. }
  60. return $entries;
  61. }
  62.  
  63. }


  1. class BookController extends Zend_Controller_Action
  2. {
  3.  
  4. public function init()
  5. {
  6. /* Initialize action controller here */
  7. }
  8.  
  9. public function indexAction()
  10. {
  11. $book = new Application_Model_BookMapper();
  12. $this->view->entries = $book->fetchAll();
  13. }
  14.  
  15. }


To jest w 90% kod z dokumentacji ZF. Ciekawe podejście, jedna klasa do obiektu książki ze zmiennymi takimi jak kolumny w bazie, druga klasa do operacji z bazą, której przesyłamy obiekt tej pierwszej klasy.

To wszystko rozumiem i jest ok. Ale teraz dochodzę do tego, że chciałbym pobrać listę książek wraz z nazwiskami ich autorów, gatunkami do których są przypisane i wyświetlić taką listę np. 20 książek wraz z pełnymi informacjami o każdej. W tej chwili metoda FetchAll wyciąga mi wszystko z tabelki Book. Nie mam pojęcia w jaki sposób podejść do tego, żeby dołączyć do tego też dane z innych tabel.

Tabela Author_Book jest do relacji między książkami i autorami. (czyli książka ma kilku autorów).

Nie wiem jak to ładnie zrobić żeby było zgodne z MVC itd. Czy porzucić całkowicie tej sposób z dwoma klasami czy nie, po prostu proszę o wskazówki w jaki sposób się takie coś robi.

pozdrawiam!
Go to the top of the page
+Quote Post
 
Start new topic
Odpowiedzi (1 - 8)
quality
post
Post #2





Grupa: Zarejestrowani
Postów: 172
Pomógł: 9
Dołączył: 13.02.2006
Skąd: Warszawa

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


Poczytaj:

http://framework.zend.com/manual/en/zend.db.select.html

pozdrawiam
Go to the top of the page
+Quote Post
markus12
post
Post #3





Grupa: Zarejestrowani
Postów: 2
Pomógł: 0
Dołączył: 18.02.2011

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


chodziło mi bardziej o to, czy pobierać z bazy dane o autorach powinienem w metodzie FetchAll klasy Application_Model_BookMapper i czy nazwiska autorów mają trafić do obiektu klasy Application_Model_Book. Czy może metoda FetchAll klasy Application_Model_BookMapper powinna poprosić metodę klasy Application_Model_AuthorMapper o autorów. Czy może powinienem pobrać same dane z tabelki Book i kontroler powinien na ich podstawie poprosić o autorów klasę modelu związanego z tabelką Autorów.

Jeśli zrobię wszystko w metodzie FetchAll klasy Application_Model_BookMapper to jakoś tak??:
  1. public function fetchAll()
  2. {
  3. $resultSet = $this->getDbTable()->fetchAll();
  4. $entries = array();
  5. foreach ($resultSet as $row) {
  6. $entry = new Application_Model_Book();
  7. $entry->setId($row->id)
  8. ->setTitle($row->title);
  9. // pobranie autorów, gatunków i wrzucenie ich do $entry??
  10. $entries[] = $entry;
  11. }
  12. return $entries;
  13. }
Go to the top of the page
+Quote Post
quality
post
Post #4





Grupa: Zarejestrowani
Postów: 172
Pomógł: 9
Dołączył: 13.02.2006
Skąd: Warszawa

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


Metoda fetchAll() - to jest standardowa metoda Zend_Db_Abstract - pobiera ona wszystkie rekorny z konkretnej tabeli.
Nadpisywanie jej nie sadze ze jest dobrym pomyslem.

Raczej proponuje ci stworzyc nowa metode publiczna fetchAllBook() i w niej napisac sobie zapytanie na podstawie Zend_Db_Select i tak pobrac dane.

I mowie poczytaj o Zend_Db_Select - bo tak Ci nie wyjdzie za bardzo laczenie tabel
Przyklad :

  1.  
  2. class Administration_Model_Wydawnictwa extends Zend_Db_Table {
  3.  
  4. protected $_name = 'wydawnictwa';
  5. protected $_primary = 'id';
  6.  
  7. /**
  8.   * Get all in list
  9.   */
  10. public function getWydawnictwa($id)
  11. {
  12. $select = $this->select()
  13. ->from(array('this'=>$this->_name),array('id','rodzaj_id','sort','publikacja','cena','nazwa','uwaga'))
  14. ->setIntegrityCheck(false);
  15.  
  16. $select ->join('base_categories AS d', 'd.id = this.dzial_id','name AS dzial')
  17. ->joinLeft('base_categories AS s', 's.id = this.stanowisko_id','name AS stanowisko')
  18. ->join('wydawnictwo_redaktorzy AS c', 'c.id = this.redaktor_id', 'c.id AS redaktor_id, CONCAT_WS(" ",c.imie, c.nazwisko) AS name')
  19. ->where("this.wydawnictwo_id=".$id)
  20. ->order('d.id ASC', 'this.sort ASC');
  21.  
  22. $rows = $this->_db->fetchAll($select);
  23.  
  24. return $rows;
  25. }
  26.  
  27. }


Pozdrawiam

Ten post edytował quality 18.02.2011, 15:32:57
Go to the top of the page
+Quote Post
Lysiur
post
Post #5





Grupa: Zarejestrowani
Postów: 66
Pomógł: 11
Dołączył: 25.07.2012

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


Witam, chciałbym odświerzyć nieco temat, ponieważ męczy mnie to samo zagadnienie co @markus12. Chodzi mi dokładnie jak najlepiej rozwiązać problem, gdy potrzebujemy pobrać "przysłowiową" książkę, oraz jej autorów, gatunek, etc. Oczywiście możemy zrobić funckję fetchAllBook(), ale zastanawia mnie jej zawrtość.

Użytkownik @quality, podał rozwiązanie bazujące na Zend_Db_Select wszsytko jest okej, ale wynik zwracany jest w postaci tablicy, kórej część pól nie ma odwzorowania w modelu Book. Zakładając, że przyjmujemy, iż mapper zwraca nam obiekt, to jakie atrybuty powinna posiadać klasa Book?

  1. class Application_Model_Book {
  2.  
  3. protected $_id = null;
  4. protected $_title = null;
  5.  
  6. protected $_autors = array();
  7.  
  8. public function setAutor(Application_Model_Autor $oAutor) {
  9. $this->_autors[] = $oAutor;
  10. return $this;
  11. }
  12. //....
  13. }
  14.  
  15. //Mapper
  16. public function fetchAllBook()
  17. {
  18. $resultSet = $this->getDbTable()->fetchAllBook(); // Czy zamiast tego lepiej poprstu zrobić Zend_Db_Select i zbudować zapytanie, a nie używać ZEnd_Db_Table w Application_Model_DbTable_Autor
  19. $entries = array();
  20. foreach ($resultSet as $row) {
  21. $entry = new Application_Model_Book($row); //Konstruktor uzupełni atrybuty
  22. $entry->setAutor(new Application_Model_Autor($row)); //j/w (o ile nie zostaną nadpisane),
  23. $entries[] = $entry;
  24. }
  25. return $entries;
  26. }
  27.  


Męczy mnie to zagadnienie, i nie bardzo mogę znaleźć rozwiązanie które by mnie satysfakcjonowało. Bo teraz szybko się okaże, że trzeba będzie wyświetlić obok listy książki i autorów, np.: gatunek, dział w którym się znajduje oraz np.: budynek. Model będzie się rozrastał i rozrastał. Rozbudujemy obiekt wg. sposobu opisanego wyżej.

Idąc dalej krokiem, będzie potrzeba w którymś miejscu wyświetlić jescze adres budynku w którym ta książka się znajduje, i mamy (np.: w widoku):
  1. //@var $oBook Application_Model_Book
  2. foreach(.... $key => $oBook):
  3. .... $oBook->getDepartament()->getAddress()->getFullAddres();
  4. endforach;


W takiej sytuacji robi się pewne misz-masz, bo na jednej liście (widoku) będziemy potrzebować wszystkich danych (łącznie z działem i budynkiem), a w innym widoku (bez tych dodatkowych danych). Wywołując fetchAllBook (w maperze) pakujemy obiekt złożony mimo, że nie zawsze jest on potrzebny.

Można zrobić tak, że w modelu Book, zrobić tak, że:

  1. class Application_Model_Book {
  2. ...
  3. public function getDepartament() {
  4. $mDepartament = new DeparatamentMapper();
  5. $this->departament = $mDepartament->findByIdBook($this->_id);
  6. }
  7. ...
  8. }


Jednakże w takiej sytuacji przy foreachowaniu $oBook będziemy za każdym razem wysyłali dodatkowe sqlki.


Jak rozwiązujecie takie sytuacje??

Ten post edytował Lysiur 14.12.2012, 10:54:40
Go to the top of the page
+Quote Post
irmidjusz
post
Post #6





Grupa: Zarejestrowani
Postów: 279
Pomógł: 60
Dołączył: 25.02.2012

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


Ja robię różnie w zależności od potrzeb. Chodzi głównie o wydajność pobierania danych z bazy. Im więcej zapytań, tym gorzej, więc lepiej robić jedno zapytanie pobierające dane do tabeli głównej z 3 joinami, niż 4 oddzielne zapytania. Ale... różnie to bywa, bo po pierwsze, zależy ile jest na raz wierszy pobieranych, ile joinowanych tabel i ile kolumn mają te tabele, bo budowanie i wypełnianie tych wszystkich obiektów-modeli zabiera czas i zużywa pamięć, i może to być naprawdę duży problem. Dlatego trzeba to wyważyć. Można też pisać sobie specjalne metody do pobierania różnych zestawów danych w zależności od zapotrzebowania - np. na jednym widoku potrzebujesz wyświetlić tylko 2 kolumny z tabeli A, a na innym 3 kolumny z tabeli A i jeszcze 8 kolumn z joinowanych tabel B, C i D - więc dwie różne metody będą w sam raz.
Poza tym, do pobierania danych tylko-do-odczytu (tylko do prezentacji) nie trzeba ich pobierać jako obiektów ORM, a wystarczą przecież proste zapytania SQL i wyniki tablicowe - potrafi to być znacznie wydajniejsze, niż pobranie tych samych danych za pomocą modeli. Modele są dobre głównie do logiki biznesowej i zapisu danych - bardzo wygodne, w zasadzie trudno je przecenić. Ale wyświetlanie (zwykle jest o kilka rzędów wielkości więcej operacji odczytu, niż zapisu) nie potrzebuje modeli - dedykowane, zoptymalizowane zapytania SQLowe o konkretne dane, zwracane jako tablice, potrafią być zdecydowanie wydajniejsze (wszystko zależy od obciążenia strony i ilości pobieranych danych oraz skomplikowania zapytań).
Go to the top of the page
+Quote Post
Lysiur
post
Post #7





Grupa: Zarejestrowani
Postów: 66
Pomógł: 11
Dołączył: 25.07.2012

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


Dziękuje @irmidjusz za odpowiedź, myślałem ostatnio nad tym co napisałeś, a przede wszystkim jak najoptymalnie zwracać wyniki (np.: złączonych kilku tabel) z Mappera. Domyślnie mapper powinien zwracać nam obiekt, ponieważ faktycznie często będzie tak, że zawartość złączonych tabelach będzie miała w jednym widoku 3 kolumny A, 2 B lub w innej konfiguracji. Niestety często jest tak, że w takich sytuacjach przydało by mi się na wejściu dostać obiekt, który byłby w stanie z wybranych atrybutów danego obiektu, np.: person zwrócić tablicę zawierającą id,firstName,surname, gdyż np.: z takiej tablicy poprzez helpera tworzę link do profilu czy ajaxowe okienko. Dlatego wygoniej będzie i jendolicie będzie wywołać w widoku

  1. $this->Profile_Link($oPerson->getPersonLink('worker'));
  2. //niż konstrukcja
  3. $this->Profile_Link(array('id'=>'123213','firstName'=>'P','surname'=>'Test'));


Pomyślałem, że zrobię clasę, np.: PersonList, który miałby wszystkie wymagane pola (zwracane, przez którekolwiek zapytanie łączące tabele). Zwracany wynik przez fetchAll() - czyli tablica, mapper wstawiałby mi to w klasę typu (kolekcji) PersonsCollection (implementująca Iterator,Countable), i przypadku chęci pobrania danego wiersza (złączonego z kilku tabel), zwracałby właśnie new PersonList();

Aczkoliwek, to narazie koncepcja, wyjdzie w praniu czy takie rozwiązanie ma sens, i czy nie jest zbyt zasoborzerne. Napewno będzie "cięższe" niż przekazanie samej tablicy zwracane przez mapper'a, ale rozwiązanie może ujednolicić kod.
Go to the top of the page
+Quote Post
CuteOne
post
Post #8





Grupa: Zarejestrowani
Postów: 2 958
Pomógł: 574
Dołączył: 23.09.2008
Skąd: wiesz, że tu jestem?

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


Weź pod uwagę fakt, że zapytanie, które łączy kilka tabel, może zwrócić coś takiego:
  1. $rows = array(
  2. array('id'=>1, 'title'=>'xxx', 'autor' => 'Heniu'),
  3. array('id'=>1, 'title'=>'xxx', 'autor' => 'Wojtek'),
  4. array('id'=>1, 'title'=>'xxx', 'autor' => 'Pędzisław')
  5. );

a co gorsza to samo zapytanie, może zwrócić
  1. $rows = array(
  2. array('id'=>1, 'title'=>'xxx', 'autor' => 'Heniu'),
  3. array('id'=>2, 'title'=>'yyy', 'autor' => 'Heniu'),
  4. array('id'=>2, 'title'=>'yyy', 'autor' => 'Wojtek')
  5. );

lub
  1. $rows = array(
  2. array('id'=>1, 'title'=>'xxx', 'autor' => 'Heniu')
  3. );


edit: do dzisiaj nie znalazłem odpowiedniej metody przekształcania tego typu danych w obiekt bez użycia:
  1. $array = array();
  2. foreach($rows as $row) {
  3.  
  4. if(!isset($array[$row->id])) {
  5. $array[$row->id] = $row;
  6. $array[$row->id]['autor'][] = $row->autor;
  7. }
  8. else {
  9. $array[$row->id]['autor'][] = $row->autor;
  10. }
  11. }

(IMG:style_emoticons/default/wink.gif)

Ten post edytował CuteOne 20.12.2012, 12:39:37
Go to the top of the page
+Quote Post
ze4lot
post
Post #9





Grupa: Zarejestrowani
Postów: 54
Pomógł: 1
Dołączył: 29.03.2007
Skąd: Kraków

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


Spotkałem się kiedyś z modelami pomocniczymi agregującymi inne modele dziedziczące bo Zend_Db_Table. Zrobione to było na zasadzie Dependency Injection. Modele pomocnicze nie miały swoich tabel w bazie, a jedynie obsługiwały i łączyły standardowe modele.
Go to the top of the page
+Quote Post

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

 



RSS Aktualny czas: 22.08.2025 - 23:50