Tworzenie Aplikacji Internetowych dr Wojciech M. Gańcza 6 Dostęp do danych Dane możemy zapisywać lub odczytywać poleceniami SQL Niestety różne serwery baz danych mają różne wymagania co do zapytań Baza może ulegać zmianom Nie będziemy ciągle modyfikowali wszystkich miejsc w których korzystamy z danych Warto umieścić zapytania w jednym miejscu. Konstrukcja bazy Bazę można przygotować w dowolnym narzędziu do edycji danych Można ją odzyskiwać z backupu Ale co jeśli zmieni się struktura? Lepiej przygotować program który wypełni bazę danymi! Najlepiej jeśli będzie to funkcja w tej samej klasie do konstrukcja bazy DatabaseConstructor class DatabaseConstructor { var $database; var $dbcreator; var $creator; var $config; function DatabaseConstructor() { $this->database = new DatabaseAccess(); $this->dbcreator = new DBDatabaseCreator(&$this->database); $this->config = new DatabaseConfiguration(); $this->creator = new DBTableCreator(&$this->database); } Na początek przygotujemy sobie warsztat pracy Przydatne informacje Warto pamiętać stan bazy danych Oczywiście najprościej – w samej bazie danych Dodajmy tablicę w której będzie przechowywany numer wersji bazy oraz opis wersji (informacje o tym jakie dane znajdują się w bazie) Tak więc funkcja konstruująca bazę powinna zawierać: Tworzymy bazę $this->dbcreator->createDatabase( $this->config->database_name); $this->creator->startTable("Version"); $this->creator->addField("VerVersion", "name"); $this->creator->addField("VerState", "name"); $this->creator->createTable(); this->insert("insert into Version (VerVersion, VerState) values ('UUK 0.1.0', 'Empty');"); Status bazy Przed jakimikolwiek operacjami na bazie – warto sprawdzić jej stan: function state() { $data = $this->database->query( "select VerState from Version;"); if ($data->eof()) return "Nonexisting"; else { $data = $data->get(); return $data[0]; } } Insert Dodając rekordy warto wiedzieć jaki identyfikator baza nadała nowemu wpisowi Można to sprawdzić zadając odpowiednie zapytanie Warto takie zapytanie dodać do metody która wstawia dane do bazy Napiszmy metodę ‘insert’: Insert … function insert($query) { $this->database->query($query); $set = $this->database->query( "select LAST_INSERT_ID();"); $set = $set->get(); return $set[0]; } Funkcja wykonuje zapytanie i zwraca nadany identyfikator Dodawanie danych Do dodawania danych warto przygotować sobie funkcje function addSubject($subject) { $this->insert("insert into Subjects (SubjectsName) values ('$subject');" ); } Tworzymy tabelę i dane Tabele i dane testowe powinny być tworzone w jednej klasie Warto przygotować różne metody do Tworzenia pustej bazy – zawierającej same struktury danych Wypełniania danymi testowymi pustej bazy (warto sprawdzać status ) Wypełniania danymi niezbędnymi do pracy z pustą bazą Wypełnianie danymi W relacyjnej bazie danych dane mogą od siebie zależeć Jeśli chcemy umieścić referencje – to musimy ją znać Najprościej zapamiętać identyfikatory w tablicach I używać ich gdy są potrzebne przy wywoływaniu funkcji dodających elementy do kolejnych tabel Wypełnianie danymi … $subjects = array(); $subjects["angielski"] = $this->addSubject('Język angielski'); $subjects["biologia"] = $this->addSubject('Biologia'); $subjects["chemia"] = $this->addSubject('Chemia'); $subjects["fizyka"] = $this->addSubject('Fizyka'); Wypełnianie danymi … $books = array(); $books[0] = $this->addBook( 'Język polski. Między tekstami. Część 1. Podręcznik. Początki. Średniowiecze (echa współczesne)', $types["podrecznik"], $publishers["gwo"] ); $this->addBookSubject($books[0], $subjects["polski"]); $this->addBookAuthor($books[0], $authors["mackiewicz"]); Wypełnianie danymi … Metody wypełniające danym mogą być dość długie (paręset linii) Nie muszą być eleganckie – są pisane na potrzeby testowania programu A jeśli o testowaniu mowa – to warto przetestować czy wszystkie dane poprawnie dodały się do bazy To ważne – bo inne testy będą zakładały odpowiednią zawartość bazy Test danych testowych function test_000100_TestingTestdatabase(&$tester) { $tester->message("Testing test database"); $constructor = new DatabaseConstructor(); $constructor->dropDatabase(); $constructor->createDatabase(); $constructor->fillWithTestData(); $tester->test("Testing count of records in Authors table", countRowsOfTable(&$database, "Authors"), 80); $tester->test("Testing count of records in Books table", countRowsOfTable(&$database, "Books"), 88); $tester->test("Testing count of records in BooksAuthors relation table", countRowsOfTable(&$database, "BooksAuthors"), 217); I już wiemy czy wszystkie dane się dodały Dostęp do danych Dostęp do danych zorganizujemy w postaci pojedynczej klasy Metody będą zwracały źródła danych W metodach będziemy się odwoływali do bazy budując odpowiednie zapytania Centralizacja wszystkich zapytań bardzo ułatwia późniejsze modyfikacje struktury bazy (nie mamy oporów przed zmianami) Dostęp do danych … Dostęp rozpoczniemy od odczytu listy książek potrzebnych wybranemu użytkownikowi. Jako parametr – musimy podać użytkownika – pozostałe parametry są w bazie Dostęp do danych nie jest prosty – przy bazie zdefiniowanej tak jak to pokazywaliśmy – musimy utworzyć zapytanie ze złączeniami Lista książek function getBooksNeededByUser($userId) { return $this->database->query( "select distinct Books.BooksID, TypesName, BchSubjects, BokTitle, BchAuthors, PublishersName from UsersGrups inner join BooksGrups on UsersGrups.GrupsID=BooksGrups.GrupsID inner join Books on BooksGrups.BooksID=Books.BooksID left outer join BooksCache on Books.BooksID=BookId left outer join Publishers on PublishersID = PublisherID left outer join Types on TypesID = TypeID where UsersID=$userId order by BokTitle;"); } Lista książek … Wyświetlenie takiej listy jest skomplikowane a nie mamy jeszcze informacji czy książka jest w posiadaniu użytkownika oraz czy jest przez niego sprzedawana Ale wszystko po kolei… Najpierw test! Test jest bardzo prosty – wynik działania tej metody dla wybranego parametry – porównujemy ze wzorcem Test listy książek Możemy utworzyć nową funkcję testową lub dodać nowy test do istniejącego testu bazy testowej $tester->testRecordset( "Testing all books in database", $dataAccess->getBooksNeededByUser(1), 341760974); Możemy test wykonać także dla innych użytkowników Panel - Widok danych Panel naszego programu będzie zawierał listę książek Mogą to być różne listy Lista wszystkich książek (posiadanych i potrzebnych) Lista książek które trzeba kupić Listę książek które można sprzedać Panel powinien wyświetlać dane z podanego źródła danych Lista książek Wystarcz podać obiektowi klasy GridRenderer źródło danych i mamy gotową stronę $output = new Output(); $page = new PageFrame(); $panel = new BookPanel( /* definicja tabeli */, /* dane – na przykład userId */); $page->setPanel(&$panel); $page->render(&$output); BookPanel class BookPanel { function render(&$output) { $db = new DatabaseAccess(); $data = new DataSource(&$db); $grid = new GridBuilder(" Tytuł:LB3| Autor:CN3:60| Wydawnictwo:CN1:160| Przedmiot:LN1:250"); $grid->renderGrid(&$output, $data->getBooksNeededByUser(1)); } } Operacje na źródłach danych Dane ze źródła są mapowane do pola tabeli w stosunku 1:1 A tymczasem często chcemy umieścić kilka informacji w jednym polu tabeli – wyróżniając poszczególne części różnym krojem pisma Czy można osiągnąć taki efekt bez modyfikacji klasy tworzącej kod tabeli? Operacje na źródłach danych Użyjemy „dekoratora” – klasy która ma taki sam interface jak oryginalne źródło danych i przeformatowuje dane ze źródła podanego jako parametr Przygotujmy taki dekorator który umieści wszystkie informacje na temat ksiązki w jednej kolumnie Pozostałe kolumny będą nam potrzebne do określenia statusu ksiązki Składanie kolumn class Concatenator { var $src; function Concatenator(&$src) { $this->src = $src; } function eof() { return $src->eof(); } Składanie kolumn … function get() { $arr = $src->get(); $res = array(); $res[] = „<b>” . $arr[0] . „</b><i>” . $arr[1] . „</i><u>” . $arr[2] . „</u>” . $arr[3]; for ($i=4; $i<count($arr); ++$i) { $res[] = $arr[i]; }; } Składanie kolumn… Dobrze jest przygotować sobie kasę przeformatowująca dane w bardziej elastycznej postaci Przekażemy informacje o tym które kolumny powinny być łączone by utworzyć kolumnę wynikową Jeśli określimy tak informacje o wszystkich kolumnach – to możemy je także przetasowywać Formatowanie Dobrze sobie przygotować klasy bazowe do formatowania Klasy takie mogą wstępnie interpretować przekazane informacje dotyczące kolumn Wszystkie operacje będą miały taki sam interfejs Klasy formatujące będą wtedy bardzo proste Ale o tym – za tydzień. W następnym odcinku Co zrobić gdy zapytania są zbyt skomplikowane. Formatowanie danych ze źródła „w locie” Operacje na źródłach danych