Archive

Archive for the ‘Bazy danych’ Category

csplit a olbrzymie pliki

December 2nd, 2009 No comments

Moim zadaniem było naprawić uszkodzony plik kopii bezpieczeństwa bazy danych PostgreSQL. Wiedziałem w której linii jest błąd i na czym polega, jednak plik kopi bezpieczeństwa był na tyle olbrzymi, że edycja jego edycja była bardzo utrudniona. Z pomocą przyszedł mi program csplit znaleziony w pakiecie GnuWin32. Program ten potrafi podzielić plik na mniejsze na podstawie dopasowania przez wyrażenie regularne. Wydałem następujące polecenie:

csplit –f "out_" nazwa_pliku_backup.sql "/^COPY /" {*}
head –n 1 out_*

W wyniku pracy programu csplit uzyskałem kilkadziesiąt mniejszych plików – po jednym dla każdej tabeli w bazie.

Categories: Bazy danych, Programowanie Tags:

Bezpieczeństwa kopiowania danych podczas prac programistycznych

September 6th, 2009 4 comments

Programując nową wersję aplikacji, często zaczynamy od zrobienia kopii aktualnej wersji systemu produkcyjnego. To kusząca idea, jednak należy pamiętać o pewnych zasadach bezpieczeństwa z tym związanych. Idee jest kusząca – ponieważ budując nowe funkcjonalności testujemy je w warunkach zbliżonych do produkcyjnych. Dzięki temu łatwiej jest wyłapać błędy związane z wydajności i łatwiej jest przeprowadzać testy.

Robiąc kopię bazy danych, należy pamiętać o pewnych zasadach bezpieczeństwa. Gdy kopia danych systemu ma być używana do celów deweloperskich – to należy zniszczyć wszystkie wrażliwe dane w bazie: imiona i nazwiska, hasła, adresy, numery telefonów. Taka kopia danych może być bowiem użyta na laptopach programistów, lub na słabiej zabezpieczonych systemach. Warto więc wypracować pewne procedury w tym zakresie.

Kilka lat temu zaproponowałem następującą procedurę: po wykonaniu kopii wszystkie imiona, hasła, adresy, nazwy i temu podobne dane zostaną zniszczone. Są trzy typy niszczenia.

Niszczenie tekstów i nazw własnych. Dla każdego znaku w ciągu znaków powinien zostać wylosowany inny znak. Czyli na przykład, gdy w bazie danych był ciąg znaków “Antoni Jakubiak” to mógł zostać zastąpiony “Xhkkia Uksbtlka”. Dzięki temu podczas testowania systemu widzimy ciągi znaków na ekranie, i zajmują one mniej więcej tyle samo miejsca co prawdziwe dane. Jest to rozwiązanie nieco lepsze, od kasowania danych lub losowania nowych danych w całości gdyż testuje się wygodniej.

Niszczenie adresów e-mail jest szczególnie ważne. Po pierwsze, adresy e-mail naszych użytkowników nie powinny się gdzieś zgubić. Po drugie, gdy nasz system wysyła e-mail automatycznie, to głupio by były żeby wyszły one z testowego systemy do prawdziwego użytkownika: “Kupiłeś sokowirówkę – obciążyliśmy twoją kartę kredytową”. Uważam, że najlepiej jest zastąpić wszystkie adresy e-mail występujące w bazie danych – własnym adresem e-mail. Podoba sprawa dotyczy numerów telefonów, szczególnie gdy nasz system dzwoni lub automatycznie wysyła SMSy. Gorzej, jeżeli adres e-mail jest polem unikalnym – na przykład loginem. Wtedy trzeba się napracować w stylu: update auth set email=concat(‘mój.adres.email+dev’||next_val(‘jakaś.tam.sekwencja’)||’@gmail.com’. Z całą pewnością jednak warto!

Niszczenie haseł. Hasła w bazie danych zwykle są jakoś zaszyfrowane, jednak ich kradzież, szczególnie połączona z kradzieżą loginu lub e-mail, będzie kompromitująca. Uważam, że hasła najlepiej jest po prostu skasować. W testowej kopii systemu można też ustawić wszystkim użytkownikom jednakowe hasła – jednak lepiej je po prosu skasować zupełnie. Testerzy systemu założą sobie po prostu nowe hasło w procedurze jego odzyskiwania. Oczywiście, jeżeli wcześniej zmieniliśmy adresy e-mail.

Napisanie takiej procedury dobremu programiście znającemu system zajmie na pewno mniej, niż dzień roboczy. Warto, żeby była to procedura półautomatyczna, lub żeby w jakiejś dokumentacji programistycznej systemu została ona zapisana. Warto pilnować, by taka procedura była przestrzegana. Ogólnie, warto pamiętać o tym by pilnować backupów systemu.

Categories: Bazy danych Tags:

Jak implementować drzewo w bazie danych?

April 22nd, 2009 4 comments

Drzewo w bazie danych to jeden z problemów, o których fajnie się dyskutuje na studiach. Istnieje wiele sposobów implementacji drzewa, znany hacker Depesz wymienił 5. Niestety, często zapomina się o najważniejszych i najprostszych metodach. Żadnych identyfikatorów rodzica. Tylko ścieżka – która jest opisana tekstowo. Lub identyfikator rodzica i ścieżka. Na przykład:

Identyfikator rekordu

Identyfikator rodzica

Ścieżka

1

 

0001

2

1

0001 / 0002

3

1

0001 / 0003

4

3

0001 / 0003 / 0004

5

3

0001 / 0003 / 0005

6

1

0001 / 0006

7

1

0001 / 0007

 

Jakie są zalety takiego rozwiązania? Prostota implementacji, błyskawiczne wyszukiwanie potomków, szybkie wyszukiwanie rodziców, miłe przeglądanie bazy w konsoli SQL, szybkie sortowanie. Oczywiście, gdy potrzebujemy sortowania to należy użyć dodatkowego identyfikatora kolejności, na przykład:

Identyfikator rekordu

Identyfikator rodzica

Kolejność

Ścieżka # Kolejność

1

 

1

0001 # 0001

2

1

2

0001 / 0002 # 0002

3

1

1

0001 / 0003 # 0001

4

3

1

0001 / 0003 / 0004 # 0001

5

3

2

0001 / 0003 / 0005 # 0002

6

1

3

0001 / 0006 # 0003

7

1

4

0001 / 0007 # 0004

 

Zestawienie zalet i wad rozwiązanie z ścieżką:

  • Wady
    • Więcej miejsca na dysku
  • Zalety
    • Prosta implementacja
    • Prostota modelu
    • Błyskawiczne wyszukiwanie potomków
    • Szybkie wyszukiwanie rodziców
    • Szybkie sortowanie
    • Miłe przeglądanie bazy w trybie administratora

Czy są lepsze rozwiązania? Oczywiście. Wystarczy wprowadzić założenie, że w naszym drzewie może być tylko kilka poziomów zagłębienia i liczba ta jest określona przez programistę systemu. Wtedy, w bazie danych możemy zapisywać informację w następujący sposób:

Identyfikator rekordu

Rodzic poziom 1

Rodzic poziom 2

Kolejność

1

  

1

2

1

 

2

3

1

 

1

4

1

3

1

5

1

3

2

6

1

 

3

7

1

 

4

 

W świecie prawdziwych problemów, często właśnie ta trywialna implementacja drzewa będzie najlepsza: jest najprostsza w implementacji i jest rewelacyjnie szybka. Przykłady kodu:

Moim zdaniem, najprostsze rozwiązanie jest zwykle najlepsze.

Categories: Bazy danych Tags:

PostgreSQL, dane binarne i dumpy

November 14th, 2008 1 comment
Zauważyłem nowa fajną cechę w PostgreSQL. Otóż potrafi on wykonać tekstowy zrzut bazy danych, która zawiera dane binarne. Czyli, mam w tabelkach PostgreSQL pliki wgrane przez użytkowników. Robię pg_dump i wszystko działa poprawnie. Kiedyś tak nie było i dlatego teraz się cieszę.
Categories: PostgreSQL Tags:

Tłumaczenia w aplikacji na wiele sposobów

December 3rd, 2007 6 comments
Zdarzyło mi się napisać aplikacje WWW, która miała działać w wielu krajach, w wielu językach. Potrzebowałem systemu tłumaczeń. Mówiąc systemu mam na myśli systematyczne podejścia do sprawy tłumaczeń. Tłumaczenia w mojej aplikacji są częścią jej biznesu. Najprościej zrozumieć to na przykładzie. Jest sobie hotel który ma opis. Ten opis może być przetłumaczony na wiele języków. Do systemu można wprowadzić nowe hotele a każdy z nich będzie miał inne tłumaczenie. Wyobraźmy sobie tabelkę hotel. Ma ona trzy pola: identyfikator, nazwa i opis. Opis to coś co trzeba przetłumaczyć. Ale jak to zrobić?
create table hotel(
  primary key int id,
  varchar nazwa,
  varchar opis
)
Istnieje wiele sposobów rozwiązania tego problemu. Do głowy przychodzą mi takie:
  • W systemie tworzymy tabelę hotel zawierającą kolumnę opis dla każdego z dostępnych języków. Będzie to wyglądać mniej więcej tak:
    table hotel(
      primary key int id,
      varchar nazwa,
      varchar opis_en, 
      varchar opis_de, 
      varchar opis_pl
    )
    
    Przedstawione rozwiązanie powinno działać bardzo szybko, jednak ma wadę – dodanie kolejnego języka do wymaga modyfikacji bazy danych – a to jest brzydkie.
  • Dla każdej wersji językowej tworzymy osobną tabelę zawierającą pola, które trzeba przetłumaczyć. Wygląda to mniej więcej tak:
    table hotel(
      primary key int id,
      varchar nazwa,
    )
    table hotel_en(
      primary key int id,
      varchar opis
    )
    table hotel_pl(
      primary key int id,
      varchar opis
    )
    
    To rozwiązanie będzie działać szybko. Dodanie nowego języka będzie wymagać modyfikacji bazy danych. Oczywiście, korzystając z tego lub z poprzedniego rozwiązania warto napisać program, który będzie tłumaczył zapytania SQL i pobierał dane z odpowiednich tabel językowych. Obydwa te rozwiązania będą działać szybko jednak moim zdaniem nie są one ładne.
  • Znam jeszcze coś takiego:
    table hotel(
      primary key int id,
      varchar nazwa
    )
    table hotel_tłumaczenia(
      primary key int id,
      int identyfikator_języka,
      varchar opis
    )
    
    To rozwiązanie jest lepsze od poprzednich tym, że wprowadzenie do systemu nowego języka nie będzie wymagało zmiany w strukturze danych.
  • Ja najbardziej lubiłem jednak tworzenie tablicy słownikowej. W uproszczeniu wygląda to tak:
    table słownik (
      primary key int identyfikator_tłumaczonego_tekstu,
      int identyfikator_języka,
      varchar tłumaczenie
    )
    table hotel(
      primary key int id,
      varchar nazwa,
      identyfikator_tłumaczonego_tekstu opis
    )
    
    To rozwiązanie jest o tyle fajne, że proste. Nie wymaga modyfikacji struktury bazy danych w momencie dodania nowego języka. Łatwo też pisać zapytania SQL, które pobierają teksty w wybranym języku. Niestety, to rozwiązanie jest wolniejsze, szczególnie wtedy gdy tabelka hotel ma wiele pól które chcemy przetłumaczyć.
Myślę, że można wymyślić jeszcze wiele innych rozwiązań tego problemu. Schemat myślenia można przenieś ze świata relacji do języków programowania obiektowego. Można zrobić też coś takiego:
@Entity
class Hotel{
  @Id
  private Long id;
 
  private String nazwa;

  @Tłumaczenie
  private String opis;
}
Tak to nowa JAVA. Dzięki adnotacjom możemy programować wielowymiarowo. Więc dlaczego by nie stworzyć dla naszej aplikacji nowego wymiaru – wymiaru tłumaczeń??? Ja właśnie to robię. Tworzę bibliotekę Java, która programistom będzie dostarczać wygodnego narzędzia rozwiązującego problem tłumaczeń. Tłumaczenia, nie będą już częścią aplikacji. Będą jej nowym wymiarem. Zainteresowanych zapraszam do projektu: JPA Translator
Categories: Java, JPA, SQL Tags:

Zend Framework i keszowanie zapytań do bazy danych

June 23rd, 2007 3 comments
Witam, Od pewnego czasu programiści PHP naśladują programistów Javy, sam zresztą to robię. Efektem tego jest przenoszenie wzorców projektowych z Javy do PHP. Na przykład Zend Framework a w szczególności jego kod odpowiedzialny za bazy danych – Zend_Db (działający prawie jak ORM) przypomina w pewien sposób to, za co świat Javy pokochał Hibernate. Konsekwencją użycia ORM w projekcie jest to, że spada nam złożoność zapytań do bazy danych, natomiast zwiększa się ich ilość. Hibernate potrafi keszować obiekty. Zend Framework niestety nie. Jednak wczoraj tego bardzo potrzebowałem, renderowanie prostej strony WWW wymagało 500 zapytań do bazy, grrr, z czego 450 było powtórzeniem poprzednich zapytań. O ile keszowanie obiektów było by bardzo skomplikowane, to w miarę prosto można było keszować wyniki zapytań SQL w obrębie jednego żądania HTTP. Jak się do tego zabrać? Zauważyłem, że wszystkie zapytania do bazy danych produkowane przez Zend Framework w pewnym momencie trafią do metody query obiektu Zend_Db_Adapter_*, fajnie:). Naszą klasę keszującą trzeba odziedziczyć po tej klasie. Oczywiście można to zrobić na wiele innych sposobów, i bardzo ładnie wpasować w to wzorzec projektowy dekorator, ale ja nie miałem czasu. Modyfikujemy metodę query, tak by zapamiętywała swoje wyniki i trzymała w keszu. U mnie działa coś takiego (wstydził bym się tego kodu, gdyby nie to że robi coś na prawdę przydatnego):
class JakubiakDbAdapterPdoPgsql extends Zend_Db_Adapter_Pdo_Pgsql{
  public function query($sql, $bind = array()){
    $sql = $sql . ""; // a sprobuj no bez tego
    $hash = $this->_calculateHash($sql,$bind);
    // czy uda nam sie pobrac z kesza
    if($this->_isCacheEnabled) {
      if($this->_allowCache($sql,$bind)) {
        if(isset($this->_cachedResults[$hash])) {
          return $this->_cachedResults[$hash];  
        } 
      }
    }
    $res = parent::query($sql, $bind);
    // dopisanie do kesza
    if($this->_isCacheEnabled) {
      $this->_cachedResults[$hash] = $res;
    }
    return $res;
  }
  // pozwalam na keszowanie tylko zapytan rozpoczynajacych sie od select
  private function _allowCache($sql,$bind) {
    return preg_match("/^select/i",$sql));
  }
  private function _calculateHash($sql,$bind) {
    return md5($sql.var_export($bind,true));
  }
  [...]
}
Ten kod omal nie zadziała. Jednak trzeba zrobić jeszcze parę myków. Przeciążyć konstruktor naszego adaptera.
  public function __construct($config) {
    parent::__construct($config);
    $this->getConnection()->setAttribute(PDO::ATTR_STATEMENT_CLASS, array('JakubiakDbStatement', array($this)));
  }
I dopisać taką oto koszmarną klasę:
class JakubiakDbStatement extends PDOStatement {
  $_cache = null;
  public function fetchAll($how, $class_name=null, $ctor_args=null) {
    if(!empty($this->_cache)) {
      return $this->_cache;
    }
    $this->_cache = parent::fetchAll($how);
    return $this->_cache;
  }
}
Ten kod, ma prawo nie działać i zawiera błędy. Jednak u mnie działa. I to dobrze działa. Zamiast 500 zapytań do bazy mam ich 50. Serwer jest mi za to wdzięczny, nie trzeba już oliwić wentylatorka od chłodzenia;). PS. do testów użyłem starej wersji Zend_Framework – 0.9.1.
Categories: ORM, PHP, PostgreSQL, Zend Framework Tags:

Hibernate i logowanie zapytań SQL wraz w wartościami parametrów

June 1st, 2007 No comments
Witam. Hibernate potrafi logować zapytania SQL. Można to skonfigurować na kilka sposobów. Ja wybrałem konfiguracje z log4j:
# logowanie zapytań SQL
log4j.logger.org.hibernate.SQL=DEBUG, R
log4j.additivity.org.hibernate.SQL=false
# logowanie wartości wstawianych do SQL
log4j.logger.org.hibernate.type=DEBUG
Pozdrawiam!
Categories: JPA, ORM Tags:

Zend Framework, zachciało mi się HQL’a

April 22nd, 2007 1 comment
Zend Framework – framework, o którym ostatnio sporo pisałem. HQL język używany w Hibernate, będący połączeniem ciemności świata relacji i światłości obiektów. HQL było inspiracją dla JPQL , dzięki czemu stanie się standardem przemysłowym. HQL jest fajny, bo można pisać na przykład tak:
from Movie m where m.media.user.name='Antek';
zamiast dłubać w SQL:
select m.* from movie v 
join media m on m.mediaid=v.mediaid 
join user u on u.userid=m.userid 
where user.name='Antek';
Niestety, Zend Framework nie posiada czegoś takiego i nie zanosi się na to. Ja, jednak klepnąłem świniaka, dzięki któremu mogę pisać podobnie. Zmodyfikowałem klasę: Zend_Db_Table ^ JakubiakDbTable. Nowy kod to:
protected function _fetch($where = null, $order = null, 
$count = null, $offset = null) {
  $select = $this->_db->select();
  $select->from($this->_name, $this->_cols);
  $where = (array) $where;
  foreach ($where as $key => $val) {
    if (is_int($key)) {
      $select->where($val);
    } else {
      // drobna modyfikacja rodzica
      $key = $this->refJoin($select,$key);
      $select->where($key, $val);
    }
  }
  // dalej tak jak u rodzica[...]
}
public function refJoin($select, $where) {
  foreach($this->_referenceMap as $key => $ref) {
    $regExp = "#^$key\.#";
    if (preg_match($regExp,$where)) {
      $refDao = new $ref['refTableClass'];
      $on = "";
      foreach($ref['columns'] as $i => $colName){
        if($i>0) $on .= ' and ';
        $on .= ' ';
        $on .= $this->getTableName() . '.' . $colName . '=';
        $on .= $refDao->getTableName() . '.' .$ref['refColumns'][$i] . ' ';
      }
      $select->join($refDao->getTableName(), $on,array());
      $where = preg_replace($regExp,"",$where);
      return $refDao->refJoin($select,$where); 
    }
  }
  return $this->getTableName().'.'.$where;
}
public function getTableName() {
  return $this->_name;
}
Kod powyżej działa, ale jest zły. Nie używaj go. Ja mogę go używać tak:
$movieTable = new MovieTable();
$movies = $movieTable->fetchAll(array("media.user.name=?"=>$_REQUEST['username']));
Czyli, prawie, prawie jak HQL.
Categories: ORM, PHP, Zend Framework Tags:

Zend Framework i PostgreSQL aka ORM

April 21st, 2007 No comments
Proponuje kilka moich poprawek do Zend Framework 0.9.2. Dzięki temu praca z tą biblioteką jest jeszcze przyjemniejsza. Wersja 0.9.2 ma parę niedociągnięć, jednak można je poprawić poprzez dziedziczenie. Zacznę od klasy Zend_Db_Adapter_Pdo_Pgsql PostgreSQL. Pisałem już o niej. Proponuje następujące ulepszenia:
class JakubiakDbAdapterPdoPgsql extends Zend_Db_Adapter_Pdo_Pgsql{
  public function query($sql, $bind = array()){
    // poprawienie błędu dla typu Boolean
    if(is_array($bind)){
      foreach($bind as $k => $v) {
        if(is_bool($v)) {
          $bind[$k] = $v ? 't' : 'f';
        }
      }
    }
    return parent::query($sql, $bind);
  }
  public function lastInsertId($tableName = null) {
    // Jeżeli sekwencja nazywasz inaczej niż domyślnie, to prawdopodobnie trzeba
    // będzie nadpisać też tą funkcję, u mnie wygląda ona tak 
    // sekwencje dla kluczy głównych nazywam {nazwa_tabelki}_seq
    if (!$tableName) {
      throw new Zend_Db_Adapter_Exception("Sequence name must be specified");
    }
    $this->_connect();
    $sequenceName = "{$tableName}_seq";
    $sequenceName = preg_replace('/_+/','_',$sequenceName);
    return $this->_connection->lastInsertId($sequenceName);
  }
Teraz zabawię się z klasą która reprezentuje tabelę Zend_Db_Table. Jest ona podwaliną ORM, dlatego mi się podoba:
class JakubiakDbTable extends Zend_Db_Table {
  // chcę, aby wszystkie wiersze w tabelkach były reprezentowane przez
  // obiekty tej klasy
  protected $_rowClass = 'JakubiakDbTableRow';
  // następnie poprawiam błąd - lastInsertedId
  public function insert(array $data)  {
    $this->_db->insert($this->_name, $data);
    return $this->_db->lastInsertId(empty($this->_seqName)
      ? $this->_name : $this->_seqName);
  }
  // przydatna funkcja do zliczania wierszy w tabeli
  public function count(array $where = array()) {
    $select = $this->_db->select();
    $select->from($this->_name, array('count'=>'count(*)'));
    $where = (array) $where;
    foreach ($where as $key => $val) {
      if (is_int($key)) {
        $select->where($val);
      } else {
        $select->where($key, $val);
      }
    }
    $select->limit(1, 0);
    $stmt = $this->_db->query($select);
    $data = $stmt->fetch();
    return empty($data['count']) ? 0 : intval($data['count']);
  }
  // pobieranie nowej encji
  public function fetchNew() {
    $newRow = parent::fetchNew();
    $newRow->loadDefaults();
    return $newRow;
  }
  // to się jeszcze przyda, aczkolwiek wydaje mi się, że ta funkcja trafi prędzej czy 
  // później do core
  public function getReferenceMap($key){
    if(empty($this->_referenceMap[$key])){
      return null;
    }
    return $this->_referenceMap[$key];     
  }
}
I jeszcze klasa która reprezentuje encje Zend_Db_Table_Row – lub jak kto woli – wiersz w tabeli:
class JakubiakDbTableRow extends Zend_Db_Table_Row {
  // czy jesteśmy nową encją?
  private $_isNew = false;
  // wczytanie domyślnych wartości, na podstawie metadanych zapisanych w tabeli
  public function loadDefaults() {
    $db = $this->_getTable()->getAdapter();    
    $info = $this->_getTable()->info();
    foreach($info['metadata'] as $col => $meta){
      if(empty($meta['DEFAULT'])) continue;
      $default = $meta['DEFAULT'];
      $one = $db->query("select $default as def")->fetch();
      $this->_data[$col] = $one['def'];
    }
    $this->_isNew = true;
  }
  // zapisywanie rekordu do bazy, kod prawie taki sam jak w klasie nadrzędnej,
  // ale wykorzystuje prywatną zmienną isNew
  public function save() {
    $keys = $this->_getPrimaryKey();
    $values = array_filter($keys);
    if ($this->_isNew) {
      $this->_insert();
      $result = $this->_getTable()->insert($this->_data);
      if (is_numeric($result)) {
        $this->_data[key($keys)] = $result;
        $this->_refresh();
      }
    } else {
      $where = $this->_getWhereQuery(false);
      $this->_update();
      $depTables = $this->_getTable()->getDependentTables();
      if (!empty($depTables)) {
        $db = $this->_getTable()->getAdapter();
        $pkNew = $this->_getPrimaryKey(true);
        $pkOld = $this->_getPrimaryKey(false);
        $thisClass = get_class($this);
        foreach ($depTables as $tableClass) {
          Zend_Loader::loadClass($tableClass);
          $t = new $tableClass(array('db' => $db));
          $t->_cascadeUpdate($this->getTableClass(), $pkOld, $pkNew);
        }
      }
      $result = $this->_getTable()->update($this->_data, $where);
      if (is_int($result)) {
        // update worked, refresh with data from the table
        $this->_refresh();
      }
    }
    return $result;
  }
  // pobieranie many to one w bardziej intuicyjny sposób
  public function __get($key) {
    $r = $this->_getTable()->getReferenceMap($key);
    if (!empty($r)) {
      // tu można dopisać loader dla klasy 
      return $this->findParentRow($r['refTableClass'],$key);
    }
    return parent::__get($key);
  }
  // domyślnie setter zabrania zmieniać wartości klucza głównego, 
  // ale czasami chcę to robić, kod prawie taki sam jak rodzica
  public function __set($key, $value) {
    if (!$this->_isNew && in_array($key, $this->_primary)) {
      require_once 'Zend/Db/Table/Row/Exception.php';
      throw new Zend_Db_Table_Row_Exception("Changing the primary key value(s) is not allowed");
    }
    if (!array_key_exists($key, $this->_data)) {
      require_once 'Zend/Db/Table/Row/Exception.php';
      throw new Zend_Db_Table_Row_Exception("Specified column \"$key\" is not in the row");
    }
    $this->_data[$key] = $value;
  }
Po tej zabawie, mogę używać bibliotek na przykład tak:
$fileTable = new FileTable();
$fileRow = $fileTable->fetchNew($_FILES[$filename]);
$fileRow->save();
$mediaTable = new MediaTable();
$mediaRow = $mediaTable->fetchNew();
$mediaRow->mediatitle = $request->getParam('mediatitle');
$mediaRow->mediadescription = $request->getParam('mediadescription');
$mediaRow->fileid = $fileRow->fileid;
$mediaRow->userid = UserTable::getFromAuth()->userid;
$mediaRow->save();
$photoTable = new PhotoTable();
$photoRow = $photoTable->fetchNew();    
$photoRow->mediaid = $mediaRow->mediaid;
$photoRow->photowidth = $width;
$photoRow->photoheight = $height;
$photoRow->save();
echo $photoRow->media->file->fileid;
Miodzio? Nie chcę wracać do czasów, gdy nie znałem ORM. Czuję się prawie jak w JPA. Acha, nie zapomnij o przeczytaniu dokumentacji Zenda bo jest bardzo dobra.
Categories: ORM, PHP, PostgreSQL, Zend Framework Tags:

Zend Framework – Zend_Db_Table

April 9th, 2007 1 comment
Krytykowałem już Zend Framework za zmienność. Dziś go pochwalę. Bardzo podoba mi się Zend_Db_Table. Jest to proste, ale prawdziwe OR/M. W dodatku, żeby go używać nie trzeba się sporo napisać i nie trzeba się wiele uczyć. Aktualizacja: 2007-04-10. Cholera! Jest sobie funkcja Zend_Db_Table_Abstract::insert() – ale nie działą dla PDO_PGSQL. Kod źródłowy, sprawia wrażenie nigdy nie testowanego. W dodatku jest nie poprawny merytorycznie. Aż zacytuje:
    public function insert(array $data)
    {
        $this->_db->insert($this->_name, $data);
        return $this->_db->lastInsertId();
    }
    public function lastInsertId($tableName = null, $primaryKey = 'id')
    {
        if (!$tableName) {
            throw new Zend_Db_Adapter_Exception("Sequence name must be specified");
        }
Zend Framework jest fajny bo nowatorski. Niestety, jest jeszcze w stadium mocno rozwojowym. Ten błąd na szczęście mogę poprawić w swojej aplikacji bez modyfikacji kodu źródłowego bibliotek Zend. Ale ile takich błędów jeszcze znajdę?
Categories: ORM, PHP, Zend Framework Tags: