Pomoc - Szukaj - Użytkownicy - Kalendarz
Pełna wersja: [ZF][ZendFramework] Bodowa Modelu
Forum PHP.pl > Forum > PHP > Frameworki
markus12
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!
quality
Poczytaj:

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

pozdrawiam
markus12
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. }
quality
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
Lysiur
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??
irmidjusz
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ń).
Lysiur
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.
CuteOne
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. }

wink.gif
ze4lot
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.
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.